diff --git a/VIPSWeb/static/js/3rdparty/ol-debug.js b/VIPSWeb/static/js/3rdparty/ol-debug.js
index e7c7f9eaceadf89b22bcefbfaac1a55d7285c925..eab35d60f3dbabfbd32d5d68746640681fb3745d 100644
--- a/VIPSWeb/static/js/3rdparty/ol-debug.js
+++ b/VIPSWeb/static/js/3rdparty/ol-debug.js
@@ -1,6 +1,6 @@
 // OpenLayers. See https://openlayers.org/
 // License: https://raw.githubusercontent.com/openlayers/openlayers/master/LICENSE.md
-// Version: v4.2.0
+// Version: v4.6.5
 ;(function (root, factory) {
   if (typeof exports === "object") {
     module.exports = factory();
@@ -30,22 +30,19 @@ this.CLOSURE_NO_DEPS = true;
 /**
  * @fileoverview Bootstrap for the Google JS Library (Closure).
  *
- * In uncompiled mode base.js will write out Closure's deps file, unless the
- * global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects to
- * include their own deps file(s) from different locations.
+ * In uncompiled mode base.js will attempt to load Closure's deps file, unless
+ * the global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects
+ * to include their own deps file(s) from different locations.
  *
  * 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
  */
 
 
 /**
- * @define {boolean} Overridden to true by the compiler when
- *     --process_closure_primitives is specified.
+ * @define {boolean} Overridden to true by the compiler.
  */
 var COMPILED = false;
 
@@ -108,8 +105,6 @@ goog.global.CLOSURE_DEFINES;
 
 /**
  * Returns true if the specified value is not undefined.
- * WARNING: Do not use this to test if an object has a property. Use the in
- * operator instead.
  *
  * @param {?} val Variable to test.
  * @return {boolean} Whether variable is defined.
@@ -120,6 +115,35 @@ goog.isDef = function(val) {
   return val !== void 0;
 };
 
+/**
+ * Returns true if the specified value is a string.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a string.
+ */
+goog.isString = function(val) {
+  return typeof val == 'string';
+};
+
+
+/**
+ * Returns true if the specified value is a boolean.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+  return typeof val == 'boolean';
+};
+
+
+/**
+ * Returns true if the specified value is a number.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+  return typeof val == 'number';
+};
+
 
 /**
  * Builds an object structure for the provided namespace path, ensuring that
@@ -129,7 +153,7 @@ goog.isDef = function(val) {
  * @param {string} name name of the object that this file defines.
  * @param {*=} opt_object the object to expose at the end of the path.
  * @param {Object=} opt_objectToExportTo The object to add the path to; default
- *     is |goog.global|.
+ *     is `goog.global`.
  * @private
  */
 goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
@@ -143,12 +167,6 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
     cur.execScript('var ' + parts[0]);
   }
 
-  // Certain browsers cannot parse code in the form for((a in b); c;);
-  // This pattern is produced by the JSCompiler when it collapses the
-  // statement above into the conditional loop below. To prevent this from
-  // happening, use a for-loop and reserve the init logic as below.
-
-  // Parentheses added to eliminate strict JS warning in Firefox.
   for (var part; parts.length && (part = parts.shift());) {
     if (!parts.length && goog.isDef(opt_object)) {
       // last part and we have an object; use it
@@ -176,11 +194,16 @@ goog.define = function(name, defaultValue) {
   var value = defaultValue;
   if (!COMPILED) {
     if (goog.global.CLOSURE_UNCOMPILED_DEFINES &&
+        // Anti DOM-clobbering runtime check (b/37736576).
+        /** @type {?} */ (goog.global.CLOSURE_UNCOMPILED_DEFINES).nodeType ===
+            undefined &&
         Object.prototype.hasOwnProperty.call(
             goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
       value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
     } else if (
         goog.global.CLOSURE_DEFINES &&
+        // Anti DOM-clobbering runtime check (b/37736576).
+        /** @type {?} */ (goog.global.CLOSURE_DEFINES).nodeType === undefined &&
         Object.prototype.hasOwnProperty.call(
             goog.global.CLOSURE_DEFINES, name)) {
       value = goog.global.CLOSURE_DEFINES[name];
@@ -192,11 +215,12 @@ goog.define = function(name, defaultValue) {
 
 /**
  * @define {boolean} DEBUG is provided as a convenience so that debugging code
- * that should not be included in a production js_binary can be easily stripped
- * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
- * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
- * because they are generally used for debugging purposes and it is difficult
- * for the JSCompiler to statically determine whether they are used.
+ * that should not be included in a production. It can be easily stripped
+ * by specifying --define goog.DEBUG=false to the Closure Compiler aka
+ * JSCompiler. For example, most toString() methods should be declared inside an
+ * "if (goog.DEBUG)" conditional because they are generally used for debugging
+ * purposes and it is difficult for the JSCompiler to statically determine
+ * whether they are used.
  */
 goog.define('goog.DEBUG', true);
 
@@ -204,7 +228,7 @@ goog.define('goog.DEBUG', true);
 /**
  * @define {string} LOCALE defines the locale being used for compilation. It is
  * used to select locale specific data to be compiled in js binary. BUILD rule
- * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
+ * can specify this value by "--define goog.LOCALE=<locale_name>" as a compiler
  * option.
  *
  * Take into account that the locale code format is important. You should use
@@ -218,7 +242,8 @@ goog.define('goog.DEBUG', true);
  * For language codes you should use values defined by ISO 693-1. See it here
  * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
  * this rule: the Hebrew language. For legacy reasons the old code (iw) should
- * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
+ * be used instead of the new code (he).
+ *
  */
 goog.define('goog.LOCALE', 'en');  // default to en
 
@@ -232,7 +257,7 @@ goog.define('goog.LOCALE', 'en');  // default to en
  *
  * If your JavaScript can be loaded by a third party site and you are wary about
  * relying on non-standard implementations, specify
- * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
+ * "--define goog.TRUSTED_SITE=false" to the compiler.
  */
 goog.define('goog.TRUSTED_SITE', true);
 
@@ -286,13 +311,13 @@ goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);
  */
 goog.provide = function(name) {
   if (goog.isInModuleLoader_()) {
-    throw Error('goog.provide can not be used within a goog.module.');
+    throw new 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
     if (goog.isProvided_(name)) {
-      throw Error('Namespace "' + name + '" already declared.');
+      throw new Error('Namespace "' + name + '" already declared.');
     }
   }
 
@@ -368,10 +393,10 @@ goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
 goog.module = function(name) {
   if (!goog.isString(name) || !name ||
       name.search(goog.VALID_MODULE_RE_) == -1) {
-    throw Error('Invalid module identifier');
+    throw new Error('Invalid module identifier');
   }
   if (!goog.isInModuleLoader_()) {
-    throw Error(
+    throw new 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 ' +
@@ -380,7 +405,7 @@ goog.module = function(name) {
         '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.');
+    throw new Error('goog.module may only be called once per module.');
   }
 
   // Store the module name for the loader.
@@ -389,7 +414,7 @@ goog.module = function(name) {
     // Ensure that the same namespace isn't provided twice.
     // A goog.module/goog.provide maps a goog.require to a specific file
     if (goog.isProvided_(name)) {
-      throw Error('Namespace "' + name + '" already declared.');
+      throw new Error('Namespace "' + name + '" already declared.');
     }
     delete goog.implicitNamespaces_[name];
   }
@@ -457,7 +482,7 @@ goog.module.declareLegacyNamespace = function() {
         'within a goog.module');
   }
   if (!COMPILED && !goog.moduleLoaderState_.moduleName) {
-    throw Error(
+    throw new Error(
         'goog.module must be called prior to ' +
         'goog.module.declareLegacyNamespace.');
   }
@@ -479,7 +504,7 @@ goog.module.declareLegacyNamespace = function() {
 goog.setTestOnly = function(opt_message) {
   if (goog.DISALLOW_TEST_ONLY_CODE) {
     opt_message = opt_message || '';
-    throw Error(
+    throw new Error(
         'Importing test-only code into non-debug environment' +
         (opt_message ? ': ' + opt_message : '.'));
   }
@@ -563,10 +588,9 @@ if (!COMPILED) {
 goog.getObjectByName = function(name, opt_obj) {
   var parts = name.split('.');
   var cur = opt_obj || goog.global;
-  for (var part; part = parts.shift();) {
-    if (goog.isDefAndNotNull(cur[part])) {
-      cur = cur[part];
-    } else {
+  for (var i = 0; i < parts.length; i++) {
+    cur = cur[parts[i]];
+    if (!goog.isDefAndNotNull(cur)) {
       return null;
     }
   }
@@ -640,7 +664,7 @@ goog.addDependency = function(relPath, provides, requires, opt_loadFlags) {
 // 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
+// base.js.  Work was done to make it disableable or replaceable for
 // different environments (DOM-less JavaScript interpreters like Rhino or V8,
 // for example). See bootstrap/ for more information.
 
@@ -672,8 +696,7 @@ goog.logToConsole_ = function(msg) {
 /**
  * Implements a system for the dynamic resolution of dependencies that works in
  * parallel with the BUILD system. Note that all calls to goog.require will be
- * stripped by the JSCompiler when the --process_closure_primitives option is
- * used.
+ * stripped by the compiler.
  * @see goog.provide
  * @param {string} name Namespace to include (as was given in goog.provide()) in
  *     the form "goog.package.part".
@@ -681,7 +704,7 @@ goog.logToConsole_ = function(msg) {
  *     module otherwise null.
  */
 goog.require = function(name) {
-  // If the object already exists we do not need do do anything.
+  // If the object already exists we do not need to do anything.
   if (!COMPILED) {
     if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) {
       goog.maybeProcessDeferredDep_(name);
@@ -699,7 +722,7 @@ goog.require = function(name) {
         var errorMessage = 'goog.require could not find: ' + name;
         goog.logToConsole_(errorMessage);
 
-        throw Error(errorMessage);
+        throw new Error(errorMessage);
       }
     }
 
@@ -723,7 +746,8 @@ goog.global.CLOSURE_BASE_PATH;
 
 
 /**
- * Whether to write out Closure's deps file. By default, the deps are written.
+ * Whether to attempt to load Closure's deps file. By default, when uncompiled,
+ * deps files will attempt to be loaded.
  * @type {boolean|undefined}
  */
 goog.global.CLOSURE_NO_DEPS;
@@ -756,14 +780,11 @@ goog.nullFunction = function() {};
  * Now if a subclass of Foo fails to override bar(), an error will be thrown
  * when bar() is invoked.
  *
- * Note: This does not take the name of the function to override as an argument
- * because that would make it more difficult to obfuscate our JavaScript code.
- *
  * @type {!Function}
  * @throws {Error} when invoked to indicate the method should be overridden.
  */
 goog.abstractMethod = function() {
-  throw Error('unimplemented abstract method');
+  throw new Error('unimplemented abstract method');
 };
 
 
@@ -899,7 +920,9 @@ if (goog.DEPENDENCIES_ENABLED) {
    * @private
    */
   goog.findBasePath_ = function() {
-    if (goog.isDef(goog.global.CLOSURE_BASE_PATH)) {
+    if (goog.isDef(goog.global.CLOSURE_BASE_PATH) &&
+        // Anti DOM-clobbering runtime check (b/37736576).
+        goog.isString(goog.global.CLOSURE_BASE_PATH)) {
       goog.basePath = goog.global.CLOSURE_BASE_PATH;
       return;
     } else if (!goog.inHtmlDocument_()) {
@@ -907,7 +930,13 @@ if (goog.DEPENDENCIES_ENABLED) {
     }
     /** @type {Document} */
     var doc = goog.global.document;
-    var scripts = doc.getElementsByTagName('SCRIPT');
+    // If we have a currentScript available, use it exclusively.
+    var currentScript = doc.currentScript;
+    if (currentScript) {
+      var scripts = [currentScript];
+    } else {
+      var scripts = doc.getElementsByTagName('SCRIPT');
+    }
     // Search backwards since the current script is in almost all cases the one
     // that has base.js.
     for (var i = scripts.length - 1; i >= 0; --i) {
@@ -1214,7 +1243,7 @@ if (goog.DEPENDENCIES_ENABLED) {
         if (isDeps) {
           return false;
         } else {
-          throw Error('Cannot write "' + src + '" after document load');
+          throw new Error('Cannot write "' + src + '" after document load');
         }
       }
 
@@ -1289,7 +1318,7 @@ if (goog.DEPENDENCIES_ENABLED) {
 
   /**
    * A readystatechange handler for legacy IE
-   * @param {!HTMLScriptElement} script
+   * @param {?} script
    * @param {number} scriptIndex
    * @return {boolean}
    * @private
@@ -1339,7 +1368,7 @@ if (goog.DEPENDENCIES_ENABLED) {
             if (requireName in deps.nameToPath) {
               visitNode(deps.nameToPath[requireName]);
             } else {
-              throw Error('Undefined nameToPath for ' + requireName);
+              throw new Error('Undefined nameToPath for ' + requireName);
             }
           }
         }
@@ -1380,7 +1409,7 @@ if (goog.DEPENDENCIES_ENABLED) {
         }
       } else {
         goog.moduleLoaderState_ = moduleState;
-        throw Error('Undefined script input');
+        throw new Error('Undefined script input');
       }
     }
 
@@ -1480,12 +1509,12 @@ goog.loadModule = function(moduleDef) {
 
       exports = goog.loadModuleFromSource_.call(undefined, moduleDef);
     } else {
-      throw Error('Invalid module definition');
+      throw new Error('Invalid module definition');
     }
 
     var moduleName = goog.moduleLoaderState_.moduleName;
     if (!goog.isString(moduleName) || !moduleName) {
-      throw Error('Invalid module name \"' + moduleName + '\"');
+      throw new Error('Invalid module name \"' + moduleName + '\"');
     }
 
     // Don't seal legacy namespaces as they may be uses as a parent of
@@ -1841,36 +1870,6 @@ goog.isDateLike = function(val) {
 };
 
 
-/**
- * Returns true if the specified value is a string.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is a string.
- */
-goog.isString = function(val) {
-  return typeof val == 'string';
-};
-
-
-/**
- * Returns true if the specified value is a boolean.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is boolean.
- */
-goog.isBoolean = function(val) {
-  return typeof val == 'boolean';
-};
-
-
-/**
- * Returns true if the specified value is a number.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is a number.
- */
-goog.isNumber = function(val) {
-  return typeof val == 'number';
-};
-
-
 /**
  * Returns true if the specified value is a function.
  * @param {?} val Variable to test.
@@ -2211,7 +2210,7 @@ goog.globalEval = function(script) {
       doc.body.removeChild(scriptElt);
     }
   } else {
-    throw Error('goog.globalEval not available');
+    throw new Error('goog.globalEval not available');
   }
 };
 
@@ -2556,12 +2555,15 @@ goog.inherits = function(childCtor, parentCtor) {
  * @return {*} The return value of the superclass method.
  * @suppress {es5Strict} This method can not be used in strict mode, but
  *     all Closure Library consumers must depend on this file.
+ * @deprecated goog.base is not strict mode compatible.  Prefer the static
+ *     "base" method added to the constructor by goog.inherits
+ *     or ES6 classes and the "super" keyword.
  */
 goog.base = function(me, opt_methodName, var_args) {
   var caller = arguments.callee.caller;
 
   if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) {
-    throw Error(
+    throw new 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');
@@ -2601,7 +2603,7 @@ goog.base = function(me, opt_methodName, var_args) {
   if (me[opt_methodName] === caller) {
     return me.constructor.prototype[opt_methodName].apply(me, args);
   } else {
-    throw Error(
+    throw new Error(
         'goog.base called from a method of one name ' +
         'to a method of a different name');
   }
@@ -2621,7 +2623,7 @@ goog.base = function(me, opt_methodName, var_args) {
  */
 goog.scope = function(fn) {
   if (goog.isInModuleLoader_()) {
-    throw Error('goog.scope is not supported within a goog.module.');
+    throw new Error('goog.scope is not supported within a goog.module.');
   }
   fn.call(goog.global);
 };
@@ -2675,7 +2677,8 @@ goog.defineClass = function(superClass, def) {
   // Wrap the constructor prior to setting up the prototype and static methods.
   if (!constructor || constructor == Object.prototype.constructor) {
     constructor = function() {
-      throw Error('cannot instantiate an interface (no constructor defined).');
+      throw new Error(
+          'cannot instantiate an interface (no constructor defined).');
     };
   }
 
@@ -4795,6 +4798,27 @@ ol.obj.isEmpty = function(object) {
   return !property;
 };
 
+goog.provide('ol.geom.GeometryType');
+
+
+/**
+ * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
+ * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
+ * `'GeometryCollection'`, `'Circle'`.
+ * @enum {string}
+ */
+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'
+};
+
 /**
  * @license
  * Latitude/longitude spherical geodesy formulae taken from
@@ -4805,6 +4829,7 @@ ol.obj.isEmpty = function(object) {
 goog.provide('ol.Sphere');
 
 goog.require('ol.math');
+goog.require('ol.geom.GeometryType');
 
 
 /**
@@ -4848,18 +4873,7 @@ ol.Sphere = function(radius) {
  * @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;
+  return ol.Sphere.getArea_(coordinates, this.radius);
 };
 
 
@@ -4872,14 +4886,7 @@ ol.Sphere.prototype.geodesicArea = function(coordinates) {
  * @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));
+  return ol.Sphere.getDistance_(c1, c2, this.radius);
 };
 
 
@@ -4905,21 +4912,203 @@ ol.Sphere.prototype.offset = function(c1, distance, bearing) {
   return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
 };
 
-goog.provide('ol.sphere.NORMAL');
 
-goog.require('ol.Sphere');
+/**
+ * The mean Earth radius (1/3 * (2a + b)) for the WGS84 ellipsoid.
+ * https://en.wikipedia.org/wiki/Earth_radius#Mean_radius
+ * @type {number}
+ */
+ol.Sphere.DEFAULT_RADIUS = 6371008.8;
 
 
 /**
- * The normal sphere.
- * @const
- * @type {ol.Sphere}
+ * Get the spherical length of a geometry.  This length is the sum of the
+ * great circle distances between coordinates.  For polygons, the length is
+ * the sum of all rings.  For points, the length is zero.  For multi-part
+ * geometries, the length is the sum of the length of each part.
+ * @param {ol.geom.Geometry} geometry A geometry.
+ * @param {olx.SphereMetricOptions=} opt_options Options for the length
+ *     calculation.  By default, geometries are assumed to be in 'EPSG:3857'.
+ *     You can change this by providing a `projection` option.
+ * @return {number} The spherical length (in meters).
+ * @api
  */
-ol.sphere.NORMAL = new ol.Sphere(6370997);
+ol.Sphere.getLength = function(geometry, opt_options) {
+  var options = opt_options || {};
+  var radius = options.radius || ol.Sphere.DEFAULT_RADIUS;
+  var projection = options.projection || 'EPSG:3857';
+  geometry = geometry.clone().transform(projection, 'EPSG:4326');
+  var type = geometry.getType();
+  var length = 0;
+  var coordinates, coords, i, ii, j, jj;
+  switch (type) {
+    case ol.geom.GeometryType.POINT:
+    case ol.geom.GeometryType.MULTI_POINT: {
+      break;
+    }
+    case ol.geom.GeometryType.LINE_STRING:
+    case ol.geom.GeometryType.LINEAR_RING: {
+      coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates();
+      length = ol.Sphere.getLength_(coordinates, radius);
+      break;
+    }
+    case ol.geom.GeometryType.MULTI_LINE_STRING:
+    case ol.geom.GeometryType.POLYGON: {
+      coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates();
+      for (i = 0, ii = coordinates.length; i < ii; ++i) {
+        length += ol.Sphere.getLength_(coordinates[i], radius);
+      }
+      break;
+    }
+    case ol.geom.GeometryType.MULTI_POLYGON: {
+      coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates();
+      for (i = 0, ii = coordinates.length; i < ii; ++i) {
+        coords = coordinates[i];
+        for (j = 0, jj = coords.length; j < jj; ++j) {
+          length += ol.Sphere.getLength_(coords[j], radius);
+        }
+      }
+      break;
+    }
+    case ol.geom.GeometryType.GEOMETRY_COLLECTION: {
+      var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
+      for (i = 0, ii = geometries.length; i < ii; ++i) {
+        length += ol.Sphere.getLength(geometries[i], opt_options);
+      }
+      break;
+    }
+    default: {
+      throw new Error('Unsupported geometry type: ' + type);
+    }
+  }
+  return length;
+};
 
-goog.provide('ol.proj.Units');
 
-goog.require('ol.sphere.NORMAL');
+/**
+ * Get the cumulative great circle length of linestring coordinates (geographic).
+ * @param {Array} coordinates Linestring coordinates.
+ * @param {number} radius The sphere radius to use.
+ * @return {number} The length (in meters).
+ */
+ol.Sphere.getLength_ = function(coordinates, radius) {
+  var length = 0;
+  for (var i = 0, ii = coordinates.length; i < ii - 1; ++i) {
+    length += ol.Sphere.getDistance_(coordinates[i], coordinates[i + 1], radius);
+  }
+  return length;
+};
+
+
+/**
+ * Get the great circle distance between two geographic coordinates.
+ * @param {Array} c1 Starting coordinate.
+ * @param {Array} c2 Ending coordinate.
+ * @param {number} radius The sphere radius to use.
+ * @return {number} The great circle distance between the points (in meters).
+ */
+ol.Sphere.getDistance_ = function(c1, c2, radius) {
+  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 * radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+};
+
+
+/**
+ * Get the spherical area of a geometry.  This is the area (in meters) assuming
+ * that polygon edges are segments of great circles on a sphere.
+ * @param {ol.geom.Geometry} geometry A geometry.
+ * @param {olx.SphereMetricOptions=} opt_options Options for the area
+ *     calculation.  By default, geometries are assumed to be in 'EPSG:3857'.
+ *     You can change this by providing a `projection` option.
+ * @return {number} The spherical area (in square meters).
+ * @api
+ */
+ol.Sphere.getArea = function(geometry, opt_options) {
+  var options = opt_options || {};
+  var radius = options.radius || ol.Sphere.DEFAULT_RADIUS;
+  var projection = options.projection || 'EPSG:3857';
+  geometry = geometry.clone().transform(projection, 'EPSG:4326');
+  var type = geometry.getType();
+  var area = 0;
+  var coordinates, coords, i, ii, j, jj;
+  switch (type) {
+    case ol.geom.GeometryType.POINT:
+    case ol.geom.GeometryType.MULTI_POINT:
+    case ol.geom.GeometryType.LINE_STRING:
+    case ol.geom.GeometryType.MULTI_LINE_STRING:
+    case ol.geom.GeometryType.LINEAR_RING: {
+      break;
+    }
+    case ol.geom.GeometryType.POLYGON: {
+      coordinates = /** @type {ol.geom.Polygon} */ (geometry).getCoordinates();
+      area = Math.abs(ol.Sphere.getArea_(coordinates[0], radius));
+      for (i = 1, ii = coordinates.length; i < ii; ++i) {
+        area -= Math.abs(ol.Sphere.getArea_(coordinates[i], radius));
+      }
+      break;
+    }
+    case ol.geom.GeometryType.MULTI_POLYGON: {
+      coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates();
+      for (i = 0, ii = coordinates.length; i < ii; ++i) {
+        coords = coordinates[i];
+        area += Math.abs(ol.Sphere.getArea_(coords[0], radius));
+        for (j = 1, jj = coords.length; j < jj; ++j) {
+          area -= Math.abs(ol.Sphere.getArea_(coords[j], radius));
+        }
+      }
+      break;
+    }
+    case ol.geom.GeometryType.GEOMETRY_COLLECTION: {
+      var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
+      for (i = 0, ii = geometries.length; i < ii; ++i) {
+        area += ol.Sphere.getArea(geometries[i], opt_options);
+      }
+      break;
+    }
+    default: {
+      throw new Error('Unsupported geometry type: ' + type);
+    }
+  }
+  return area;
+};
+
+
+/**
+ * Returns the spherical area for a list of coordinates.
+ *
+ * [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
+ *
+ * @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.
+ * @param {number} radius The sphere radius.
+ * @return {number} Area (in square meters).
+ */
+ol.Sphere.getArea_ = function(coordinates, radius) {
+  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 * radius * radius / 2.0;
+};
+
+goog.provide('ol.proj.Units');
 
 
 /**
@@ -4944,8 +5133,9 @@ ol.proj.Units = {
  * @api
  */
 ol.proj.Units.METERS_PER_UNIT = {};
+// use the radius of the Normal sphere
 ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.DEGREES] =
-    2 * Math.PI * ol.sphere.NORMAL.radius / 360;
+    2 * Math.PI * 6370997 / 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;
@@ -5015,66 +5205,75 @@ goog.require('ol.proj.proj4');
  * @api
  */
 ol.proj.Projection = function(options) {
- /**
-  * @private
-  * @type {string}
-  */
+  /**
+   * @private
+   * @type {string}
+   */
   this.code_ = options.code;
 
- /**
-  * @private
-  * @type {ol.proj.Units}
-  */
+  /**
+   * Units of projected coordinates. When set to `ol.proj.Units.TILE_PIXELS`, a
+   * `this.extent_` and `this.worldExtent_` must be configured properly for each
+   * tile.
+   * @private
+   * @type {ol.proj.Units}
+   */
   this.units_ = /** @type {ol.proj.Units} */ (options.units);
 
- /**
-  * @private
-  * @type {ol.Extent}
-  */
+  /**
+   * Validity extent of the projection in projected coordinates. For projections
+   * with `ol.proj.Units.TILE_PIXELS` units, this is the extent of the tile in
+   * tile pixel space.
+   * @private
+   * @type {ol.Extent}
+   */
   this.extent_ = options.extent !== undefined ? options.extent : null;
 
- /**
-  * @private
-  * @type {ol.Extent}
-  */
+  /**
+   * Extent of the world in EPSG:4326. For projections with
+   * `ol.proj.Units.TILE_PIXELS` units, this is the extent of the tile in
+   * projected coordinate space.
+   * @private
+   * @type {ol.Extent}
+   */
   this.worldExtent_ = options.worldExtent !== undefined ?
-     options.worldExtent : null;
+    options.worldExtent : null;
 
- /**
-  * @private
-  * @type {string}
-  */
+  /**
+   * @private
+   * @type {string}
+   */
   this.axisOrientation_ = options.axisOrientation !== undefined ?
-     options.axisOrientation : 'enu';
+    options.axisOrientation : 'enu';
 
- /**
-  * @private
-  * @type {boolean}
-  */
+  /**
+   * @private
+   * @type {boolean}
+   */
   this.global_ = options.global !== undefined ? options.global : false;
 
- /**
-  * @private
-  * @type {boolean}
-  */
+  /**
+   * @private
+   * @type {boolean}
+   */
   this.canWrapX_ = !!(this.global_ && this.extent_);
 
- /**
- * @private
- * @type {function(number, ol.Coordinate):number|undefined}
- */
+  /**
+   * @private
+   * @type {function(number, ol.Coordinate):number|undefined}
+   */
   this.getPointResolutionFunc_ = options.getPointResolution;
 
- /**
-  * @private
-  * @type {ol.tilegrid.TileGrid}
-  */
+  /**
+   * @private
+   * @type {ol.tilegrid.TileGrid}
+   */
   this.defaultTileGrid_ = null;
 
- /**
-  * @private
-  * @type {number|undefined}
-  */
+  /**
+   * @private
+   * @type {number|undefined}
+   */
   this.metersPerUnit_ = options.metersPerUnit;
 
   var code = options.code;
@@ -5167,6 +5366,7 @@ ol.proj.Projection.prototype.getWorldExtent = function() {
  * wnu - westing, northing, up - some planetary coordinate systems have
  *     "west positive" coordinate systems
  * @return {string} Axis orientation.
+ * @api
  */
 ol.proj.Projection.prototype.getAxisOrientation = function() {
   return this.axisOrientation_;
@@ -5285,6 +5485,8 @@ ol.inherits(ol.proj.EPSG3857.Projection_, ol.proj.Projection);
 
 
 /**
+ * Radius of WGS84 sphere
+ *
  * @const
  * @type {number}
  */
@@ -5315,31 +5517,21 @@ ol.proj.EPSG3857.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.Projection_(code);
-});
+ol.proj.EPSG3857.PROJECTIONS = [
+  new ol.proj.EPSG3857.Projection_('EPSG:3857'),
+  new ol.proj.EPSG3857.Projection_('EPSG:102100'),
+  new ol.proj.EPSG3857.Projection_('EPSG:102113'),
+  new ol.proj.EPSG3857.Projection_('EPSG:900913'),
+  new ol.proj.EPSG3857.Projection_('urn:ogc:def:crs:EPSG:6.18:3:3857'),
+  new ol.proj.EPSG3857.Projection_('urn:ogc:def:crs:EPSG::3857'),
+  new ol.proj.EPSG3857.Projection_('http://www.opengis.net/gml/srs/epsg.xml#3857')
+];
 
 
 /**
@@ -5406,24 +5598,11 @@ ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) {
   return output;
 };
 
-goog.provide('ol.sphere.WGS84');
-
-goog.require('ol.Sphere');
-
-
-/**
- * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid.
- * @const
- * @type {ol.Sphere}
- */
-ol.sphere.WGS84 = new ol.Sphere(6378137);
-
 goog.provide('ol.proj.EPSG4326');
 
 goog.require('ol');
 goog.require('ol.proj.Projection');
 goog.require('ol.proj.Units');
-goog.require('ol.sphere.WGS84');
 
 
 /**
@@ -5454,6 +5633,15 @@ ol.proj.EPSG4326.Projection_ = function(code, opt_axisOrientation) {
 ol.inherits(ol.proj.EPSG4326.Projection_, ol.proj.Projection);
 
 
+/**
+ * Radius of WGS84 sphere
+ *
+ * @const
+ * @type {number}
+ */
+ol.proj.EPSG4326.RADIUS = 6378137;
+
+
 /**
  * Extent of the EPSG:4326 projection which is the whole world.
  *
@@ -5467,7 +5655,7 @@ ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90];
  * @const
  * @type {number}
  */
-ol.proj.EPSG4326.METERS_PER_UNIT = Math.PI * ol.sphere.WGS84.radius / 180;
+ol.proj.EPSG4326.METERS_PER_UNIT = Math.PI * ol.proj.EPSG4326.RADIUS / 180;
 
 
 /**
@@ -5605,7 +5793,9 @@ ol.proj.transforms.get = function(sourceCode, destinationCode) {
 goog.provide('ol.proj');
 
 goog.require('ol');
+goog.require('ol.Sphere');
 goog.require('ol.extent');
+goog.require('ol.math');
 goog.require('ol.proj.EPSG3857');
 goog.require('ol.proj.EPSG4326');
 goog.require('ol.proj.Projection');
@@ -5613,7 +5803,6 @@ 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');
 
 
 /**
@@ -5625,6 +5814,14 @@ goog.require('ol.sphere.NORMAL');
 ol.proj.METERS_PER_UNIT = ol.proj.Units.METERS_PER_UNIT;
 
 
+/**
+ * A place to store the mean radius of the Earth.
+ * @private
+ * @type {ol.Sphere}
+ */
+ol.proj.SPHERE_ = new ol.Sphere(ol.Sphere.DEFAULT_RADIUS);
+
+
 if (ol.ENABLE_PROJ4JS) {
   /**
    * Register proj4. If not explicitly registered, it will be assumed that
@@ -5659,10 +5856,12 @@ if (ol.ENABLE_PROJ4JS) {
  * @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.
+ * @param {ol.proj.Units=} opt_units Units to get the point resolution in.
+ * Default is the projection's units.
+ * @return {number} Point resolution.
  * @api
  */
-ol.proj.getPointResolution = function(projection, resolution, point) {
+ol.proj.getPointResolution = function(projection, resolution, point, opt_units) {
   projection = ol.proj.get(projection);
   var pointResolution;
   var getter = projection.getPointResolutionFunc();
@@ -5670,7 +5869,7 @@ ol.proj.getPointResolution = function(projection, resolution, point) {
     pointResolution = getter(resolution, point);
   } else {
     var units = projection.getUnits();
-    if (units == ol.proj.Units.DEGREES) {
+    if (units == ol.proj.Units.DEGREES && !opt_units || opt_units == ol.proj.Units.DEGREES) {
       pointResolution = resolution;
     } else {
       // Estimate point resolution by transforming the center pixel to EPSG:4326,
@@ -5684,12 +5883,14 @@ ol.proj.getPointResolution = function(projection, resolution, point) {
         point[0], point[1] + resolution / 2
       ];
       vertices = toEPSG4326(vertices, vertices, 2);
-      var width = ol.sphere.NORMAL.haversineDistance(
+      var width = ol.proj.SPHERE_.haversineDistance(
           vertices.slice(0, 2), vertices.slice(2, 4));
-      var height = ol.sphere.NORMAL.haversineDistance(
+      var height = ol.proj.SPHERE_.haversineDistance(
           vertices.slice(4, 6), vertices.slice(6, 8));
       pointResolution = (width + height) / 2;
-      var metersPerUnit = projection.getMetersPerUnit();
+      var metersPerUnit = opt_units ?
+        ol.proj.Units.METERS_PER_UNIT[opt_units] :
+        projection.getMetersPerUnit();
       if (metersPerUnit !== undefined) {
         pointResolution /= metersPerUnit;
       }
@@ -5825,27 +6026,27 @@ ol.proj.addCoordinateTransforms = function(source, destination, forward, inverse
  */
 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];
-          }
+    /**
+     * @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;
-      });
+      }
+      return output;
+    });
 };
 
 
@@ -5874,8 +6075,13 @@ ol.proj.fromLonLat = function(coordinate, opt_projection) {
  * @api
  */
 ol.proj.toLonLat = function(coordinate, opt_projection) {
-  return ol.proj.transform(coordinate,
+  var lonLat = ol.proj.transform(coordinate,
       opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326');
+  var lon = lonLat[0];
+  if (lon < -180 || lon > 180) {
+    lonLat[0] = ol.math.modulo(lon + 180, 360) - 180;
+  }
+  return lonLat;
 };
 
 
@@ -5895,9 +6101,9 @@ ol.proj.get = function(projectionLike) {
   } else if (typeof projectionLike === 'string') {
     var code = projectionLike;
     projection = ol.proj.projections.get(code);
-    if (ol.ENABLE_PROJ4JS) {
+    if (ol.ENABLE_PROJ4JS && !projection) {
       var proj4js = ol.proj.proj4.get();
-      if (!projection && typeof proj4js == 'function' &&
+      if (typeof proj4js == 'function' &&
           proj4js.defs(code) !== undefined) {
         projection = new ol.proj.Projection({code: code});
         ol.proj.addProjection(projection);
@@ -6131,6 +6337,26 @@ ol.tilecoord.getKeyZXY = function(z, x, y) {
 };
 
 
+/**
+ * Get the key for a tile coord.
+ * @param {ol.TileCoord} tileCoord The tile coord.
+ * @return {string} Key.
+ */
+ol.tilecoord.getKey = function(tileCoord) {
+  return ol.tilecoord.getKeyZXY(tileCoord[0], tileCoord[1], tileCoord[2]);
+};
+
+
+/**
+ * Get a tile coord given a key.
+ * @param {string} key The tile coord key.
+ * @return {ol.TileCoord} The tile coord.
+ */
+ol.tilecoord.fromKey = function(key) {
+  return key.split('/').map(Number);
+};
+
+
 /**
  * @param {ol.TileCoord} tileCoord Tile coord.
  * @return {number} Hash.
@@ -6231,6 +6457,30 @@ ol.tilegrid.TileGrid = function(options) {
     return b - a;
   }, true), 17); // `resolutions` must be sorted in descending order
 
+
+  // check if we've got a consistent zoom factor and origin
+  var zoomFactor;
+  if (!options.origins) {
+    for (var i = 0, ii = this.resolutions_.length - 1; i < ii; ++i) {
+      if (!zoomFactor) {
+        zoomFactor = this.resolutions_[i] / this.resolutions_[i + 1];
+      } else {
+        if (this.resolutions_[i] / this.resolutions_[i + 1] !== zoomFactor) {
+          zoomFactor = undefined;
+          break;
+        }
+      }
+    }
+  }
+
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.zoomFactor_ = zoomFactor;
+
+
   /**
    * @protected
    * @type {number}
@@ -6281,8 +6531,8 @@ ol.tilegrid.TileGrid = function(options) {
    * @type {number|ol.Size}
    */
   this.tileSize_ = options.tileSize !== undefined ?
-      options.tileSize :
-      !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null;
+    options.tileSize :
+    !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null;
   ol.asserts.assert(
       (!this.tileSize_ && this.tileSizes_) ||
       (this.tileSize_ && !this.tileSizes_),
@@ -6332,7 +6582,7 @@ ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];
  * Call a function with each tile coordinate for a given extent and zoom level.
  *
  * @param {ol.Extent} extent Extent.
- * @param {number} zoom Zoom level.
+ * @param {number} zoom Integer zoom level.
  * @param {function(ol.TileCoord)} callback Function called with each tile coordinate.
  * @api
  */
@@ -6356,11 +6606,24 @@ ol.tilegrid.TileGrid.prototype.forEachTileCoord = function(extent, zoom, callbac
  * @template T
  */
 ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) {
-  var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
+  var tileRange, x, y;
+  var tileCoordExtent = null;
   var z = tileCoord[0] - 1;
+  if (this.zoomFactor_ === 2) {
+    x = tileCoord[1];
+    y = tileCoord[2];
+  } else {
+    tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
+  }
   while (z >= this.minZoom) {
-    if (callback.call(opt_this, z,
-        this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) {
+    if (this.zoomFactor_ === 2) {
+      x = Math.floor(x / 2);
+      y = Math.floor(y / 2);
+      tileRange = ol.TileRange.createOrUpdate(x, x, y, y, opt_tileRange);
+    } else {
+      tileRange = this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange);
+    }
+    if (callback.call(opt_this, z, tileRange)) {
       return true;
     }
     --z;
@@ -6400,7 +6663,7 @@ ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
 
 /**
  * Get the origin for the grid at the given zoom level.
- * @param {number} z Z.
+ * @param {number} z Integer zoom level.
  * @return {ol.Coordinate} Origin.
  * @api
  */
@@ -6415,7 +6678,7 @@ ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
 
 /**
  * Get the resolution for the given zoom level.
- * @param {number} z Z.
+ * @param {number} z Integer zoom level.
  * @return {number} Resolution.
  * @api
  */
@@ -6442,17 +6705,22 @@ ol.tilegrid.TileGrid.prototype.getResolutions = function() {
  */
 ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = function(tileCoord, opt_tileRange, opt_extent) {
   if (tileCoord[0] < this.maxZoom) {
+    if (this.zoomFactor_ === 2) {
+      var minX = tileCoord[1] * 2;
+      var minY = tileCoord[2] * 2;
+      return ol.TileRange.createOrUpdate(minX, minX + 1, minY, minY + 1, opt_tileRange);
+    }
     var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
     return this.getTileRangeForExtentAndZ(
         tileCoordExtent, tileCoord[0] + 1, opt_tileRange);
-  } else {
-    return null;
   }
+  return null;
 };
 
 
 /**
- * @param {number} z Z.
+ * Get the extent for a tile range.
+ * @param {number} z Integer zoom level.
  * @param {ol.TileRange} tileRange Tile range.
  * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
  * @return {ol.Extent} Extent.
@@ -6470,37 +6738,23 @@ ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_e
 
 
 /**
+ * Get a tile range for the given extent and integer zoom level.
  * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
+ * @param {number} z Integer zoom level.
  * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
  * @return {ol.TileRange} Tile range.
  */
-ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(extent, resolution, opt_tileRange) {
+ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) {
   var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_;
-  this.getTileCoordForXYAndResolution_(
-      extent[0], extent[1], resolution, false, tileCoord);
+  this.getTileCoordForXYAndZ_(extent[0], extent[1], z, false, tileCoord);
   var minX = tileCoord[1];
   var minY = tileCoord[2];
-  this.getTileCoordForXYAndResolution_(
-      extent[2], extent[3], resolution, true, tileCoord);
+  this.getTileCoordForXYAndZ_(extent[2], extent[3], z, true, tileCoord);
   return ol.TileRange.createOrUpdate(
       minX, tileCoord[1], minY, tileCoord[2], opt_tileRange);
 };
 
 
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} z Z.
- * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
- * @return {ol.TileRange} Tile range.
- */
-ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) {
-  var resolution = this.getResolution(z);
-  return this.getTileRangeForExtentAndResolution(
-      extent, resolution, opt_tileRange);
-};
-
-
 /**
  * @param {ol.TileCoord} tileCoord Tile coordinate.
  * @return {ol.Coordinate} Tile center.
@@ -6554,9 +6808,11 @@ ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coor
 
 
 /**
+ * Note that this method should not be called for resolutions that correspond
+ * to an integer zoom level.  Instead call the `getTileCoordForXYAndZ_` method.
  * @param {number} x X.
  * @param {number} y Y.
- * @param {number} resolution Resolution.
+ * @param {number} resolution Resolution (for a non-integer zoom level).
  * @param {boolean} reverseIntersectionPolicy Instead of letting edge
  *     intersections go to the higher tile coordinate, let edge intersections
  *     go to the lower tile coordinate.
@@ -6590,6 +6846,45 @@ ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
 };
 
 
+/**
+ * Although there is repetition between this method and `getTileCoordForXYAndResolution_`,
+ * they should have separate implementations.  This method is for integer zoom
+ * levels.  The other method should only be called for resolutions corresponding
+ * to non-integer zoom levels.
+ * @param {number} x Map x coordinate.
+ * @param {number} y Map y coordinate.
+ * @param {number} z Integer zoom level.
+ * @param {boolean} reverseIntersectionPolicy Instead of letting edge
+ *     intersections go to the higher tile coordinate, let edge intersections
+ *     go to the lower tile coordinate.
+ * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object.
+ * @return {ol.TileCoord} Tile coordinate.
+ * @private
+ */
+ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndZ_ = function(x, y, z, reverseIntersectionPolicy, opt_tileCoord) {
+  var origin = this.getOrigin(z);
+  var resolution = this.getResolution(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 = xFromOrigin / tileSize[0];
+  var tileCoordY = yFromOrigin / tileSize[1];
+
+  if (reverseIntersectionPolicy) {
+    tileCoordX = Math.ceil(tileCoordX) - 1;
+    tileCoordY = Math.ceil(tileCoordY) - 1;
+  } else {
+    tileCoordX = Math.floor(tileCoordX);
+    tileCoordY = Math.floor(tileCoordY);
+  }
+
+  return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord);
+};
+
+
 /**
  * Get a tile coordinate given a map coordinate and zoom level.
  * @param {ol.Coordinate} coordinate Coordinate.
@@ -6599,9 +6894,8 @@ ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
  * @api
  */
 ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = function(coordinate, z, opt_tileCoord) {
-  var resolution = this.getResolution(z);
-  return this.getTileCoordForXYAndResolution_(
-      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
+  return this.getTileCoordForXYAndZ_(
+      coordinate[0], coordinate[1], z, false, opt_tileCoord);
 };
 
 
@@ -6732,7 +7026,7 @@ ol.tilegrid.wrapX = function(tileGrid, tileCoord, projection) {
  */
 ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) {
   var corner = opt_corner !== undefined ?
-      opt_corner : ol.extent.Corner.TOP_LEFT;
+    opt_corner : ol.extent.Corner.TOP_LEFT;
 
   var resolutions = ol.tilegrid.resolutionsFromExtent(
       extent, opt_maxZoom, opt_tileSize);
@@ -6755,7 +7049,7 @@ ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_co
 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} */ ({}));
+    opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({}));
   if (options.extent === undefined) {
     options.extent = ol.proj.get('EPSG:3857').getExtent();
   }
@@ -6778,13 +7072,13 @@ ol.tilegrid.createXYZ = function(opt_options) {
  */
 ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) {
   var maxZoom = opt_maxZoom !== undefined ?
-      opt_maxZoom : ol.DEFAULT_MAX_ZOOM;
+    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);
+    opt_tileSize : ol.DEFAULT_TILE_SIZE);
   var maxResolution = Math.max(
       width / tileSize[0], height / tileSize[1]);
 
@@ -6855,6 +7149,7 @@ goog.require('ol.tilegrid');
  *     ..
  *
  * @constructor
+ * @deprecated This class is deprecated and will removed in the next major release.
  * @param {olx.AttributionOptions} options Attribution options.
  * @struct
  * @api
@@ -6929,42 +7224,6 @@ ol.Attribution.prototype.intersectsAnyTileRange = function(tileRanges, tileGrid,
   return false;
 };
 
-goog.provide('ol.CenterConstraint');
-
-goog.require('ol.math');
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @return {ol.CenterConstraintType} The constraint.
- */
-ol.CenterConstraint.createExtent = function(extent) {
-  return (
-      /**
-       * @param {ol.Coordinate|undefined} center Center.
-       * @return {ol.Coordinate|undefined} Center.
-       */
-      function(center) {
-        if (center) {
-          return [
-            ol.math.clamp(center[0], extent[0], extent[2]),
-            ol.math.clamp(center[1], extent[1], extent[3])
-          ];
-        } else {
-          return undefined;
-        }
-      });
-};
-
-
-/**
- * @param {ol.Coordinate|undefined} center Center.
- * @return {ol.Coordinate|undefined} Center.
- */
-ol.CenterConstraint.none = function(center) {
-  return center;
-};
-
 goog.provide('ol.CollectionEventType');
 
 /**
@@ -7322,15 +7581,15 @@ ol.events.Event = function(type) {
  */
 ol.events.Event.prototype.preventDefault =
 
-/**
- * Stop event propagation.
- * @function
- * @override
- * @api
- */
-ol.events.Event.prototype.stopPropagation = function() {
-  this.propagationStopped = true;
-};
+  /**
+   * Stop event propagation.
+   * @function
+   * @override
+   * @api
+   */
+  ol.events.Event.prototype.stopPropagation = function() {
+    this.propagationStopped = true;
+  };
 
 
 /**
@@ -7481,8 +7740,8 @@ ol.events.EventTarget.prototype.getListeners = function(type) {
  */
 ol.events.EventTarget.prototype.hasListener = function(opt_type) {
   return opt_type ?
-      opt_type in this.listeners_ :
-      Object.keys(this.listeners_).length > 0;
+    opt_type in this.listeners_ :
+    Object.keys(this.listeners_).length > 0;
 };
 
 
@@ -7521,6 +7780,7 @@ ol.events.EventType = {
    */
   CHANGE: 'change',
 
+  CLEAR: 'clear',
   CLICK: 'click',
   DBLCLICK: 'dblclick',
   DRAGENTER: 'dragenter',
@@ -7789,8 +8049,8 @@ ol.Object.changeEventTypeCache_ = {};
  */
 ol.Object.getChangeEventType = function(key) {
   return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
-      ol.Object.changeEventTypeCache_[key] :
-      (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
+    ol.Object.changeEventTypeCache_[key] :
+    (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
 };
 
 
@@ -7951,7 +8211,7 @@ goog.require('ol.events.Event');
  * @constructor
  * @extends {ol.Object}
  * @fires ol.Collection.Event
- * @param {!Array.<T>=} opt_array Array.
+ * @param {Array.<T>=} opt_array Array.
  * @param {olx.CollectionOptions=} opt_options Collection options.
  * @template T
  * @api
@@ -8023,7 +8283,11 @@ ol.Collection.prototype.extend = function(arr) {
  * @api
  */
 ol.Collection.prototype.forEach = function(f, opt_this) {
-  this.array_.forEach(f, opt_this);
+  var fn = (opt_this) ? f.bind(opt_this) : f;
+  var array = this.array_;
+  for (var i = 0, ii = array.length; i < ii; ++i) {
+    fn(array[i], i, array);
+  }
 };
 
 
@@ -8225,2577 +8489,2031 @@ ol.Collection.Event = function(type, opt_element) {
 };
 ol.inherits(ol.Collection.Event, ol.events.Event);
 
-goog.provide('ol.color');
+goog.provide('ol.MapEvent');
 
-goog.require('ol.asserts');
-goog.require('ol.math');
+goog.require('ol');
+goog.require('ol.events.Event');
 
 
 /**
- * This RegExp matches # followed by 3 or 6 hex digits.
- * @const
- * @type {RegExp}
- * @private
+ * @classdesc
+ * Events emitted as map events are instances of this type.
+ * See {@link ol.Map} for which events trigger a map event.
+ *
+ * @constructor
+ * @extends {ol.events.Event}
+ * @implements {oli.MapEvent}
+ * @param {string} type Event type.
+ * @param {ol.PluggableMap} map Map.
+ * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-ol.color.HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
+ol.MapEvent = function(type, map, opt_frameState) {
+
+  ol.events.Event.call(this, type);
+
+  /**
+   * The map where the event occurred.
+   * @type {ol.PluggableMap}
+   * @api
+   */
+  this.map = map;
+
+  /**
+   * 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);
+
+goog.provide('ol.MapBrowserEvent');
+
+goog.require('ol');
+goog.require('ol.MapEvent');
 
 
 /**
- * Regular expression for matching potential named color style strings.
- * @const
- * @type {RegExp}
- * @private
+ * @classdesc
+ * Events emitted as map browser events are instances of this type.
+ * See {@link ol.Map} for which events trigger a map browser event.
+ *
+ * @constructor
+ * @extends {ol.MapEvent}
+ * @implements {oli.MapBrowserEvent}
+ * @param {string} type Event type.
+ * @param {ol.PluggableMap} map Map.
+ * @param {Event} browserEvent Browser event.
+ * @param {boolean=} opt_dragging Is the map currently being dragged?
+ * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-ol.color.NAMED_COLOR_RE_ = /^([a-z]*)$/i;
+ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging,
+    opt_frameState) {
+
+  ol.MapEvent.call(this, type, map, opt_frameState);
+
+  /**
+   * The original browser event.
+   * @const
+   * @type {Event}
+   * @api
+   */
+  this.originalEvent = browserEvent;
+
+  /**
+   * The map pixel relative to the viewport corresponding to the original browser event.
+   * @type {ol.Pixel}
+   * @api
+   */
+  this.pixel = map.getEventPixel(browserEvent);
+
+  /**
+   * The coordinate in view projection corresponding to the original browser event.
+   * @type {ol.Coordinate}
+   * @api
+   */
+  this.coordinate = map.getCoordinateFromPixel(this.pixel);
+
+  /**
+   * Indicates if the map is currently being dragged. Only set for
+   * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`.
+   *
+   * @type {boolean}
+   * @api
+   */
+  this.dragging = opt_dragging !== undefined ? opt_dragging : false;
+
+};
+ol.inherits(ol.MapBrowserEvent, ol.MapEvent);
 
 
 /**
- * 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.
+ * Prevents the default browser action.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
+ * @override
  * @api
  */
-ol.color.asArray = function(color) {
-  if (Array.isArray(color)) {
-    return color;
-  } else {
-    return ol.color.fromString(/** @type {string} */ (color));
-  }
+ol.MapBrowserEvent.prototype.preventDefault = function() {
+  ol.MapEvent.prototype.preventDefault.call(this);
+  this.originalEvent.preventDefault();
 };
 
 
 /**
- * Return the color as an rgba string.
- * @param {ol.Color|string} color Color.
- * @return {string} Rgba string.
+ * Prevents further propagation of the current event.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
+ * @override
  * @api
  */
-ol.color.asString = function(color) {
-  if (typeof color === 'string') {
-    return color;
-  } else {
-    return ol.color.toString(color);
-  }
+ol.MapBrowserEvent.prototype.stopPropagation = function() {
+  ol.MapEvent.prototype.stopPropagation.call(this);
+  this.originalEvent.stopPropagation();
 };
 
+goog.provide('ol.webgl');
+
 /**
- * Return named color as an rgba string.
- * @param {string} color Named color.
- * @return {string} Rgb string.
+ * Constants taken from goog.webgl
  */
-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;
-};
 
 
 /**
- * @param {string} s String.
- * @return {ol.Color} Color.
+ * @const
+ * @type {number}
  */
-ol.color.fromString = (
-    function() {
+ol.webgl.ONE = 1;
 
-      // 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;
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.SRC_ALPHA = 0x0302;
 
-      /**
-       * @type {Object.<string, ol.Color>}
-       */
-      var cache = {};
 
-      /**
-       * @type {number}
-       */
-      var cacheSize = 0;
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0;
 
-      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;
-          });
 
-    })();
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.COLOR_BUFFER_BIT = 0x00004000;
 
 
 /**
- * @param {string} s String.
- * @private
- * @return {ol.Color} Color.
+ * @const
+ * @type {number}
  */
-ol.color.fromStringInternal_ = function(s) {
-  var r, g, b, a, color, parts;
+ol.webgl.TRIANGLES = 0x0004;
 
-  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);
-};
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.TRIANGLE_STRIP = 0x0005;
 
 
 /**
- * @param {ol.Color} color Color.
- * @param {ol.Color=} opt_color Color.
- * @return {ol.Color} Clamped color.
+ * @const
+ * @type {number}
  */
-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.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;
 
 
 /**
- * @param {ol.Color} color Color.
- * @return {string} String.
+ * @const
+ * @type {number}
  */
-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 + ')';
-};
+ol.webgl.ARRAY_BUFFER = 0x8892;
 
-goog.provide('ol.colorlike');
 
-goog.require('ol.color');
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;
 
 
 /**
- * @param {ol.Color|ol.ColorLike} color Color.
- * @return {ol.ColorLike} The color as an ol.ColorLike
- * @api
+ * @const
+ * @type {number}
  */
-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));
-  }
-};
+ol.webgl.STREAM_DRAW = 0x88E0;
 
 
 /**
- * @param {?} color The value that is potentially an ol.ColorLike
- * @return {boolean} Whether the color is an ol.ColorLike
+ * @const
+ * @type {number}
  */
-ol.colorlike.isColorLike = function(color) {
-  return (
-      typeof color === 'string' ||
-      color instanceof CanvasPattern ||
-      color instanceof CanvasGradient
-  );
-};
-
-goog.provide('ol.dom');
+ol.webgl.STATIC_DRAW = 0x88E4;
 
 
 /**
- * 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.
+ * @const
+ * @type {number}
  */
-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');
-};
+ol.webgl.DYNAMIC_DRAW = 0x88E8;
 
 
 /**
- * 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.
+ * @const
+ * @type {number}
  */
-ol.dom.outerWidth = function(element) {
-  var width = element.offsetWidth;
-  var style = getComputedStyle(element);
-  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
-
-  return width;
-};
+ol.webgl.CULL_FACE = 0x0B44;
 
 
 /**
- * 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.
+ * @const
+ * @type {number}
  */
-ol.dom.outerHeight = function(element) {
-  var height = element.offsetHeight;
-  var style = getComputedStyle(element);
-  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
+ol.webgl.BLEND = 0x0BE2;
 
-  return height;
-};
 
 /**
- * @param {Node} newNode Node to replace old node
- * @param {Node} oldNode The node to be replaced
+ * @const
+ * @type {number}
  */
-ol.dom.replaceNode = function(newNode, oldNode) {
-  var parent = oldNode.parentNode;
-  if (parent) {
-    parent.replaceChild(newNode, oldNode);
-  }
-};
+ol.webgl.STENCIL_TEST = 0x0B90;
+
 
 /**
- * @param {Node} node The node to remove.
- * @returns {Node} The node that was removed or null.
+ * @const
+ * @type {number}
  */
-ol.dom.removeNode = function(node) {
-  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
-};
+ol.webgl.DEPTH_TEST = 0x0B71;
+
 
 /**
- * @param {Node} node The node to remove the children from.
+ * @const
+ * @type {number}
  */
-ol.dom.removeChildren = function(node) {
-  while (node.lastChild) {
-    node.removeChild(node.lastChild);
-  }
-};
+ol.webgl.SCISSOR_TEST = 0x0C11;
 
-goog.provide('ol.MapEventType');
 
 /**
- * @enum {string}
+ * @const
+ * @type {number}
  */
-ol.MapEventType = {
-
-  /**
-   * Triggered after a map frame is rendered.
-   * @event ol.MapEvent#postrender
-   * @api
-   */
-  POSTRENDER: 'postrender',
-
-  /**
-   * Triggered when the map starts moving.
-   * @event ol.MapEvent#movestart
-   * @api
-   */
-  MOVESTART: 'movestart',
-
-  /**
-   * Triggered after the map is moved.
-   * @event ol.MapEvent#moveend
-   * @api
-   */
-  MOVEEND: 'moveend'
-
-};
-
-goog.provide('ol.control.Control');
-
-goog.require('ol');
-goog.require('ol.MapEventType');
-goog.require('ol.Object');
-goog.require('ol.dom');
-goog.require('ol.events');
+ol.webgl.UNSIGNED_BYTE = 0x1401;
 
 
 /**
- * @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
+ * @const
+ * @type {number}
  */
-ol.control.Control = function(options) {
-
-  ol.Object.call(this);
+ol.webgl.UNSIGNED_SHORT = 0x1403;
 
-  /**
-   * @protected
-   * @type {Element}
-   */
-  this.element = options.element ? options.element : null;
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.target_ = null;
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = null;
 
-  /**
-   * @protected
-   * @type {!Array.<ol.EventsKey>}
-   */
-  this.listenerKeys = [];
-
-  /**
-   * @type {function(ol.MapEvent)}
-   */
-  this.render = options.render ? options.render : ol.nullFunction;
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.UNSIGNED_INT = 0x1405;
 
-  if (options.target) {
-    this.setTarget(options.target);
-  }
 
-};
-ol.inherits(ol.control.Control, ol.Object);
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.FLOAT = 0x1406;
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {number}
  */
-ol.control.Control.prototype.disposeInternal = function() {
-  ol.dom.removeNode(this.element);
-  ol.Object.prototype.disposeInternal.call(this);
-};
+ol.webgl.RGBA = 0x1908;
 
 
 /**
- * Get the map associated with this control.
- * @return {ol.Map} Map.
- * @api
+ * @const
+ * @type {number}
  */
-ol.control.Control.prototype.getMap = function() {
-  return this.map_;
-};
+ol.webgl.FRAGMENT_SHADER = 0x8B30;
 
 
 /**
- * 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
+ * @const
+ * @type {number}
  */
-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]);
-  }
-  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();
-  }
-};
+ol.webgl.VERTEX_SHADER = 0x8B31;
 
 
 /**
- * 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
+ * @const
+ * @type {number}
  */
-ol.control.Control.prototype.setTarget = function(target) {
-  this.target_ = typeof target === 'string' ?
-    document.getElementById(target) :
-    target;
-};
-
-goog.provide('ol.css');
+ol.webgl.LINK_STATUS = 0x8B82;
 
 
 /**
- * The CSS class for hidden feature.
- *
  * @const
- * @type {string}
+ * @type {number}
  */
-ol.css.CLASS_HIDDEN = 'ol-hidden';
+ol.webgl.LINEAR = 0x2601;
 
 
 /**
- * The CSS class that we'll give the DOM elements to have them selectable.
- *
  * @const
- * @type {string}
+ * @type {number}
  */
-ol.css.CLASS_SELECTABLE = 'ol-selectable';
+ol.webgl.TEXTURE_MAG_FILTER = 0x2800;
+
 
 /**
- * The CSS class that we'll give the DOM elements to have them unselectable.
- *
  * @const
- * @type {string}
+ * @type {number}
  */
-ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
+ol.webgl.TEXTURE_MIN_FILTER = 0x2801;
 
 
 /**
- * The CSS class for unsupported feature.
- *
  * @const
- * @type {string}
+ * @type {number}
  */
-ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
+ol.webgl.TEXTURE_WRAP_S = 0x2802;
 
 
 /**
- * The CSS class for controls.
- *
  * @const
- * @type {string}
+ * @type {number}
  */
-ol.css.CLASS_CONTROL = 'ol-control';
-
-// FIXME handle date line wrap
+ol.webgl.TEXTURE_WRAP_T = 0x2803;
 
-goog.provide('ol.control.Attribution');
 
-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');
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.TEXTURE_2D = 0x0DE1;
 
 
 /**
- * @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
+ * @const
+ * @type {number}
  */
-ol.control.Attribution = function(opt_options) {
+ol.webgl.TEXTURE0 = 0x84C0;
 
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.ulElement_ = document.createElement('UL');
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.logoLi_ = document.createElement('LI');
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.CLAMP_TO_EDGE = 0x812F;
 
-  this.ulElement_.appendChild(this.logoLi_);
-  this.logoLi_.style.display = 'none';
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.COMPILE_STATUS = 0x8B81;
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.collapsible_ = options.collapsible !== undefined ?
-      options.collapsible : true;
 
-  if (!this.collapsible_) {
-    this.collapsed_ = false;
-  }
+/**
+ * @const
+ * @type {number}
+ */
+ol.webgl.FRAMEBUFFER = 0x8D40;
 
-  var className = options.className !== undefined ? options.className : 'ol-attribution';
 
-  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions';
+/** end of goog.webgl constants
+ */
 
-  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB';
 
-  if (typeof collapseLabel === 'string') {
-    /**
-     * @private
-     * @type {Node}
-     */
-    this.collapseLabel_ = document.createElement('span');
-    this.collapseLabel_.textContent = collapseLabel;
-  } else {
-    this.collapseLabel_ = collapseLabel;
-  }
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.webgl.CONTEXT_IDS_ = [
+  'experimental-webgl',
+  'webgl',
+  'webkit-3d',
+  'moz-webgl'
+];
 
-  var label = options.label !== undefined ? options.label : 'i';
 
-  if (typeof label === 'string') {
-    /**
-     * @private
-     * @type {Node}
-     */
-    this.label_ = document.createElement('span');
-    this.label_.textContent = label;
-  } else {
-    this.label_ = label;
+/**
+ * @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
+    }
   }
+  return null;
+};
 
+goog.provide('ol.has');
 
-  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);
+goog.require('ol');
+goog.require('ol.webgl');
 
-  ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this);
+var ua = typeof navigator !== 'undefined' ?
+  navigator.userAgent.toLowerCase() : '';
 
-  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);
+/**
+ * User agent string says we are dealing with Firefox as browser.
+ * @type {boolean}
+ */
+ol.has.FIREFOX = ua.indexOf('firefox') !== -1;
 
-  var render = options.render ? options.render : ol.control.Attribution.render;
+/**
+ * User agent string says we are dealing with Safari as browser.
+ * @type {boolean}
+ */
+ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1;
 
-  ol.control.Control.call(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
+/**
+ * User agent string says we are dealing with a WebKit engine.
+ * @type {boolean}
+ */
+ol.has.WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1;
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
+/**
+ * User agent string says we are dealing with a Mac as platform.
+ * @type {boolean}
+ */
+ol.has.MAC = ua.indexOf('macintosh') !== -1;
 
-  /**
-   * @private
-   * @type {Object.<string, Element>}
-   */
-  this.attributionElements_ = {};
 
-  /**
-   * @private
-   * @type {Object.<string, boolean>}
-   */
-  this.attributionElementRenderedVisible_ = {};
+/**
+ * The ratio between physical pixels and device-independent pixels
+ * (dips) on the device (`window.devicePixelRatio`).
+ * @const
+ * @type {number}
+ * @api
+ */
+ol.has.DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
 
-  /**
-   * @private
-   * @type {Object.<string, Element>}
-   */
-  this.logoElements_ = {};
 
-};
-ol.inherits(ol.control.Attribution, ol.control.Control);
+/**
+ * True if the browser's Canvas implementation implements {get,set}LineDash.
+ * @type {boolean}
+ */
+ol.has.CANVAS_LINE_DASH = false;
 
 
 /**
- * @param {?olx.FrameState} frameState Frame state.
- * @return {Array.<Object.<string, ol.Attribution>>} Attributions.
+ * 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
  */
-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;
+ol.has.CANVAS = ol.ENABLE_CANVAS && (
+  /**
+   * @return {boolean} Canvas supported.
+   */
+  function() {
+    if (!('HTMLCanvasElement' in window)) {
+      return false;
     }
-    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);
+    try {
+      var context = document.createElement('CANVAS').getContext('2d');
+      if (!context) {
+        return false;
       } 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;
+        if (context.setLineDash !== undefined) {
+          ol.has.CANVAS_LINE_DASH = true;
         }
-      } else {
-        hiddenAttributions[sourceAttributionKey] = sourceAttribution;
+        return true;
       }
+    } catch (e) {
+      return false;
     }
-  }
-  return [attributions, hiddenAttributions];
-};
+  })();
 
 
 /**
- * Update the attribution element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.Attribution}
+ * Indicates if DeviceOrientation is supported in the user's browser.
+ * @const
+ * @type {boolean}
  * @api
  */
-ol.control.Attribution.render = function(mapEvent) {
-  this.updateElement_(mapEvent.frameState);
-};
+ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in window;
 
 
 /**
- * @private
- * @param {?olx.FrameState} frameState Frame state.
+ * Is HTML5 geolocation supported in the current browser?
+ * @const
+ * @type {boolean}
+ * @api
  */
-ol.control.Attribution.prototype.updateElement_ = function(frameState) {
-
-  if (!frameState) {
-    if (this.renderedVisible_) {
-      this.element.style.display = 'none';
-      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]) {
-        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;
-  }
-
-  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);
-
-};
+ol.has.GEOLOCATION = 'geolocation' in navigator;
 
 
 /**
- * @param {?olx.FrameState} frameState Frame state.
- * @private
+ * True if browser supports touch events.
+ * @const
+ * @type {boolean}
+ * @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)) {
-      ol.dom.removeNode(logoElements[logo]);
-      delete logoElements[logo];
-    }
-  }
-
-  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';
-
-};
+ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in window;
 
 
 /**
- * @param {Event} event The event to handle
- * @private
+ * True if browser supports pointer events.
+ * @const
+ * @type {boolean}
  */
-ol.control.Attribution.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleToggle_();
-};
+ol.has.POINTER = 'PointerEvent' in window;
 
 
 /**
- * @private
+ * True if browser supports ms pointer events (IE 10).
+ * @const
+ * @type {boolean}
  */
-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_;
-};
+ol.has.MSPOINTER = !!(navigator.msPointerEnabled);
 
 
 /**
- * Return `true` if the attribution is collapsible, `false` otherwise.
- * @return {boolean} True if the widget is collapsible.
+ * 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
  */
-ol.control.Attribution.prototype.getCollapsible = function() {
-  return this.collapsible_;
-};
+ol.has.WEBGL;
 
 
-/**
- * Set whether the attribution should be collapsible.
- * @param {boolean} collapsible True if the widget is collapsible.
- * @api
- */
-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_();
+(function() {
+  if (ol.ENABLE_WEBGL) {
+    var hasWebGL = false;
+    var textureSize;
+    var /** @type {Array.<string>} */ extensions = [];
+
+    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');
 
-/**
- * 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
- */
-ol.control.Attribution.prototype.setCollapsed = function(collapsed) {
-  if (!this.collapsible_ || this.collapsed_ === collapsed) {
-    return;
-  }
-  this.handleToggle_();
-};
+goog.require('ol.events.EventType');
 
 
 /**
- * Return `true` when the attribution is currently collapsed or `false`
- * otherwise.
- * @return {boolean} True if the widget is collapsed.
- * @api
+ * Constants for event names.
+ * @enum {string}
  */
-ol.control.Attribution.prototype.getCollapsed = function() {
-  return this.collapsed_;
+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',
+
+  /**
+   * 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,
+
+  /**
+   * 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',
+
+  POINTERDOWN: 'pointerdown',
+  POINTERUP: 'pointerup',
+  POINTEROVER: 'pointerover',
+  POINTEROUT: 'pointerout',
+  POINTERENTER: 'pointerenter',
+  POINTERLEAVE: 'pointerleave',
+  POINTERCANCEL: 'pointercancel'
 };
 
-goog.provide('ol.easing');
+goog.provide('ol.MapBrowserPointerEvent');
+
+goog.require('ol');
+goog.require('ol.MapBrowserEvent');
 
 
 /**
- * Start slow and speed up.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
+ * @constructor
+ * @extends {ol.MapBrowserEvent}
+ * @param {string} type Event type.
+ * @param {ol.PluggableMap} map Map.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @param {boolean=} opt_dragging Is the map currently being dragged?
+ * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-ol.easing.easeIn = function(t) {
-  return Math.pow(t, 3);
+ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging,
+    opt_frameState) {
+
+  ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging,
+      opt_frameState);
+
+  /**
+   * @const
+   * @type {ol.pointer.PointerEvent}
+   */
+  this.pointerEvent = pointerEvent;
+
 };
+ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);
+
+goog.provide('ol.pointer.EventType');
 
 
 /**
- * Start fast and slow down.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
+ * Constants for event names.
+ * @enum {string}
  */
-ol.easing.easeOut = function(t) {
-  return 1 - ol.easing.easeIn(1 - t);
+ol.pointer.EventType = {
+  POINTERMOVE: 'pointermove',
+  POINTERDOWN: 'pointerdown',
+  POINTERUP: 'pointerup',
+  POINTEROVER: 'pointerover',
+  POINTEROUT: 'pointerout',
+  POINTERENTER: 'pointerenter',
+  POINTERLEAVE: 'pointerleave',
+  POINTERCANCEL: 'pointercancel'
 };
 
+goog.provide('ol.pointer.EventSource');
+
 
 /**
- * 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 {ol.pointer.PointerEventHandler} dispatcher Event handler.
+ * @param {!Object.<string, function(Event)>} mapping Event
+ *     mapping.
+ * @constructor
  */
-ol.easing.inAndOut = function(t) {
-  return 3 * t * t - 2 * t * t * t;
+ol.pointer.EventSource = function(dispatcher, mapping) {
+  /**
+   * @type {ol.pointer.PointerEventHandler}
+   */
+  this.dispatcher = dispatcher;
+
+  /**
+   * @private
+   * @const
+   * @type {!Object.<string, function(Event)>}
+   */
+  this.mapping_ = mapping;
 };
 
 
 /**
- * Maintain a constant speed over time.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
+ * List of events supported by this source.
+ * @return {Array.<string>} Event names
  */
-ol.easing.linear = function(t) {
-  return t;
+ol.pointer.EventSource.prototype.getEvents = function() {
+  return Object.keys(this.mapping_);
 };
 
 
 /**
- * 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
+ * Returns the handler that should handle a given event type.
+ * @param {string} eventType The event type.
+ * @return {function(Event)} Handler
  */
-ol.easing.upAndDown = function(t) {
-  if (t < 0.5) {
-    return ol.easing.inAndOut(2 * t);
-  } else {
-    return 1 - ol.easing.inAndOut(2 * (t - 0.5));
-  }
+ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
+  return this.mapping_[eventType];
 };
 
-goog.provide('ol.control.Rotate');
+// 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.events');
-goog.require('ol.events.EventType');
 goog.require('ol');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
+goog.require('ol.pointer.EventSource');
 
 
 /**
- * @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.
- *
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
  * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.RotateOptions=} opt_options Rotate options.
- * @api
+ * @extends {ol.pointer.EventSource}
  */
-ol.control.Rotate = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var className = options.className !== undefined ? options.className : 'ol-rotate';
+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);
 
-  var label = options.label !== undefined ? options.label : '\u21E7';
+  /**
+   * @const
+   * @type {!Object.<string, Event|Object>}
+   */
+  this.pointerMap = dispatcher.pointerMap;
 
   /**
-   * @type {Element}
-   * @private
+   * @const
+   * @type {Array.<ol.Pixel>}
    */
-  this.label_ = null;
+  this.lastTouches = [];
+};
+ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
 
-  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');
-  }
 
-  var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation';
+/**
+ * @const
+ * @type {number}
+ */
+ol.pointer.MouseSource.POINTER_ID = 1;
 
-  var button = document.createElement('button');
-  button.className = className + '-reset';
-  button.setAttribute('type', 'button');
-  button.title = tipLabel;
-  button.appendChild(this.label_);
 
-  ol.events.listen(button, ol.events.EventType.CLICK,
-      ol.control.Rotate.prototype.handleClick_, this);
+/**
+ * @const
+ * @type {string}
+ */
+ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
 
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = document.createElement('div');
-  element.className = cssClasses;
-  element.appendChild(button);
 
-  var render = options.render ? options.render : ol.control.Rotate.render;
+/**
+ * Radius around touchend that swallows mouse events.
+ *
+ * @const
+ * @type {number}
+ */
+ol.pointer.MouseSource.DEDUP_DIST = 25;
 
-  this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
 
-  ol.control.Control.call(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
+/**
+ * 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 {Event} inEvent The in event.
+ * @return {boolean} True, if the event was generated by a touch.
+ */
+ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ = function(inEvent) {
+  var lts = this.lastTouches;
+  var x = inEvent.clientX, y = inEvent.clientY;
+  for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
+    // simulated mouse events will be swallowed near a primary touchend
+    var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]);
+    if (dx <= ol.pointer.MouseSource.DEDUP_DIST &&
+        dy <= ol.pointer.MouseSource.DEDUP_DIST) {
+      return true;
+    }
+  }
+  return false;
+};
 
-  /**
-   * @type {number}
-   * @private
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
+/**
+ * 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.
+ */
+ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
+  var e = dispatcher.cloneEvent(inEvent, inEvent);
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.rotation_ = undefined;
+  // forward mouse preventDefault
+  var pd = e.preventDefault;
+  e.preventDefault = function() {
+    inEvent.preventDefault();
+    pd();
+  };
 
-  if (this.autoHide_) {
-    this.element.classList.add(ol.css.CLASS_HIDDEN);
-  }
+  e.pointerId = ol.pointer.MouseSource.POINTER_ID;
+  e.isPrimary = true;
+  e.pointerType = ol.pointer.MouseSource.POINTER_TYPE;
 
+  return e;
 };
-ol.inherits(ol.control.Rotate, ol.control.Control);
 
 
 /**
- * @param {Event} event The event to handle
- * @private
+ * Handler for `mousedown`.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.control.Rotate.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  if (this.callResetNorth_ !== undefined) {
-    this.callResetNorth_();
-  } else {
-    this.resetNorth_();
+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);
   }
 };
 
 
 /**
- * @private
+ * Handler for `mousemove`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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 (view.getRotation() !== undefined) {
-    if (this.duration_ > 0) {
-      view.animate({
-        rotation: 0,
-        duration: this.duration_,
-        easing: ol.easing.easeOut
-      });
-    } else {
-      view.setRotation(0);
-    }
+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);
   }
 };
 
 
 /**
- * Update the rotate control element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.Rotate}
- * @api
+ * Handler for `mouseup`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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_) {
-      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);
-      }
+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();
     }
-    this.label_.style.msTransform = transform;
-    this.label_.style.webkitTransform = transform;
-    this.label_.style.transform = transform;
   }
-  this.rotation_ = rotation;
 };
 
-goog.provide('ol.control.Zoom');
 
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
+/**
+ * Handler for `mouseover`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+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);
+  }
+};
 
 
 /**
- * @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`.
+ * Handler for `mouseout`.
  *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomOptions=} opt_options Zoom options.
- * @api
+ * @param {Event} inEvent The in event.
  */
-ol.control.Zoom = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+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);
+  }
+};
 
-  var className = options.className !== undefined ? options.className : 'ol-zoom';
 
-  var delta = options.delta !== undefined ? options.delta : 1;
+/**
+ * Dispatches a `pointercancel` event.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
+  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
+  this.dispatcher.cancel(e, inEvent);
+  this.cleanupMouse();
+};
 
-  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';
+/**
+ * Remove the mouse from the list of active pointers.
+ */
+ol.pointer.MouseSource.prototype.cleanupMouse = function() {
+  delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
+};
 
-  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
-  );
+// Based on https://github.com/Polymer/PointerEvents
 
-  ol.events.listen(inElement, ol.events.EventType.CLICK,
-      ol.control.Zoom.prototype.handleClick_.bind(this, delta));
+// 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.
 
-  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
-  );
+goog.provide('ol.pointer.MsSource');
 
-  ol.events.listen(outElement, ol.events.EventType.CLICK,
-      ol.control.Zoom.prototype.handleClick_.bind(this, -delta));
+goog.require('ol');
+goog.require('ol.pointer.EventSource');
 
-  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
-  });
+/**
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
+ * @constructor
+ * @extends {ol.pointer.EventSource}
+ */
+ol.pointer.MsSource = function(dispatcher) {
+  var mapping = {
+    'MSPointerDown': this.msPointerDown,
+    'MSPointerMove': this.msPointerMove,
+    'MSPointerUp': this.msPointerUp,
+    'MSPointerOut': this.msPointerOut,
+    'MSPointerOver': this.msPointerOver,
+    'MSPointerCancel': this.msPointerCancel,
+    'MSGotPointerCapture': this.msGotPointerCapture,
+    'MSLostPointerCapture': this.msLostPointerCapture
+  };
+  ol.pointer.EventSource.call(this, dispatcher, mapping);
 
   /**
-   * @type {number}
-   * @private
+   * @const
+   * @type {!Object.<string, Event|Object>}
    */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
+  this.pointerMap = dispatcher.pointerMap;
 
+  /**
+   * @const
+   * @type {Array.<string>}
+   */
+  this.POINTER_TYPES = [
+    '',
+    'unavailable',
+    'touch',
+    'pen',
+    'mouse'
+  ];
 };
-ol.inherits(ol.control.Zoom, ol.control.Control);
+ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource);
 
 
 /**
- * @param {number} delta Zoom delta.
- * @param {Event} event The event to handle
+ * Creates a copy of the original event that will be used
+ * for the fake pointer event.
+ *
  * @private
+ * @param {Event} inEvent The in event.
+ * @return {Object} The copied event.
  */
-ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
-  event.preventDefault();
-  this.zoomByDelta_(delta);
+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];
+  }
+
+  return e;
 };
 
 
 /**
- * @param {number} delta Zoom delta.
- * @private
+ * Remove this pointer from the list of active pointers.
+ * @param {number} pointerId Pointer identifier.
  */
-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);
-    }
-  }
+ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
+  delete this.pointerMap[pointerId.toString()];
 };
 
-goog.provide('ol.control');
-
-goog.require('ol.Collection');
-goog.require('ol.control.Attribution');
-goog.require('ol.control.Rotate');
-goog.require('ol.control.Zoom');
-
 
 /**
- * Set of controls included in maps by default. Unless configured otherwise,
- * this returns a collection containing an instance of each of the following
- * controls:
- * * {@link ol.control.Zoom}
- * * {@link ol.control.Rotate}
- * * {@link ol.control.Attribution}
+ * Handler for `msPointerDown`.
  *
- * @param {olx.control.DefaultsOptions=} opt_options Defaults options.
- * @return {ol.Collection.<ol.control.Control>} Controls.
- * @api
+ * @param {Event} inEvent The in event.
  */
-ol.control.defaults = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
+  this.pointerMap[inEvent.pointerId.toString()] = inEvent;
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.down(e, inEvent);
+};
 
-  var controls = new ol.Collection();
 
-  var zoomControl = options.zoom !== undefined ? options.zoom : true;
-  if (zoomControl) {
-    controls.push(new ol.control.Zoom(options.zoomOptions));
-  }
+/**
+ * Handler for `msPointerMove`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.move(e, inEvent);
+};
 
-  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));
-  }
+/**
+ * Handler for `msPointerUp`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.up(e, inEvent);
+  this.cleanup(inEvent.pointerId);
+};
 
-  return controls;
 
+/**
+ * Handler for `msPointerOut`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.leaveOut(e, inEvent);
 };
 
-goog.provide('ol.control.FullScreen');
 
-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');
+/**
+ * Handler for `msPointerOver`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.enterOver(e, inEvent);
+};
 
 
 /**
- * @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.
- *
+ * Handler for `msPointerCancel`.
  *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.FullScreenOptions=} opt_options Options.
- * @api
+ * @param {Event} inEvent The in event.
  */
-ol.control.FullScreen = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
+  var e = this.prepareEvent_(inEvent);
+  this.dispatcher.cancel(e, inEvent);
+  this.cleanup(inEvent.pointerId);
+};
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.cssClassName_ = options.className !== undefined ? options.className :
-      'ol-full-screen';
 
-  var label = options.label !== undefined ? options.label : '\u2922';
+/**
+ * Handler for `msLostPointerCapture`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
+  var e = this.dispatcher.makeEvent('lostpointercapture',
+      inEvent, inEvent);
+  this.dispatcher.dispatchEvent(e);
+};
 
-  /**
-   * @private
-   * @type {Node}
-   */
-  this.labelNode_ = typeof label === 'string' ?
-      document.createTextNode(label) : label;
 
-  var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7';
+/**
+ * Handler for `msGotPointerCapture`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
+  var e = this.dispatcher.makeEvent('gotpointercapture',
+      inEvent, inEvent);
+  this.dispatcher.dispatchEvent(e);
+};
 
-  /**
-   * @private
-   * @type {Node}
-   */
-  this.labelActiveNode_ = typeof labelActive === 'string' ?
-      document.createTextNode(labelActive) : labelActive;
+// Based on https://github.com/Polymer/PointerEvents
 
-  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_);
+// 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.
 
-  ol.events.listen(button, ol.events.EventType.CLICK,
-      this.handleClick_, this);
+goog.provide('ol.pointer.NativeSource');
 
-  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);
+goog.require('ol');
+goog.require('ol.pointer.EventSource');
 
-  ol.control.Control.call(this, {
-    element: element,
-    target: options.target
-  });
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.keys_ = options.keys !== undefined ? options.keys : false;
+/**
+ * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
+ * @constructor
+ * @extends {ol.pointer.EventSource}
+ */
+ol.pointer.NativeSource = function(dispatcher) {
+  var mapping = {
+    'pointerdown': this.pointerDown,
+    'pointermove': this.pointerMove,
+    'pointerup': this.pointerUp,
+    'pointerout': this.pointerOut,
+    'pointerover': this.pointerOver,
+    'pointercancel': this.pointerCancel,
+    'gotpointercapture': this.gotPointerCapture,
+    'lostpointercapture': this.lostPointerCapture
+  };
+  ol.pointer.EventSource.call(this, dispatcher, mapping);
+};
+ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
 
-  /**
-   * @private
-   * @type {Element|string|undefined}
-   */
-  this.source_ = options.source;
 
+/**
+ * Handler for `pointerdown`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
-ol.inherits(ol.control.FullScreen, ol.control.Control);
 
 
 /**
- * @param {Event} event The event to handle
- * @private
+ * Handler for `pointermove`.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.control.FullScreen.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleFullScreen_();
+ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @private
+ * Handler for `pointerup`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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);
-    }
-  }
+ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @private
+ * Handler for `pointerout`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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();
-  }
+ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * Handler for `pointerover`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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)
-    );
-  }
+ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
+
 /**
- * @return {boolean} Fullscreen is supported by the current platform.
+ * Handler for `pointercancel`.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.control.FullScreen.isFullScreenSupported = function() {
-  var body = document.body;
-  return !!(
-    body.webkitRequestFullscreen ||
-    (body.mozRequestFullScreen && document.mozFullScreenEnabled) ||
-    (body.msRequestFullscreen && document.msFullscreenEnabled) ||
-    (body.requestFullscreen && document.fullscreenEnabled)
-  );
+ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
+
 /**
- * @return {boolean} Element is currently in fullscreen.
+ * Handler for `lostpointercapture`.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.control.FullScreen.isFullScreen = function() {
-  return !!(
-    document.webkitIsFullScreen || document.mozFullScreen ||
-    document.msFullscreenElement || document.fullscreenElement
-  );
+ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
+
 /**
- * Request to fullscreen an element.
- * @param {Node} element Element to request fullscreen
- */
-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();
-  }
-};
-
-/**
- * Request to fullscreen an element with keyboard input.
- * @param {Node} element Element to request fullscreen
+ * Handler for `gotpointercapture`.
+ *
+ * @param {Event} inEvent The in event.
  */
-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);
-  }
+ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
+  this.dispatcher.fireNativeEvent(inEvent);
 };
 
-/**
- * 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();
-  }
-};
+// Based on https://github.com/Polymer/PointerEvents
 
-/**
- * @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;
-  };
-})();
+// 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.
 
-// FIXME should listen on appropriate pane, once it is defined
+goog.provide('ol.pointer.PointerEvent');
 
-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');
+goog.require('ol.events.Event');
 
 
 /**
- * @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`.
+ * A class for pointer events.
+ *
+ * This class is used as an abstraction for mouse events,
+ * touch events and even native pointer events.
  *
  * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.MousePositionOptions=} opt_options Mouse position
- *     options.
- * @api
+ * @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.
  */
-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.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) {
+  ol.events.Event.call(this, type);
 
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.control.MousePosition.Property_.PROJECTION),
-      this.handleProjectionChanged_, this);
+  /**
+   * @const
+   * @type {Event}
+   */
+  this.originalEvent = originalEvent;
 
-  if (options.coordinateFormat) {
-    this.setCoordinateFormat(options.coordinateFormat);
-  }
-  if (options.projection) {
-    this.setProjection(options.projection);
-  }
+  var eventDict = opt_eventDict ? opt_eventDict : {};
 
   /**
-   * @private
-   * @type {string}
+   * @type {number}
    */
-  this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : '';
+  this.buttons = this.getButtons_(eventDict);
 
   /**
-   * @private
-   * @type {string}
+   * @type {number}
    */
-  this.renderedHTML_ = element.innerHTML;
+  this.pressure = this.getPressure_(eventDict, this.buttons);
+
+  // MouseEvent related properties
 
   /**
-   * @private
-   * @type {ol.proj.Projection}
+   * @type {boolean}
    */
-  this.mapProjection_ = null;
+  this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;
 
   /**
-   * @private
-   * @type {?ol.TransformFunction}
+   * @type {boolean}
    */
-  this.transform_ = null;
+  this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;
 
   /**
-   * @private
-   * @type {ol.Pixel}
+   * @type {Object}
    */
-  this.lastMouseMovePixel_ = null;
-
-};
-ol.inherits(ol.control.MousePosition, ol.control.Control);
-
-
-/**
- * Update the mouseposition element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.MousePosition}
- * @api
- */
-ol.control.MousePosition.render = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (!frameState) {
-    this.mapProjection_ = null;
-  } else {
-    if (this.mapProjection_ != frameState.viewState.projection) {
-      this.mapProjection_ = frameState.viewState.projection;
-      this.transform_ = null;
-    }
-  }
-  this.updateHTML_(this.lastMouseMovePixel_);
-};
-
-
-/**
- * @private
- */
-ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
-  this.transform_ = null;
-};
-
-
-/**
- * Return the coordinate format type used to render the current position or
- * undefined.
- * @return {ol.CoordinateFormatType|undefined} The format to render the current
- *     position in.
- * @observable
- * @api
- */
-ol.control.MousePosition.prototype.getCoordinateFormat = function() {
-  return /** @type {ol.CoordinateFormatType|undefined} */ (
-      this.get(ol.control.MousePosition.Property_.COORDINATE_FORMAT));
-};
-
-
-/**
- * Return the projection that is used to report the mouse position.
- * @return {ol.proj.Projection|undefined} The projection to report mouse
- *     position in.
- * @observable
- * @api
- */
-ol.control.MousePosition.prototype.getProjection = function() {
-  return /** @type {ol.proj.Projection|undefined} */ (
-      this.get(ol.control.MousePosition.Property_.PROJECTION));
-};
-
-
-/**
- * @param {Event} event Browser event.
- * @protected
- */
-ol.control.MousePosition.prototype.handleMouseMove = function(event) {
-  var map = this.getMap();
-  this.lastMouseMovePixel_ = map.getEventPixel(event);
-  this.updateHTML_(this.lastMouseMovePixel_);
-};
-
-
-/**
- * @param {Event} event Browser event.
- * @protected
- */
-ol.control.MousePosition.prototype.handleMouseOut = function(event) {
-  this.updateHTML_(null);
-  this.lastMouseMovePixel_ = null;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-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)
-    );
-  }
-};
-
-
-/**
- * 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
- */
-ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
-  this.set(ol.control.MousePosition.Property_.COORDINATE_FORMAT, format);
-};
-
-
-/**
- * Set the projection that is used to report the mouse position.
- * @param {ol.ProjectionLike} projection The projection to report mouse
- *     position in.
- * @observable
- * @api
- */
-ol.control.MousePosition.prototype.setProjection = function(projection) {
-  this.set(ol.control.MousePosition.Property_.PROJECTION, ol.proj.get(projection));
-};
-
-
-/**
- * @param {?ol.Pixel} pixel Pixel.
- * @private
- */
-ol.control.MousePosition.prototype.updateHTML_ = function(pixel) {
-  var html = this.undefinedHTML_;
-  if (pixel && this.mapProjection_) {
-    if (!this.transform_) {
-      var projection = this.getProjection();
-      if (projection) {
-        this.transform_ = ol.proj.getTransformFromProjections(
-            this.mapProjection_, projection);
-      } else {
-        this.transform_ = ol.proj.identityTransform;
-      }
-    }
-    var map = this.getMap();
-    var coordinate = map.getCoordinateFromPixel(pixel);
-    if (coordinate) {
-      this.transform_(coordinate, coordinate);
-      var coordinateFormat = this.getCoordinateFormat();
-      if (coordinateFormat) {
-        html = coordinateFormat(coordinate);
-      } else {
-        html = coordinate.toString();
-      }
-    }
-  }
-  if (!this.renderedHTML_ || html != this.renderedHTML_) {
-    this.element.innerHTML = html;
-    this.renderedHTML_ = html;
-  }
-};
-
-
-/**
- * @enum {string}
- * @private
- */
-ol.control.MousePosition.Property_ = {
-  PROJECTION: 'projection',
-  COORDINATE_FORMAT: 'coordinateFormat'
-};
-
-goog.provide('ol.MapEvent');
-
-goog.require('ol');
-goog.require('ol.events.Event');
-
-
-/**
- * @classdesc
- * Events emitted as map events are instances of this type.
- * See {@link ol.Map} for which events trigger a map event.
- *
- * @constructor
- * @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);
+  this.view = 'view' in eventDict ? eventDict['view'] : null;
 
   /**
-   * The map where the event occurred.
-   * @type {ol.Map}
-   * @api
+   * @type {number}
    */
-  this.map = map;
+  this.detail = 'detail' in eventDict ? eventDict['detail'] : null;
 
   /**
-   * The frame state at the time of the event.
-   * @type {?olx.FrameState}
-   * @api
+   * @type {number}
    */
-  this.frameState = opt_frameState !== undefined ? opt_frameState : null;
-
-};
-ol.inherits(ol.MapEvent, ol.events.Event);
-
-goog.provide('ol.MapBrowserEvent');
-
-goog.require('ol');
-goog.require('ol.MapEvent');
-
-
-/**
- * @classdesc
- * Events emitted as map browser events are instances of this type.
- * See {@link ol.Map} for which events trigger a map browser event.
- *
- * @constructor
- * @extends {ol.MapEvent}
- * @implements {oli.MapBrowserEvent}
- * @param {string} type Event type.
- * @param {ol.Map} map Map.
- * @param {Event} browserEvent Browser event.
- * @param {boolean=} opt_dragging Is the map currently being dragged?
- * @param {?olx.FrameState=} opt_frameState Frame state.
- */
-ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging,
-    opt_frameState) {
-
-  ol.MapEvent.call(this, type, map, opt_frameState);
+  this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;
 
   /**
-   * The original browser event.
-   * @const
-   * @type {Event}
-   * @api
+   * @type {number}
    */
-  this.originalEvent = browserEvent;
+  this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;
 
   /**
-   * The map pixel relative to the viewport corresponding to the original browser event.
-   * @type {ol.Pixel}
-   * @api
+   * @type {number}
    */
-  this.pixel = map.getEventPixel(browserEvent);
+  this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;
 
   /**
-   * The coordinate in view projection corresponding to the original browser event.
-   * @type {ol.Coordinate}
-   * @api
+   * @type {number}
    */
-  this.coordinate = map.getCoordinateFromPixel(this.pixel);
+  this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;
 
   /**
-   * Indicates if the map is currently being dragged. Only set for
-   * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`.
-   *
    * @type {boolean}
-   * @api
-   */
-  this.dragging = opt_dragging !== undefined ? opt_dragging : false;
-
-};
-ol.inherits(ol.MapBrowserEvent, ol.MapEvent);
-
-
-/**
- * Prevents the default browser action.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
- * @override
- * @api
- */
-ol.MapBrowserEvent.prototype.preventDefault = function() {
-  ol.MapEvent.prototype.preventDefault.call(this);
-  this.originalEvent.preventDefault();
-};
-
-
-/**
- * Prevents further propagation of the current event.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
- * @override
- * @api
- */
-ol.MapBrowserEvent.prototype.stopPropagation = function() {
-  ol.MapEvent.prototype.stopPropagation.call(this);
-  this.originalEvent.stopPropagation();
-};
-
-goog.provide('ol.webgl');
-
-goog.require('ol');
-
-
-if (ol.ENABLE_WEBGL) {
-
-  /** Constants taken from goog.webgl
    */
-
+  this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;
 
   /**
-   * @const
-   * @type {number}
+   * @type {boolean}
    */
-  ol.webgl.ONE = 1;
-
+  this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;
 
   /**
-   * @const
-   * @type {number}
+   * @type {boolean}
    */
-  ol.webgl.SRC_ALPHA = 0x0302;
-
+  this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;
 
   /**
-   * @const
-   * @type {number}
+   * @type {boolean}
    */
-  ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0;
-
+  this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;
 
   /**
-   * @const
    * @type {number}
    */
-  ol.webgl.COLOR_BUFFER_BIT = 0x00004000;
-
+  this.button = 'button' in eventDict ? eventDict['button'] : 0;
 
   /**
-   * @const
-   * @type {number}
+   * @type {Node}
    */
-  ol.webgl.TRIANGLES = 0x0004;
+  this.relatedTarget = 'relatedTarget' in eventDict ?
+    eventDict['relatedTarget'] : null;
 
+  // PointerEvent related properties
 
   /**
    * @const
    * @type {number}
    */
-  ol.webgl.TRIANGLE_STRIP = 0x0005;
-
+  this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;
 
   /**
-   * @const
    * @type {number}
    */
-  ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;
-
+  this.width = 'width' in eventDict ? eventDict['width'] : 0;
 
   /**
-   * @const
    * @type {number}
    */
-  ol.webgl.ARRAY_BUFFER = 0x8892;
-
+  this.height = 'height' in eventDict ? eventDict['height'] : 0;
 
   /**
-   * @const
    * @type {number}
    */
-  ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;
-
+  this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;
 
   /**
-   * @const
    * @type {number}
    */
-  ol.webgl.STREAM_DRAW = 0x88E0;
-
+  this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;
 
   /**
-   * @const
-   * @type {number}
+   * @type {string}
    */
-  ol.webgl.STATIC_DRAW = 0x88E4;
-
+  this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';
 
   /**
-   * @const
    * @type {number}
    */
-  ol.webgl.DYNAMIC_DRAW = 0x88E8;
-
+  this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;
 
   /**
-   * @const
-   * @type {number}
+   * @type {boolean}
    */
-  ol.webgl.CULL_FACE = 0x0B44;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.BLEND = 0x0BE2;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.STENCIL_TEST = 0x0B90;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.DEPTH_TEST = 0x0B71;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.SCISSOR_TEST = 0x0C11;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.UNSIGNED_BYTE = 0x1401;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.UNSIGNED_SHORT = 0x1403;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.UNSIGNED_INT = 0x1405;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.FLOAT = 0x1406;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.RGBA = 0x1908;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.FRAGMENT_SHADER = 0x8B30;
-
-
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.VERTEX_SHADER = 0x8B31;
+  this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;
 
+  // keep the semantics of preventDefault
+  if (originalEvent.preventDefault) {
+    this.preventDefault = function() {
+      originalEvent.preventDefault();
+    };
+  }
+};
+ol.inherits(ol.pointer.PointerEvent, ol.events.Event);
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.LINK_STATUS = 0x8B82;
 
+/**
+ * @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 buttons;
+};
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.LINEAR = 0x2601;
 
+/**
+ * @private
+ * @param {Object.<string, ?>} eventDict The event dictionary.
+ * @param {number} buttons Button indicator.
+ * @return {number} The pressure.
+ */
+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;
+};
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.TEXTURE_MAG_FILTER = 0x2800;
 
+/**
+ * Is the `buttons` property supported?
+ * @type {boolean}
+ */
+ol.pointer.PointerEvent.HAS_BUTTONS = false;
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.TEXTURE_MIN_FILTER = 0x2801;
 
+/**
+ * Checks if the `buttons` property is supported.
+ */
+(function() {
+  try {
+    var ev = new MouseEvent('click', {buttons: 1});
+    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
+  } catch (e) {
+    // pass
+  }
+})();
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.TEXTURE_WRAP_S = 0x2802;
+// 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.
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.TEXTURE_WRAP_T = 0x2803;
+goog.provide('ol.pointer.TouchSource');
 
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.pointer.EventSource');
+goog.require('ol.pointer.MouseSource');
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.webgl.TEXTURE_2D = 0x0DE1;
 
+/**
+ * @constructor
+ * @param {ol.pointer.PointerEventHandler} dispatcher The event handler.
+ * @param {ol.pointer.MouseSource} mouseSource Mouse source.
+ * @extends {ol.pointer.EventSource}
+ */
+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 {number}
+   * @type {!Object.<string, Event|Object>}
    */
-  ol.webgl.TEXTURE0 = 0x84C0;
-
+  this.pointerMap = dispatcher.pointerMap;
 
   /**
    * @const
-   * @type {number}
+   * @type {ol.pointer.MouseSource}
    */
-  ol.webgl.CLAMP_TO_EDGE = 0x812F;
-
+  this.mouseSource = mouseSource;
 
   /**
-   * @const
-   * @type {number}
+   * @private
+   * @type {number|undefined}
    */
-  ol.webgl.COMPILE_STATUS = 0x8B81;
-
+  this.firstTouchId_ = undefined;
 
   /**
-   * @const
+   * @private
    * @type {number}
    */
-  ol.webgl.FRAMEBUFFER = 0x8D40;
-
-
-  /** end of goog.webgl constants
-   */
-
+  this.clickCount_ = 0;
 
   /**
-   * @const
    * @private
-   * @type {Array.<string>}
-   */
-  ol.webgl.CONTEXT_IDS_ = [
-    'experimental-webgl',
-    'webgl',
-    'webkit-3d',
-    'moz-webgl'
-  ];
-
-
-  /**
-   * @param {HTMLCanvasElement} canvas Canvas.
-   * @param {Object=} opt_attributes Attributes.
-   * @return {WebGLRenderingContext} WebGL rendering context.
+   * @type {number|undefined}
    */
-  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
-      }
-    }
-    return null;
-  };
-
-}
-
-goog.provide('ol.has');
-
-goog.require('ol');
-goog.require('ol.webgl');
-
-var ua = typeof navigator !== 'undefined' ?
-    navigator.userAgent.toLowerCase() : '';
+  this.resetId_ = undefined;
+};
+ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);
 
-/**
- * User agent string says we are dealing with Firefox as browser.
- * @type {boolean}
- */
-ol.has.FIREFOX = ua.indexOf('firefox') !== -1;
 
 /**
- * User agent string says we are dealing with Safari as browser.
- * @type {boolean}
+ * Mouse event timeout: This should be long enough to
+ * ignore compat mouse events made by touch.
+ * @const
+ * @type {number}
  */
-ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1;
+ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
 
-/**
- * User agent string says we are dealing with a WebKit engine.
- * @type {boolean}
- */
-ol.has.WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1;
 
 /**
- * User agent string says we are dealing with a Mac as platform.
- * @type {boolean}
+ * @const
+ * @type {number}
  */
-ol.has.MAC = ua.indexOf('macintosh') !== -1;
+ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
 
 
 /**
- * The ratio between physical pixels and device-independent pixels
- * (dips) on the device (`window.devicePixelRatio`).
  * @const
- * @type {number}
- * @api
+ * @type {string}
  */
-ol.has.DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
+ol.pointer.TouchSource.POINTER_TYPE = 'touch';
 
 
 /**
- * True if the browser's Canvas implementation implements {get,set}LineDash.
- * @type {boolean}
+ * @private
+ * @param {Touch} inTouch The in touch.
+ * @return {boolean} True, if this is the primary touch.
  */
-ol.has.CANVAS_LINE_DASH = false;
+ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
+  return this.firstTouchId_ === inTouch.identifier;
+};
 
 
 /**
- * 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
+ * Set primary touch if there are no pointers, or the only pointer is the mouse.
+ * @param {Touch} inTouch The in touch.
+ * @private
  */
-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;
-      }
-    })();
+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_();
+  }
+};
 
 
 /**
- * Indicates if DeviceOrientation is supported in the user's browser.
- * @const
- * @type {boolean}
- * @api
+ * @private
+ * @param {Object} inPointer The in pointer object.
  */
-ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in window;
+ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
+  if (inPointer.isPrimary) {
+    this.firstTouchId_ = undefined;
+    this.resetClickCount_();
+  }
+};
 
 
 /**
- * Is HTML5 geolocation supported in the current browser?
- * @const
- * @type {boolean}
- * @api
+ * @private
  */
-ol.has.GEOLOCATION = 'geolocation' in navigator;
+ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
+  this.resetId_ = setTimeout(
+      this.resetClickCountHandler_.bind(this),
+      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
+};
 
 
 /**
- * True if browser supports touch events.
- * @const
- * @type {boolean}
- * @api
+ * @private
  */
-ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in window;
+ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
+  this.clickCount_ = 0;
+  this.resetId_ = undefined;
+};
 
 
 /**
- * True if browser supports pointer events.
- * @const
- * @type {boolean}
+ * @private
  */
-ol.has.POINTER = 'PointerEvent' in window;
+ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
+  if (this.resetId_ !== undefined) {
+    clearTimeout(this.resetId_);
+  }
+};
 
 
 /**
- * True if browser supports ms pointer events (IE 10).
- * @const
- * @type {boolean}
+ * @private
+ * @param {Event} browserEvent Browser event
+ * @param {Touch} inTouch Touch event
+ * @return {Object} A pointer object.
  */
-ol.has.MSPOINTER = !!(navigator.msPointerEnabled);
+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;
+};
 
 
 /**
- * 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
+ * @private
+ * @param {Event} inEvent Touch event
+ * @param {function(Event, Object)} inFunction In function.
  */
-ol.has.WEBGL;
-
+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);
+  }
+};
 
-(function() {
-  if (ol.ENABLE_WEBGL) {
-    var hasWebGL = false;
-    var textureSize;
-    var /** @type {Array.<string>} */ extensions = [];
 
-    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
-      }
+/**
+ * @private
+ * @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.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.has.WEBGL = hasWebGL;
-    ol.WEBGL_EXTENSIONS = extensions;
-    ol.WEBGL_MAX_TEXTURE_SIZE = textureSize;
   }
-})();
-
-goog.provide('ol.MapBrowserEventType');
-
-goog.require('ol.events.EventType');
+  return false;
+};
 
 
 /**
- * Constants for event names.
- * @enum {string}
+ * 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.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',
-
-  /**
-   * A click with no dragging. A double click will fire two of this.
-   * @event ol.MapBrowserEvent#click
-   * @api
-   */
-  CLICK: ol.events.EventType.CLICK,
+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]);
+    }
+  }
+};
 
-  /**
-   * A true double click, with no dragging.
-   * @event ol.MapBrowserEvent#dblclick
-   * @api
-   */
-  DBLCLICK: ol.events.EventType.DBLCLICK,
 
-  /**
-   * Triggered when a pointer is dragged.
-   * @event ol.MapBrowserEvent#pointerdrag
-   * @api
-   */
-  POINTERDRAG: 'pointerdrag',
+/**
+ * Handler for `touchstart`, triggers `pointerover`,
+ * `pointerenter` and `pointerdown` events.
+ *
+ * @param {Event} inEvent The in event.
+ */
+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_);
+};
 
-  /**
-   * 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',
 
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
+/**
+ * @private
+ * @param {Event} browserEvent The event.
+ * @param {Object} inPointer The in pointer object.
+ */
+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);
 };
 
-goog.provide('ol.MapBrowserPointerEvent');
 
-goog.require('ol');
-goog.require('ol.MapBrowserEvent');
+/**
+ * Handler for `touchmove`.
+ *
+ * @param {Event} inEvent The in event.
+ */
+ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
+  inEvent.preventDefault();
+  this.processTouches_(inEvent, this.moveOverOut_);
+};
 
 
 /**
- * @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.
+ * @private
+ * @param {Event} browserEvent The event.
+ * @param {Object} inPointer The in pointer.
  */
-ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging,
-    opt_frameState) {
-
-  ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging,
-      opt_frameState);
+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;
+};
 
-  /**
-   * @const
-   * @type {ol.pointer.PointerEvent}
-   */
-  this.pointerEvent = pointerEvent;
 
+/**
+ * Handler for `touchend`, triggers `pointerup`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {Event} inEvent The event.
+ */
+ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
+  this.dedupSynthMouse_(inEvent);
+  this.processTouches_(inEvent, this.upOut_);
 };
-ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);
-
-goog.provide('ol.pointer.EventType');
 
 
 /**
- * Constants for event names.
- * @enum {string}
+ * @private
+ * @param {Event} browserEvent An event.
+ * @param {Object} inPointer The inPointer object.
  */
-ol.pointer.EventType = {
-  POINTERMOVE: 'pointermove',
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
+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);
 };
 
-goog.provide('ol.pointer.EventSource');
-
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @param {!Object.<string, function(Event)>} mapping Event
- *     mapping.
- * @constructor
+ * Handler for `touchcancel`, triggers `pointercancel`,
+ * `pointerout` and `pointerleave` events.
+ *
+ * @param {Event} inEvent The in event.
  */
-ol.pointer.EventSource = function(dispatcher, mapping) {
-  /**
-   * @type {ol.pointer.PointerEventHandler}
-   */
-  this.dispatcher = dispatcher;
+ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
+  this.processTouches_(inEvent, this.cancelOut_);
+};
 
-  /**
-   * @private
-   * @const
-   * @type {!Object.<string, function(Event)>}
-   */
-  this.mapping_ = mapping;
+
+/**
+ * @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);
 };
 
 
 /**
- * List of events supported by this source.
- * @return {Array.<string>} Event names
+ * @private
+ * @param {Object} inPointer The inPointer object.
  */
-ol.pointer.EventSource.prototype.getEvents = function() {
-  return Object.keys(this.mapping_);
+ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
+  delete this.pointerMap[inPointer.pointerId];
+  this.removePrimaryPointer_(inPointer);
 };
 
 
 /**
- * Returns the handler that should handle a given event type.
- * @param {string} eventType The event type.
- * @return {function(Event)} Handler
+ * Prevent synth mouse events from creating pointer events.
+ *
+ * @private
+ * @param {Event} inEvent The in event.
  */
-ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
-  return this.mapping_[eventType];
+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);
+  }
 };
 
 // Based on https://github.com/Polymer/PointerEvents
@@ -10828,3845 +10546,3702 @@ ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
 // (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.provide('ol.pointer.PointerEventHandler');
 
 goog.require('ol');
-goog.require('ol.pointer.EventSource');
+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');
 
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
  * @constructor
- * @extends {ol.pointer.EventSource}
+ * @extends {ol.events.EventTarget}
+ * @param {Element|HTMLDocument} element Viewport element.
  */
-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);
+ol.pointer.PointerEventHandler = function(element) {
+  ol.events.EventTarget.call(this);
 
   /**
    * @const
-   * @type {!Object.<string, Event|Object>}
+   * @private
+   * @type {Element|HTMLDocument}
    */
-  this.pointerMap = dispatcher.pointerMap;
+  this.element_ = element;
 
   /**
    * @const
-   * @type {Array.<ol.Pixel>}
+   * @type {!Object.<string, Event|Object>}
    */
-  this.lastTouches = [];
-};
-ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
-
+  this.pointerMap = {};
 
-/**
- * @const
- * @type {number}
- */
-ol.pointer.MouseSource.POINTER_ID = 1;
+  /**
+   * @type {Object.<string, function(Event)>}
+   * @private
+   */
+  this.eventMap_ = {};
 
+  /**
+   * @type {Array.<ol.pointer.EventSource>}
+   * @private
+   */
+  this.eventSourceList_ = [];
 
-/**
- * @const
- * @type {string}
- */
-ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
+  this.registerSources();
+};
+ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget);
 
 
 /**
- * Radius around touchend that swallows mouse events.
- *
- * @const
- * @type {number}
+ * Set up the event sources (mouse, touch and native pointers)
+ * that generate pointer events.
  */
-ol.pointer.MouseSource.DEDUP_DIST = 25;
-
+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);
 
-/**
- * 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 {Event} inEvent The in event.
- * @return {boolean} True, if the event was generated by a touch.
- */
-ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ = function(inEvent) {
-  var lts = this.lastTouches;
-  var x = inEvent.clientX, y = inEvent.clientY;
-  for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
-    // simulated mouse events will be swallowed near a primary touchend
-    var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]);
-    if (dx <= ol.pointer.MouseSource.DEDUP_DIST &&
-        dy <= ol.pointer.MouseSource.DEDUP_DIST) {
-      return true;
+    if (ol.has.TOUCH) {
+      this.registerSource('touch',
+          new ol.pointer.TouchSource(this, mouseSource));
     }
   }
-  return false;
+
+  // register events on the viewport element
+  this.register_();
 };
 
 
 /**
- * Creates a copy of the original event that will be used
- * for the fake pointer event.
+ * Add a new event source that will generate pointer events.
  *
- * @param {Event} inEvent The in event.
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @return {Object} The copied event.
+ * @param {string} name A name for the event source
+ * @param {ol.pointer.EventSource} source The source event.
  */
-ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
-  var e = dispatcher.cloneEvent(inEvent, inEvent);
-
-  // forward mouse preventDefault
-  var pd = e.preventDefault;
-  e.preventDefault = function() {
-    inEvent.preventDefault();
-    pd();
-  };
+ol.pointer.PointerEventHandler.prototype.registerSource = function(name, source) {
+  var s = source;
+  var newEvents = s.getEvents();
 
-  e.pointerId = ol.pointer.MouseSource.POINTER_ID;
-  e.isPrimary = true;
-  e.pointerType = ol.pointer.MouseSource.POINTER_TYPE;
+  if (newEvents) {
+    newEvents.forEach(function(e) {
+      var handler = s.getHandlerForEvent(e);
 
-  return e;
+      if (handler) {
+        this.eventMap_[e] = handler.bind(s);
+      }
+    }, this);
+    this.eventSourceList_.push(s);
+  }
 };
 
 
 /**
- * Handler for `mousedown`.
- *
- * @param {Event} inEvent The in event.
+ * Set up the events for all registered event sources.
+ * @private
  */
-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.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());
   }
 };
 
 
 /**
- * Handler for `mousemove`.
- *
- * @param {Event} inEvent The in event.
+ * Remove all registered events.
+ * @private
  */
-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.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());
   }
 };
 
 
 /**
- * Handler for `mouseup`.
- *
- * @param {Event} inEvent The in event.
+ * Calls the right handler for a new event.
+ * @private
+ * @param {Event} inEvent Browser event.
  */
-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.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
+  var type = inEvent.type;
+  var handler = this.eventMap_[type];
+  if (handler) {
+    handler(inEvent);
   }
 };
 
 
 /**
- * Handler for `mouseover`.
- *
- * @param {Event} inEvent The in event.
+ * Setup listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
  */
-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.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
+  events.forEach(function(eventName) {
+    ol.events.listen(this.element_, eventName, this.eventHandler_, this);
+  }, this);
 };
 
 
 /**
- * Handler for `mouseout`.
- *
- * @param {Event} inEvent The in event.
+ * Unregister listeners for the given events.
+ * @private
+ * @param {Array.<string>} events List of events.
  */
-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.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
+  events.forEach(function(e) {
+    ol.events.unlisten(this.element_, e, this.eventHandler_, this);
+  }, this);
 };
 
 
 /**
- * Dispatches a `pointercancel` event.
+ * Returns a snapshot of inEvent, with writable properties.
  *
- * @param {Event} inEvent The in event.
+ * @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.pointer.MouseSource.prototype.cancel = function(inEvent) {
-  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanupMouse();
+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 eventCopy;
 };
 
 
+// EVENTS
+
+
 /**
- * Remove the mouse from the list of active pointers.
+ * Triggers a 'pointerdown' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MouseSource.prototype.cleanupMouse = function() {
-  delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
-};
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.MsSource');
-
-goog.require('ol');
-goog.require('ol.pointer.EventSource');
-
-
-/**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @constructor
- * @extends {ol.pointer.EventSource}
- */
-ol.pointer.MsSource = function(dispatcher) {
-  var mapping = {
-    'MSPointerDown': this.msPointerDown,
-    'MSPointerMove': this.msPointerMove,
-    'MSPointerUp': this.msPointerUp,
-    'MSPointerOut': this.msPointerOut,
-    'MSPointerOver': this.msPointerOver,
-    'MSPointerCancel': this.msPointerCancel,
-    'MSGotPointerCapture': this.msGotPointerCapture,
-    'MSLostPointerCapture': this.msLostPointerCapture
-  };
-  ol.pointer.EventSource.call(this, dispatcher, mapping);
-
-  /**
-   * @const
-   * @type {!Object.<string, Event|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
-
-  /**
-   * @const
-   * @type {Array.<string>}
-   */
-  this.POINTER_TYPES = [
-    '',
-    'unavailable',
-    'touch',
-    'pen',
-    'mouse'
-  ];
-};
-ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource);
-
-
-/**
- * Creates a copy of the original event that will be used
- * for the fake pointer event.
- *
- * @private
- * @param {Event} inEvent The in event.
- * @return {Object} The copied event.
- */
-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];
-  }
-
-  return e;
-};
-
-
-/**
- * Remove this pointer from the list of active pointers.
- * @param {number} pointerId Pointer identifier.
- */
-ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
-  delete this.pointerMap[pointerId.toString()];
-};
-
-
-/**
- * Handler for `msPointerDown`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
-  this.pointerMap[inEvent.pointerId.toString()] = inEvent;
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.down(e, inEvent);
+ol.pointer.PointerEventHandler.prototype.down = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event);
 };
 
 
 /**
- * Handler for `msPointerMove`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a 'pointermove' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.move(e, inEvent);
+ol.pointer.PointerEventHandler.prototype.move = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event);
 };
 
 
 /**
- * Handler for `msPointerUp`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a 'pointerup' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.up(e, inEvent);
-  this.cleanup(inEvent.pointerId);
+ol.pointer.PointerEventHandler.prototype.up = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERUP, data, event);
 };
 
 
 /**
- * Handler for `msPointerOut`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a 'pointerenter' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.leaveOut(e, inEvent);
+ol.pointer.PointerEventHandler.prototype.enter = function(data, event) {
+  data.bubbles = false;
+  this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event);
 };
 
 
 /**
- * Handler for `msPointerOver`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a 'pointerleave' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.enterOver(e, inEvent);
+ol.pointer.PointerEventHandler.prototype.leave = function(data, event) {
+  data.bubbles = false;
+  this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event);
 };
 
 
 /**
- * Handler for `msPointerCancel`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a 'pointerover' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanup(inEvent.pointerId);
+ol.pointer.PointerEventHandler.prototype.over = function(data, event) {
+  data.bubbles = true;
+  this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event);
 };
 
 
 /**
- * Handler for `msLostPointerCapture`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a 'pointerout' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('lostpointercapture',
-      inEvent, inEvent);
-  this.dispatcher.dispatchEvent(e);
+ol.pointer.PointerEventHandler.prototype.out = function(data, event) {
+  data.bubbles = true;
+  this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event);
 };
 
 
 /**
- * Handler for `msGotPointerCapture`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a 'pointercancel' event.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('gotpointercapture',
-      inEvent, inEvent);
-  this.dispatcher.dispatchEvent(e);
+ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) {
+  this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event);
 };
 
-// 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');
-goog.require('ol.pointer.EventSource');
-
 
 /**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @constructor
- * @extends {ol.pointer.EventSource}
+ * Triggers a combination of 'pointerout' and 'pointerleave' events.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-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.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) {
+  this.out(data, event);
+  if (!this.contains_(data.target, data.relatedTarget)) {
+    this.leave(data, event);
+  }
 };
-ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
 
 
 /**
- * Handler for `pointerdown`.
- *
- * @param {Event} inEvent The in event.
+ * Triggers a combination of 'pointerover' and 'pointerevents' events.
+ * @param {Object} data Pointer event data.
+ * @param {Event} event The event.
  */
-ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) {
+  this.over(data, event);
+  if (!this.contains_(data.target, data.relatedTarget)) {
+    this.enter(data, event);
+  }
 };
 
 
 /**
- * Handler for `pointermove`.
- *
- * @param {Event} inEvent The in event.
+ * @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.NativeSource.prototype.pointerMove = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) {
+  if (!container || !contained) {
+    return false;
+  }
+  return container.contains(contained);
 };
 
 
+// EVENT CREATION AND TRACKING
 /**
- * Handler for `pointerup`.
+ * Creates a new Event of type `inType`, based on the information in
+ * `data`.
  *
- * @param {Event} inEvent The in event.
+ * @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.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) {
+  return new ol.pointer.PointerEvent(inType, event, data);
 };
 
 
 /**
- * Handler for `pointerout`.
- *
- * @param {Event} inEvent The in event.
+ * 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.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) {
+  var e = this.makeEvent(inType, data, event);
+  this.dispatchEvent(e);
 };
 
 
 /**
- * Handler for `pointerover`.
- *
- * @param {Event} inEvent The in event.
+ * Creates a pointer event from a native pointer event
+ * and dispatches this event.
+ * @param {Event} event A platform event with a target.
  */
-ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) {
+  var e = this.makeEvent(event.type, event, event);
+  this.dispatchEvent(e);
 };
 
 
 /**
- * Handler for `pointercancel`.
- *
- * @param {Event} inEvent The in event.
+ * 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.
  */
-ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) {
+  var pointerEvent = this.makeEvent(
+      eventType, ol.pointer.MouseSource.prepareEvent(event, this), event);
+  return pointerEvent;
 };
 
 
 /**
- * Handler for `lostpointercapture`.
- *
- * @param {Event} inEvent The in event.
+ * @inheritDoc
  */
-ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
+ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
+  this.unregister_();
+  ol.events.EventTarget.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * Handler for `gotpointercapture`.
- *
- * @param {Event} inEvent The in event.
+ * Properties to copy when cloning an event, with default values.
+ * @type {Array.<Array>}
  */
-ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.PointerEvent');
+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');
 
 goog.require('ol');
-goog.require('ol.events.Event');
+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');
 
 
 /**
- * A class for pointer events.
- *
- * This class is used as an abstraction for mouse events,
- * touch events and even native pointer events.
- *
+ * @param {ol.PluggableMap} 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.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.
+ * @extends {ol.events.EventTarget}
  */
-ol.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) {
-  ol.events.Event.call(this, type);
-
-  /**
-   * @const
-   * @type {Event}
-   */
-  this.originalEvent = originalEvent;
+ol.MapBrowserEventHandler = function(map, moveTolerance) {
 
-  var eventDict = opt_eventDict ? opt_eventDict : {};
+  ol.events.EventTarget.call(this);
 
   /**
-   * @type {number}
+   * This is the element that we will listen to the real events on.
+   * @type {ol.PluggableMap}
+   * @private
    */
-  this.buttons = this.getButtons_(eventDict);
+  this.map_ = map;
 
   /**
    * @type {number}
+   * @private
    */
-  this.pressure = this.getPressure_(eventDict, this.buttons);
-
-  // MouseEvent related properties
+  this.clickTimeoutId_ = 0;
 
   /**
    * @type {boolean}
+   * @private
    */
-  this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;
+  this.dragging_ = false;
 
   /**
-   * @type {boolean}
+   * @type {!Array.<ol.EventsKey>}
+   * @private
    */
-  this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;
+  this.dragListenerKeys_ = [];
 
   /**
-   * @type {Object}
+   * @type {number}
+   * @private
    */
-  this.view = 'view' in eventDict ? eventDict['view'] : null;
+  this.moveTolerance_ = moveTolerance ?
+    moveTolerance * ol.has.DEVICE_PIXEL_RATIO : ol.has.DEVICE_PIXEL_RATIO;
 
   /**
-   * @type {number}
+   * The most recent "down" type event (or null if none have occurred).
+   * Set on pointerdown.
+   * @type {ol.pointer.PointerEvent}
+   * @private
    */
-  this.detail = 'detail' in eventDict ? eventDict['detail'] : null;
+  this.down_ = null;
+
+  var element = this.map_.getViewport();
 
   /**
    * @type {number}
+   * @private
    */
-  this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;
+  this.activePointers_ = 0;
 
   /**
-   * @type {number}
+   * @type {!Object.<number, boolean>}
+   * @private
    */
-  this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;
+  this.trackedTouches_ = {};
 
   /**
-   * @type {number}
+   * Event handler which generates pointer events for
+   * the viewport element.
+   *
+   * @type {ol.pointer.PointerEventHandler}
+   * @private
    */
-  this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;
+  this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element);
 
   /**
-   * @type {number}
+   * Event handler which generates pointer events for
+   * the document (used when dragging).
+   *
+   * @type {ol.pointer.PointerEventHandler}
+   * @private
    */
-  this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;
+  this.documentPointerEventHandler_ = null;
 
   /**
-   * @type {boolean}
+   * @type {?ol.EventsKey}
+   * @private
    */
-  this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;
+  this.pointerdownListenerKey_ = ol.events.listen(this.pointerEventHandler_,
+      ol.pointer.EventType.POINTERDOWN,
+      this.handlePointerDown_, this);
 
   /**
-   * @type {boolean}
+   * @type {?ol.EventsKey}
+   * @private
    */
-  this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;
+  this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_,
+      ol.pointer.EventType.POINTERMOVE,
+      this.relayEvent_, this);
 
-  /**
-   * @type {boolean}
-   */
-  this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;
+};
+ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget);
 
-  /**
-   * @type {boolean}
-   */
-  this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;
 
-  /**
-   * @type {number}
-   */
-  this.button = 'button' in eventDict ? eventDict['button'] : 0;
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+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);
+  }
+};
 
-  /**
-   * @type {Node}
-   */
-  this.relatedTarget = 'relatedTarget' in eventDict ?
-      eventDict['relatedTarget'] : null;
 
-  // PointerEvent related properties
+/**
+ * Keeps track on how many pointers are currently active.
+ *
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.updateActivePointers_ = function(pointerEvent) {
+  var event = pointerEvent;
 
-  /**
-   * @const
-   * @type {number}
-   */
-  this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;
+  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;
+};
 
-  /**
-   * @type {number}
-   */
-  this.width = 'width' in eventDict ? eventDict['width'] : 0;
 
-  /**
-   * @type {number}
-   */
-  this.height = 'height' in eventDict ? eventDict['height'] : 0;
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
+  this.updateActivePointers_(pointerEvent);
+  var newEvent = new ol.MapBrowserPointerEvent(
+      ol.MapBrowserEventType.POINTERUP, this.map_, pointerEvent);
+  this.dispatchEvent(newEvent);
 
-  /**
-   * @type {number}
-   */
-  this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;
+  // We emulate click events on left mouse button click, touch contact, and pen
+  // contact. isMouseActionButton returns true in these cases (evt.button is set
+  // to 0).
+  // See http://www.w3.org/TR/pointerevents/#button-states
+  // We only fire click, singleclick, and doubleclick if nobody has called
+  // event.stopPropagation() or event.preventDefault().
+  if (!newEvent.propagationStopped && !this.dragging_ && this.isMouseActionButton_(pointerEvent)) {
+    this.emulateClick_(this.down_);
+  }
 
-  /**
-   * @type {number}
-   */
-  this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;
+  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;
+  }
+};
 
-  /**
-   * @type {string}
-   */
-  this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';
 
-  /**
-   * @type {number}
-   */
-  this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @return {boolean} If the left mouse button was pressed.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) {
+  return pointerEvent.button === 0;
+};
 
-  /**
-   * @type {boolean}
-   */
-  this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;
 
-  // keep the semantics of preventDefault
-  if (originalEvent.preventDefault) {
-    this.preventDefault = function() {
-      originalEvent.preventDefault();
-    };
+/**
+ * @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 (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);
+
+    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)
+    );
   }
 };
-ol.inherits(ol.pointer.PointerEvent, ol.events.Event);
 
 
 /**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
  * @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;
-    }
+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);
   }
-  return buttons;
+
+  // Some native android browser triggers mousemove events during small period
+  // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or
+  // https://code.google.com/p/android/issues/detail?id=19827
+  // ex: Galaxy Tab P3110 + Android 4.1.1
+  pointerEvent.preventDefault();
 };
 
 
 /**
+ * Wrap and relay a pointer event.  Note that this requires that the type
+ * string for the MapBrowserPointerEvent matches the PointerEvent type.
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
  * @private
- * @param {Object.<string, ?>} eventDict The event dictionary.
- * @param {number} buttons Button indicator.
- * @return {number} The pressure.
  */
-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;
+ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
+  var dragging = !!(this.down_ && this.isMoving_(pointerEvent));
+  this.dispatchEvent(new ol.MapBrowserPointerEvent(
+      pointerEvent.type, this.map_, pointerEvent, dragging));
 };
 
 
 /**
- * Is the `buttons` property supported?
- * @type {boolean}
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @return {boolean} Is moving.
+ * @private
  */
-ol.pointer.PointerEvent.HAS_BUTTONS = false;
+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_;
+};
 
 
 /**
- * Checks if the `buttons` property is supported.
+ * @inheritDoc
  */
-(function() {
-  try {
-    var ev = new MouseEvent('click', {buttons: 1});
-    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
-  } catch (e) {
-    // pass
+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;
   }
-})();
 
-// Based on https://github.com/Polymer/PointerEvents
+  this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.dragListenerKeys_.length = 0;
 
-// 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.
+  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.pointer.TouchSource');
+goog.provide('ol.MapEventType');
 
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.pointer.EventSource');
-goog.require('ol.pointer.MouseSource');
+/**
+ * @enum {string}
+ */
+ol.MapEventType = {
+
+  /**
+   * Triggered after a map frame is rendered.
+   * @event ol.MapEvent#postrender
+   * @api
+   */
+  POSTRENDER: 'postrender',
+
+  /**
+   * Triggered when the map starts moving.
+   * @event ol.MapEvent#movestart
+   * @api
+   */
+  MOVESTART: 'movestart',
+
+  /**
+   * Triggered after the map is moved.
+   * @event ol.MapEvent#moveend
+   * @api
+   */
+  MOVEEND: 'moveend'
+
+};
+
+goog.provide('ol.MapProperty');
+
+/**
+ * @enum {string}
+ */
+ol.MapProperty = {
+  LAYERGROUP: 'layergroup',
+  SIZE: 'size',
+  TARGET: 'target',
+  VIEW: 'view'
+};
+
+goog.provide('ol.TileState');
+
+/**
+ * @enum {number}
+ */
+ol.TileState = {
+  IDLE: 0,
+  LOADING: 1,
+  LOADED: 2,
+  ERROR: 3,
+  EMPTY: 4,
+  ABORT: 5
+};
+
+goog.provide('ol.structs.PriorityQueue');
+
+goog.require('ol.asserts');
+goog.require('ol.obj');
 
 
 /**
+ * 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 {ol.pointer.PointerEventHandler} dispatcher The event handler.
- * @param {ol.pointer.MouseSource} mouseSource Mouse source.
- * @extends {ol.pointer.EventSource}
+ * @param {function(T): number} priorityFunction Priority function.
+ * @param {function(T): string} keyFunction Key function.
+ * @struct
+ * @template T
  */
-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);
+ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
 
   /**
-   * @const
-   * @type {!Object.<string, Event|Object>}
+   * @type {function(T): number}
+   * @private
    */
-  this.pointerMap = dispatcher.pointerMap;
+  this.priorityFunction_ = priorityFunction;
 
   /**
-   * @const
-   * @type {ol.pointer.MouseSource}
+   * @type {function(T): string}
+   * @private
    */
-  this.mouseSource = mouseSource;
+  this.keyFunction_ = keyFunction;
 
   /**
+   * @type {Array.<T>}
    * @private
-   * @type {number|undefined}
    */
-  this.firstTouchId_ = undefined;
+  this.elements_ = [];
 
   /**
+   * @type {Array.<number>}
    * @private
-   * @type {number}
    */
-  this.clickCount_ = 0;
+  this.priorities_ = [];
 
   /**
+   * @type {Object.<string, boolean>}
    * @private
-   * @type {number|undefined}
    */
-  this.resetId_ = undefined;
-};
-ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);
-
+  this.queuedElements_ = {};
 
-/**
- * Mouse event timeout: This should be long enough to
- * ignore compat mouse events made by touch.
- * @const
- * @type {number}
- */
-ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
+};
 
 
 /**
  * @const
  * @type {number}
  */
-ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
+ol.structs.PriorityQueue.DROP = Infinity;
 
 
 /**
- * @const
- * @type {string}
+ * FIXME empty description for jsdoc
  */
-ol.pointer.TouchSource.POINTER_TYPE = 'touch';
+ol.structs.PriorityQueue.prototype.clear = function() {
+  this.elements_.length = 0;
+  this.priorities_.length = 0;
+  ol.obj.clear(this.queuedElements_);
+};
 
 
 /**
- * @private
- * @param {Touch} inTouch The in touch.
- * @return {boolean} True, if this is the primary touch.
+ * Remove and return the highest-priority element. O(log N).
+ * @return {T} Element.
  */
-ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
-  return this.firstTouchId_ === inTouch.identifier;
+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;
 };
 
 
 /**
- * Set primary touch if there are no pointers, or the only pointer is the mouse.
- * @param {Touch} inTouch The in touch.
- * @private
+ * Enqueue an element. O(log N).
+ * @param {T} element Element.
+ * @return {boolean} The element was added to the queue.
  */
-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_();
+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;
 };
 
 
 /**
- * @private
- * @param {Object} inPointer The in pointer object.
+ * @return {number} Count.
  */
-ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
-  if (inPointer.isPrimary) {
-    this.firstTouchId_ = undefined;
-    this.resetClickCount_();
-  }
+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.pointer.TouchSource.prototype.resetClickCount_ = function() {
-  this.resetId_ = setTimeout(
-      this.resetClickCountHandler_.bind(this),
-      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
+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.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
-  this.clickCount_ = 0;
-  this.resetId_ = undefined;
-};
-
-
-/**
- * @private
- */
-ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
-  if (this.resetId_ !== undefined) {
-    clearTimeout(this.resetId_);
-  }
+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
- * @param {Event} browserEvent Browser event
- * @param {Touch} inTouch Touch event
- * @return {Object} A pointer 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.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
+  return (index - 1) >> 1;
 };
 
 
 /**
+ * Make this a heap. O(N).
  * @private
- * @param {Event} inEvent Touch event
- * @param {function(Event, Object)} inFunction In function.
  */
-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);
+ol.structs.PriorityQueue.prototype.heapify_ = function() {
+  var i;
+  for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
+    this.siftUp_(i);
   }
 };
 
 
 /**
- * @private
- * @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.
+ * @return {boolean} Is empty.
  */
-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;
+ol.structs.PriorityQueue.prototype.isEmpty = function() {
+  return this.elements_.length === 0;
 };
 
 
 /**
- * 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.
+ * @param {string} key Key.
+ * @return {boolean} Is key queued.
  */
-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]);
-    }
-  }
+ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
+  return key in this.queuedElements_;
 };
 
 
 /**
- * Handler for `touchstart`, triggers `pointerover`,
- * `pointerenter` and `pointerdown` events.
- *
- * @param {Event} inEvent The in event.
+ * @param {T} element Element.
+ * @return {boolean} Is queued.
  */
-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_);
+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
- * @param {Event} browserEvent The event.
- * @param {Object} inPointer The in pointer object.
  */
-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.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;
 
-/**
- * Handler for `touchmove`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
-  inEvent.preventDefault();
-  this.processTouches_(inEvent, this.moveOverOut_);
-};
+  while (index < (count >> 1)) {
+    var lIndex = this.getLeftChildIndex_(index);
+    var rIndex = this.getRightChildIndex_(index);
 
+    var smallerChildIndex = rIndex < count &&
+        priorities[rIndex] < priorities[lIndex] ?
+      rIndex : lIndex;
 
-/**
- * @private
- * @param {Event} browserEvent The event.
- * @param {Object} inPointer The in pointer.
- */
-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);
-    }
+    elements[index] = elements[smallerChildIndex];
+    priorities[index] = priorities[smallerChildIndex];
+    index = smallerChildIndex;
   }
-  pointer.out = event;
-  pointer.outTarget = event.target;
-};
-
-
-/**
- * Handler for `touchend`, triggers `pointerup`,
- * `pointerout` and `pointerleave` events.
- *
- * @param {Event} inEvent The event.
- */
-ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
-  this.dedupSynthMouse_(inEvent);
-  this.processTouches_(inEvent, this.upOut_);
-};
-
-
-/**
- * @private
- * @param {Event} browserEvent An event.
- * @param {Object} inPointer The inPointer object.
- */
-ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) {
-  this.dispatcher.up(inPointer, browserEvent);
-  this.dispatcher.out(inPointer, browserEvent);
-  this.dispatcher.leave(inPointer, browserEvent);
-  this.cleanUpPointer_(inPointer);
-};
-
 
-/**
- * Handler for `touchcancel`, triggers `pointercancel`,
- * `pointerout` and `pointerleave` events.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
-  this.processTouches_(inEvent, this.cancelOut_);
+  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
- * @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);
-};
-
+ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) {
+  var elements = this.elements_;
+  var priorities = this.priorities_;
+  var element = elements[index];
+  var priority = priorities[index];
 
-/**
- * @private
- * @param {Object} inPointer The inPointer object.
- */
-ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
-  delete this.pointerMap[inPointer.pointerId];
-  this.removePrimaryPointer_(inPointer);
+  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;
 };
 
 
 /**
- * Prevent synth mouse events from creating pointer events.
- *
- * @private
- * @param {Event} inEvent The in event.
+ * FIXME empty description for jsdoc
  */
-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);
+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_();
 };
 
-// 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.provide('ol.TileQueue');
 
 goog.require('ol');
+goog.require('ol.TileState');
 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');
+goog.require('ol.events.EventType');
+goog.require('ol.structs.PriorityQueue');
 
 
 /**
  * @constructor
- * @extends {ol.events.EventTarget}
- * @param {Element|HTMLDocument} element Viewport element.
+ * @extends {ol.structs.PriorityQueue.<Array>}
+ * @param {ol.TilePriorityFunction} tilePriorityFunction
+ *     Tile priority function.
+ * @param {function(): ?} tileChangeCallback
+ *     Function called on each tile change event.
+ * @struct
  */
-ol.pointer.PointerEventHandler = function(element) {
-  ol.events.EventTarget.call(this);
+ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
 
-  /**
-   * @const
-   * @private
-   * @type {Element|HTMLDocument}
-   */
-  this.element_ = element;
+  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();
+      });
 
   /**
-   * @const
-   * @type {!Object.<string, Event|Object>}
+   * @private
+   * @type {function(): ?}
    */
-  this.pointerMap = {};
+  this.tileChangeCallback_ = tileChangeCallback;
 
   /**
-   * @type {Object.<string, function(Event)>}
    * @private
+   * @type {number}
    */
-  this.eventMap_ = {};
+  this.tilesLoading_ = 0;
 
   /**
-   * @type {Array.<ol.pointer.EventSource>}
    * @private
+   * @type {!Object.<string,boolean>}
    */
-  this.eventSourceList_ = [];
+  this.tilesLoadingKeys_ = {};
 
-  this.registerSources();
 };
-ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget);
+ol.inherits(ol.TileQueue, ol.structs.PriorityQueue);
 
 
 /**
- * Set up the event sources (mouse, touch and native pointers)
- * that generate pointer events.
+ * @inheritDoc
  */
-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.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);
   }
-
-  // register events on the viewport element
-  this.register_();
+  return added;
 };
 
 
 /**
- * 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.
+ * @return {number} Number of tiles loading.
  */
-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);
-  }
+ol.TileQueue.prototype.getTilesLoading = function() {
+  return this.tilesLoading_;
 };
 
 
 /**
- * Set up the events for all registered event sources.
- * @private
+ * @param {ol.events.Event} event Event.
+ * @protected
  */
-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.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_();
   }
 };
 
 
 /**
- * Remove all registered events.
- * @private
+ * @param {number} maxTotalLoading Maximum number tiles to load simultaneously.
+ * @param {number} maxNewLoads Maximum number of new tiles to load.
  */
-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.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) {
+  var newLoads = 0;
+  var abortedTiles = false;
+  var state, tile, tileKey;
+  while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads &&
+         this.getCount() > 0) {
+    tile = /** @type {ol.Tile} */ (this.dequeue()[0]);
+    tileKey = tile.getKey();
+    state = tile.getState();
+    if (state === ol.TileState.ABORT) {
+      abortedTiles = true;
+    } else if (state === ol.TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
+      this.tilesLoadingKeys_[tileKey] = true;
+      ++this.tilesLoading_;
+      ++newLoads;
+      tile.load();
+    }
+  }
+  if (newLoads === 0 && abortedTiles) {
+    // Do not stop the render loop when all wanted tiles were aborted due to
+    // a small, saturated tile cache.
+    this.tileChangeCallback_();
   }
 };
 
+goog.provide('ol.CenterConstraint');
 
-/**
- * Calls the right handler for a new event.
- * @private
- * @param {Event} inEvent Browser event.
- */
-ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
-  var type = inEvent.type;
-  var handler = this.eventMap_[type];
-  if (handler) {
-    handler(inEvent);
-  }
-};
+goog.require('ol.math');
 
 
 /**
- * Setup listeners for the given events.
- * @private
- * @param {Array.<string>} events List of events.
+ * @param {ol.Extent} extent Extent.
+ * @return {ol.CenterConstraintType} The constraint.
  */
-ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
-  events.forEach(function(eventName) {
-    ol.events.listen(this.element_, eventName, this.eventHandler_, this);
-  }, this);
+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;
+      }
+    });
 };
 
 
 /**
- * Unregister listeners for the given events.
- * @private
- * @param {Array.<string>} events List of events.
+ * @param {ol.Coordinate|undefined} center Center.
+ * @return {ol.Coordinate|undefined} Center.
  */
-ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
-  events.forEach(function(e) {
-    ol.events.unlisten(this.element_, e, this.eventHandler_, this);
-  }, this);
+ol.CenterConstraint.none = function(center) {
+  return center;
 };
 
+goog.provide('ol.ResolutionConstraint');
+
+goog.require('ol.array');
+goog.require('ol.math');
+
 
 /**
- * Returns a snapshot of inEvent, with writable properties.
- *
- * @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.
+ * @param {Array.<number>} resolutions Resolutions.
+ * @return {ol.ResolutionConstraintType} Zoom function.
  */
-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];
-  }
+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;
+      }
+    });
+};
 
-  return eventCopy;
+
+/**
+ * @param {number} power Power.
+ * @param {number} maxResolution Maximum resolution.
+ * @param {number=} opt_maxLevel Maximum level.
+ * @return {ol.ResolutionConstraintType} Zoom function.
+ */
+ol.ResolutionConstraint.createSnapToPower = function(power, maxResolution, opt_maxLevel) {
+  return (
+    /**
+     * @param {number|undefined} resolution Resolution.
+     * @param {number} delta Delta.
+     * @param {number} direction Direction.
+     * @return {number|undefined} Resolution.
+     */
+    function(resolution, delta, direction) {
+      if (resolution !== undefined) {
+        var offset = -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');
 
-// EVENTS
+goog.require('ol.math');
 
 
 /**
- * Triggers a 'pointerdown' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
  */
-ol.pointer.PointerEventHandler.prototype.down = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event);
+ol.RotationConstraint.disable = function(rotation, delta) {
+  if (rotation !== undefined) {
+    return 0;
+  } else {
+    return undefined;
+  }
 };
 
 
 /**
- * Triggers a 'pointermove' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * @param {number|undefined} rotation Rotation.
+ * @param {number} delta Delta.
+ * @return {number|undefined} Rotation.
  */
-ol.pointer.PointerEventHandler.prototype.move = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event);
+ol.RotationConstraint.none = function(rotation, delta) {
+  if (rotation !== undefined) {
+    return rotation + delta;
+  } else {
+    return undefined;
+  }
 };
 
 
 /**
- * Triggers a 'pointerup' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * @param {number} n N.
+ * @return {ol.RotationConstraintType} Rotation constraint.
  */
-ol.pointer.PointerEventHandler.prototype.up = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERUP, data, event);
+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;
+      }
+    });
 };
 
 
 /**
- * Triggers a 'pointerenter' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * @param {number=} opt_tolerance Tolerance.
+ * @return {ol.RotationConstraintType} Rotation constraint.
  */
-ol.pointer.PointerEventHandler.prototype.enter = function(data, event) {
-  data.bubbles = false;
-  this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event);
+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');
 
 /**
- * Triggers a 'pointerleave' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * @enum {number}
  */
-ol.pointer.PointerEventHandler.prototype.leave = function(data, event) {
-  data.bubbles = false;
-  this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event);
+ol.ViewHint = {
+  ANIMATING: 0,
+  INTERACTING: 1
 };
 
+goog.provide('ol.ViewProperty');
 
 /**
- * Triggers a 'pointerover' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * @enum {string}
  */
-ol.pointer.PointerEventHandler.prototype.over = function(data, event) {
-  data.bubbles = true;
-  this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event);
+ol.ViewProperty = {
+  CENTER: 'center',
+  RESOLUTION: 'resolution',
+  ROTATION: 'rotation'
 };
 
+goog.provide('ol.string');
 
 /**
- * Triggers a 'pointerout' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * @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;
+};
+
+/**
+ * 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
  */
-ol.pointer.PointerEventHandler.prototype.out = function(data, event) {
-  data.bubbles = true;
-  this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event);
+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);
+
+    if (n1 > n2) {
+      return 1;
+    }
+    if (n2 > n1) {
+      return -1;
+    }
+  }
+
+  return 0;
 };
 
+goog.provide('ol.coordinate');
+
+goog.require('ol.math');
+goog.require('ol.string');
+
 
 /**
- * Triggers a 'pointercancel' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * 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
  */
-ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event);
+ol.coordinate.add = function(coordinate, delta) {
+  coordinate[0] += delta[0];
+  coordinate[1] += delta[1];
+  return coordinate;
 };
 
 
 /**
- * Triggers a combination of 'pointerout' and 'pointerleave' events.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * Calculates the point closest to the passed coordinate on the passed circle.
+ *
+ * @param {ol.Coordinate} coordinate The coordinate.
+ * @param {ol.geom.Circle} circle The circle.
+ * @return {ol.Coordinate} Closest point on the circumference
  */
-ol.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) {
-  this.out(data, event);
-  if (!this.contains_(data.target, data.relatedTarget)) {
-    this.leave(data, event);
+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);
+
+  var x, y;
+
+  x = x0 + r * dx / d;
+  y = y0 + r * dy / d;
+
+  return [x, y];
 };
 
 
 /**
- * Triggers a combination of 'pointerover' and 'pointerevents' events.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
+ * Calculates the point closest to the passed coordinate on the passed segment.
+ * This is the foot of the perpendicular of the coordinate to the segment when
+ * the foot is on the segment, or the closest segment coordinate when the foot
+ * is outside the segment.
+ *
+ * @param {ol.Coordinate} coordinate The coordinate.
+ * @param {Array.<ol.Coordinate>} segment The two coordinates of the segment.
+ * @return {ol.Coordinate} The foot of the perpendicular of the coordinate to
+ *     the segment.
  */
-ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) {
-  this.over(data, event);
-  if (!this.contains_(data.target, data.relatedTarget)) {
-    this.enter(data, event);
+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];
 };
 
 
 /**
- * @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.
+ * 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
  */
-ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) {
-  if (!container || !contained) {
-    return false;
+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);
+    });
+};
+
+
+/**
+ * @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.
+ */
+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;
+
+  if (sec >= 60) {
+    sec = 0;
+    min += 1;
   }
-  return container.contains(contained);
+
+  if (min >= 60) {
+    min = 0;
+    deg += 1;
+  }
+
+  return deg + '\u00b0 ' + ol.string.padNumber(min, 2) + '\u2032 ' +
+    ol.string.padNumber(sec, 2, dflPrecision) + '\u2033' +
+    (normalizedDegrees == 0 ? '' : ' ' + hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0));
 };
 
 
-// EVENT CREATION AND TRACKING
 /**
- * Creates a new Event of type `inType`, based on the information in
- * `data`.
+ * 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 {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`.
+ * 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
  */
-ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) {
-  return new ol.pointer.PointerEvent(inType, event, data);
+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 '';
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.Coordinate} coordinate1 First coordinate.
+ * @param {ol.Coordinate} coordinate2 Second coordinate.
+ * @return {boolean} Whether the passed coordinates are equal.
  */
-ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) {
-  var e = this.makeEvent(inType, data, event);
-  this.dispatchEvent(e);
+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;
 };
 
 
 /**
- * Creates a pointer event from a native pointer event
- * and dispatches this event.
- * @param {Event} event A platform event with a target.
+ * 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
  */
-ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) {
-  var e = this.makeEvent(event.type, event, event);
-  this.dispatchEvent(e);
+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;
 };
 
 
 /**
- * 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.
+ * Scale `coordinate` by `scale`. `coordinate` is modified in place and returned
+ * by the function.
+ *
+ * Example:
+ *
+ *     var coord = [7.85, 47.983333];
+ *     var scale = 1.2;
+ *     ol.coordinate.scale(coord, scale);
+ *     // coord is now [9.42, 57.5799996]
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {number} scale Scale factor.
+ * @return {ol.Coordinate} Coordinate.
  */
-ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) {
-  var pointerEvent = this.makeEvent(
-      eventType, ol.pointer.MouseSource.prepareEvent(event, this), event);
-  return pointerEvent;
+ol.coordinate.scale = function(coordinate, scale) {
+  coordinate[0] *= scale;
+  coordinate[1] *= scale;
+  return coordinate;
 };
 
 
 /**
- * @inheritDoc
+ * Subtract `delta` to `coordinate`. `coordinate` is modified in place and
+ * returned by the function.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Coordinate} delta Delta.
+ * @return {ol.Coordinate} Coordinate.
  */
-ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
-  this.unregister_();
-  ol.events.EventTarget.prototype.disposeInternal.call(this);
+ol.coordinate.sub = function(coordinate, delta) {
+  coordinate[0] -= delta[0];
+  coordinate[1] -= delta[1];
+  return coordinate;
 };
 
 
 /**
- * Properties to copy when cloning an event, with default values.
- * @type {Array.<Array>}
+ * @param {ol.Coordinate} coord1 First coordinate.
+ * @param {ol.Coordinate} coord2 Second coordinate.
+ * @return {number} Squared distance between coord1 and coord2.
  */
-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');
-
-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');
+ol.coordinate.squaredDistance = function(coord1, coord2) {
+  var dx = coord1[0] - coord2[0];
+  var dy = coord1[1] - coord2[1];
+  return dx * dx + dy * dy;
+};
 
 
 /**
- * @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}
+ * @param {ol.Coordinate} coord1 First coordinate.
+ * @param {ol.Coordinate} coord2 Second coordinate.
+ * @return {number} Distance between coord1 and coord2.
  */
-ol.MapBrowserEventHandler = function(map, moveTolerance) {
-
-  ol.events.EventTarget.call(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.<ol.EventsKey>}
-   * @private
-   */
-  this.dragListenerKeys_ = [];
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.moveTolerance_ = moveTolerance ?
-      moveTolerance * ol.has.DEVICE_PIXEL_RATIO : ol.has.DEVICE_PIXEL_RATIO;
-
-  /**
-   * 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);
+ol.coordinate.distance = function(coord1, coord2) {
+  return Math.sqrt(ol.coordinate.squaredDistance(coord1, coord2));
+};
 
-  /**
-   * @type {?ol.EventsKey}
-   * @private
-   */
-  this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_,
-      ol.pointer.EventType.POINTERMOVE,
-      this.relayEvent_, this);
 
+/**
+ * Calculate the squared distance from a coordinate to a line segment.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate of the point.
+ * @param {Array.<ol.Coordinate>} segment Line segment (2 coordinates).
+ * @return {number} Squared distance from the point to the line segment.
+ */
+ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
+  return ol.coordinate.squaredDistance(coordinate,
+      ol.coordinate.closestOnSegment(coordinate, segment));
 };
-ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget);
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
+ * Format a geographic coordinate with the hemisphere, degrees, minutes, and
+ * seconds.
+ *
+ * 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
  */
-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);
+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 {
-    // 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);
+    return '';
   }
 };
 
 
 /**
- * Keeps track on how many pointers are currently active.
+ * Format a coordinate as a comma delimited string.
  *
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
+ * 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
  */
-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;
+ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
+  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
 };
 
+goog.provide('ol.easing');
+
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
+ * Start slow and speed up.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
  */
-ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
-  this.updateActivePointers_(pointerEvent);
-  var newEvent = new ol.MapBrowserPointerEvent(
-     ol.MapBrowserEventType.POINTERUP, this.map_, pointerEvent);
-  this.dispatchEvent(newEvent);
+ol.easing.easeIn = function(t) {
+  return Math.pow(t, 3);
+};
 
- // 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_);
-  }
 
-  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;
-  }
+/**
+ * Start fast and slow down.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
+ */
+ol.easing.easeOut = function(t) {
+  return 1 - ol.easing.easeIn(1 - t);
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @return {boolean} If the left mouse button was pressed.
- * @private
+ * Start slow, speed up, and then slow down again.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
  */
-ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) {
-  return pointerEvent.button === 0;
+ol.easing.inAndOut = function(t) {
+  return 3 * t * t - 2 * t * t * t;
 };
 
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
+ * Maintain a constant speed over time.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
  */
-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;
+ol.easing.linear = function(t) {
+  return t;
+};
 
-  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);
 
-    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)
-    );
+/**
+ * Start slow, speed up, and at the very end slow down again.  This has the
+ * same general behavior as {@link ol.easing.inAndOut}, but the final slowdown
+ * is delayed.
+ * @param {number} t Input between 0 and 1.
+ * @return {number} Output between 0 and 1.
+ * @api
+ */
+ol.easing.upAndDown = function(t) {
+  if (t < 0.5) {
+    return ol.easing.inAndOut(2 * t);
+  } else {
+    return 1 - ol.easing.inAndOut(2 * (t - 0.5));
   }
 };
 
+goog.provide('ol.geom.GeometryLayout');
+
 
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
+ * 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}
  */
-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();
+ol.geom.GeometryLayout = {
+  XY: 'XY',
+  XYZ: 'XYZ',
+  XYM: 'XYM',
+  XYZM: 'XYZM'
 };
 
+goog.provide('ol.functions');
 
 /**
- * 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
+ * Always returns true.
+ * @returns {boolean} true.
  */
-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.functions.TRUE = function() {
+  return true;
 };
 
-
 /**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @return {boolean} Is moving.
- * @private
+ * Always returns false.
+ * @returns {boolean} false.
  */
-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_;
+ol.functions.FALSE = function() {
+  return false;
 };
 
+goog.provide('ol.geom.flat.transform');
+
 
 /**
- * @inheritDoc
+ * @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.
  */
-ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
-  if (this.relayedListenerKey_) {
-    ol.events.unlistenByKey(this.relayedListenerKey_);
-    this.relayedListenerKey_ = null;
+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 (this.pointerdownListenerKey_) {
-    ol.events.unlistenByKey(this.pointerdownListenerKey_);
-    this.pointerdownListenerKey_ = null;
+  if (opt_dest && dest.length != i) {
+    dest.length = i;
   }
+  return dest;
+};
 
-  this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.dragListenerKeys_.length = 0;
 
-  if (this.documentPointerEventHandler_) {
-    this.documentPointerEventHandler_.dispose();
-    this.documentPointerEventHandler_ = null;
+/**
+ * @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.
+ */
+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];
+    }
   }
-  if (this.pointerEventHandler_) {
-    this.pointerEventHandler_.dispose();
-    this.pointerEventHandler_ = null;
+  if (opt_dest && dest.length != i) {
+    dest.length = i;
   }
-  ol.events.EventTarget.prototype.disposeInternal.call(this);
+  return dest;
 };
 
-goog.provide('ol.MapProperty');
 
 /**
- * @enum {string}
+ * 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.
  */
-ol.MapProperty = {
-  LAYERGROUP: 'layergroup',
-  SIZE: 'size',
-  TARGET: 'target',
-  VIEW: 'view'
+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;
 };
 
-goog.provide('ol.TileState');
 
 /**
- * @enum {number}
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} deltaX Delta X.
+ * @param {number} deltaY Delta Y.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed coordinates.
  */
-ol.TileState = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3,
-  EMPTY: 4,
-  ABORT: 5
+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.structs.PriorityQueue');
+goog.provide('ol.transform');
 
 goog.require('ol.asserts');
-goog.require('ol.obj');
 
 
 /**
- * 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
+ * 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.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}
+ * @private
+ * @type {ol.Transform}
  */
-ol.structs.PriorityQueue.DROP = Infinity;
+ol.transform.tmp_ = new Array(6);
 
 
 /**
- * FIXME empty description for jsdoc
+ * Create an identity transform.
+ * @return {!ol.Transform} Identity transform.
  */
-ol.structs.PriorityQueue.prototype.clear = function() {
-  this.elements_.length = 0;
-  this.priorities_.length = 0;
-  ol.obj.clear(this.queuedElements_);
+ol.transform.create = function() {
+  return [1, 0, 0, 1, 0, 0];
 };
 
 
 /**
- * Remove and return the highest-priority element. O(log N).
- * @return {T} Element.
+ * Resets the given transform to an identity transform.
+ * @param {!ol.Transform} transform Transform.
+ * @return {!ol.Transform} Transform.
  */
-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;
+ol.transform.reset = function(transform) {
+  return ol.transform.set(transform, 1, 0, 0, 1, 0, 0);
 };
 
 
 /**
- * Enqueue an element. O(log N).
- * @param {T} element Element.
- * @return {boolean} The element was added to the queue.
+ * 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.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;
-};
+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];
 
+  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 {number} Count.
- */
-ol.structs.PriorityQueue.prototype.getCount = function() {
-  return this.elements_.length;
+  return transform1;
 };
 
-
 /**
- * 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
+ * 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.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) {
-  return index * 2 + 1;
+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;
 };
 
 
 /**
- * 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
+ * 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.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) {
-  return index * 2 + 2;
+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;
 };
 
 
 /**
- * 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
+ * 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.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
-  return (index - 1) >> 1;
+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;
 };
 
 
 /**
- * Make this a heap. O(N).
- * @private
+ * Applies rotation to the given transform.
+ * @param {!ol.Transform} transform Transform.
+ * @param {number} angle Angle in radians.
+ * @return {!ol.Transform} The rotated transform.
  */
-ol.structs.PriorityQueue.prototype.heapify_ = function() {
-  var i;
-  for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
-    this.siftUp_(i);
-  }
+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));
 };
 
 
 /**
- * @return {boolean} Is empty.
+ * 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.structs.PriorityQueue.prototype.isEmpty = function() {
-  return this.elements_.length === 0;
+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 {string} key Key.
- * @return {boolean} Is key queued.
+ * 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.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
-  return key in this.queuedElements_;
+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 {T} element Element.
- * @return {boolean} Is queued.
+ * 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.structs.PriorityQueue.prototype.isQueued = function(element) {
-  return this.isKeyQueued(this.keyFunction_(element));
+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 {number} index The index of the node to move down.
- * @private
+ * Invert the given transform.
+ * @param {!ol.Transform} transform Transform.
+ * @return {!ol.Transform} Inverse of the transform.
  */
-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);
-};
+ol.transform.invert = function(transform) {
+  var det = ol.transform.determinant(transform);
+  ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted
 
+  var a = transform[0];
+  var b = transform[1];
+  var c = transform[2];
+  var d = transform[3];
+  var e = transform[4];
+  var f = transform[5];
 
-/**
- * @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];
+  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;
 
-  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;
+  return transform;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * Returns the determinant of the given matrix.
+ * @param {!ol.Transform} mat Matrix.
+ * @return {number} Determinant.
  */
-ol.structs.PriorityQueue.prototype.reprioritize = function() {
-  var priorityFunction = this.priorityFunction_;
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var index = 0;
-  var n = elements.length;
-  var element, i, priority;
-  for (i = 0; i < n; ++i) {
-    element = elements[i];
-    priority = priorityFunction(element);
-    if (priority == ol.structs.PriorityQueue.DROP) {
-      delete this.queuedElements_[this.keyFunction_(element)];
-    } else {
-      priorities[index] = priority;
-      elements[index++] = element;
-    }
-  }
-  elements.length = index;
-  priorities.length = index;
-  this.heapify_();
+ol.transform.determinant = function(mat) {
+  return mat[0] * mat[3] - mat[1] * mat[2];
 };
 
-goog.provide('ol.TileQueue');
+goog.provide('ol.geom.Geometry');
 
 goog.require('ol');
-goog.require('ol.TileState');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.structs.PriorityQueue');
+goog.require('ol.Object');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
+goog.require('ol.transform');
 
 
 /**
+ * @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.structs.PriorityQueue.<Array>}
- * @param {ol.TilePriorityFunction} tilePriorityFunction
- *     Tile priority function.
- * @param {function(): ?} tileChangeCallback
- *     Function called on each tile change event.
- * @struct
+ * @abstract
+ * @extends {ol.Object}
+ * @api
  */
-ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
+ol.geom.Geometry = function() {
 
-  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();
-      });
+  ol.Object.call(this);
 
   /**
    * @private
-   * @type {function(): ?}
+   * @type {ol.Extent}
    */
-  this.tileChangeCallback_ = tileChangeCallback;
+  this.extent_ = ol.extent.createEmpty();
 
   /**
    * @private
    * @type {number}
    */
-  this.tilesLoading_ = 0;
+  this.extentRevision_ = -1;
+
+  /**
+   * @protected
+   * @type {Object.<string, ol.geom.Geometry>}
+   */
+  this.simplifiedGeometryCache = {};
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.simplifiedGeometryMaxMinSquaredTolerance = 0;
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.simplifiedGeometryRevision = 0;
 
   /**
    * @private
-   * @type {!Object.<string,boolean>}
+   * @type {ol.Transform}
    */
-  this.tilesLoadingKeys_ = {};
+  this.tmpTransform_ = ol.transform.create();
 
 };
-ol.inherits(ol.TileQueue, ol.structs.PriorityQueue);
+ol.inherits(ol.geom.Geometry, ol.Object);
 
 
 /**
- * @inheritDoc
+ * Make a complete copy of the geometry.
+ * @abstract
+ * @return {!ol.geom.Geometry} Clone.
  */
-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;
-};
+ol.geom.Geometry.prototype.clone = function() {};
 
 
 /**
- * @return {number} Number of tiles loading.
+ * @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.
  */
-ol.TileQueue.prototype.getTilesLoading = function() {
-  return this.tilesLoading_;
-};
+ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {};
 
 
 /**
- * @param {ol.events.Event} event Event.
- * @protected
+ * 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
  */
-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_();
-  }
+ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) {
+  var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN];
+  this.closestPointXY(point[0], point[1], closestPoint, Infinity);
+  return closestPoint;
 };
 
 
 /**
- * @param {number} maxTotalLoading Maximum number tiles to load simultaneously.
- * @param {number} maxNewLoads Maximum number of new tiles to load.
+ * 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
  */
-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();
-    }
-  }
+ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) {
+  return this.containsXY(coordinate[0], coordinate[1]);
 };
 
-goog.provide('ol.ResolutionConstraint');
-
-goog.require('ol.array');
-goog.require('ol.math');
-
 
 /**
- * @param {Array.<number>} resolutions Resolutions.
- * @return {ol.ResolutionConstraintType} Zoom function.
+ * @abstract
+ * @param {ol.Extent} extent Extent.
+ * @protected
+ * @return {ol.Extent} extent Extent.
  */
-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;
-        }
-      });
-};
+ol.geom.Geometry.prototype.computeExtent = function(extent) {};
 
 
 /**
- * @param {number} power Power.
- * @param {number} maxResolution Maximum resolution.
- * @param {number=} opt_maxLevel Maximum level.
- * @return {ol.ResolutionConstraintType} Zoom function.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {boolean} Contains (x, y).
  */
-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');
+ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE;
 
 
 /**
- * @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
- * @return {number|undefined} Rotation.
+ * Get the extent of the geometry.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} extent Extent.
+ * @api
  */
-ol.RotationConstraint.disable = function(rotation, delta) {
-  if (rotation !== undefined) {
-    return 0;
-  } else {
-    return undefined;
+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);
 };
 
 
 /**
- * @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
- * @return {number|undefined} Rotation.
+ * 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
  */
-ol.RotationConstraint.none = function(rotation, delta) {
-  if (rotation !== undefined) {
-    return rotation + delta;
-  } else {
-    return undefined;
-  }
-};
+ol.geom.Geometry.prototype.rotate = function(angle, anchor) {};
 
 
 /**
- * @param {number} n N.
- * @return {ol.RotationConstraintType} Rotation constraint.
+ * 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
  */
-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.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {};
 
 
 /**
- * @param {number=} opt_tolerance Tolerance.
- * @return {ol.RotationConstraintType} Rotation constraint.
+ * Create a simplified version of this geometry.  For linestrings, this uses
+ * the the {@link
+ * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
+ * Douglas Peucker} algorithm.  For polygons, a quantization-based
+ * simplification is used to preserve topology.
+ * @function
+ * @param {number} tolerance The tolerance distance for simplification.
+ * @return {ol.geom.Geometry} A new, simplified version of the original
+ *     geometry.
+ * @api
  */
-ol.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;
-        }
-      });
+ol.geom.Geometry.prototype.simplify = function(tolerance) {
+  return this.getSimplifiedGeometry(tolerance * tolerance);
 };
 
-goog.provide('ol.ViewHint');
 
 /**
- * @enum {number}
+ * 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.
  */
-ol.ViewHint = {
-  ANIMATING: 0,
-  INTERACTING: 1
-};
+ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {};
 
-goog.provide('ol.ViewProperty');
 
 /**
- * @enum {string}
+ * Get the type of this geometry.
+ * @abstract
+ * @return {ol.geom.GeometryType} Geometry type.
  */
-ol.ViewProperty = {
-  CENTER: 'center',
-  RESOLUTION: 'resolution',
-  ROTATION: 'rotation'
-};
-
-goog.provide('ol.string');
+ol.geom.Geometry.prototype.getType = function() {};
 
-/**
- * @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;
-};
 
 /**
- * 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
+ * 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.
  */
-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);
+ol.geom.Geometry.prototype.applyTransform = function(transformFn) {};
 
-    if (n1 > n2) {
-      return 1;
-    }
-    if (n2 > n1) {
-      return -1;
-    }
-  }
 
-  return 0;
-};
+/**
+ * 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) {};
 
-goog.provide('ol.coordinate');
 
-goog.require('ol.math');
-goog.require('ol.string');
+/**
+ * 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) {};
 
 
 /**
- * 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]
+ * 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.Coordinate} coordinate Coordinate.
- * @param {ol.Coordinate} delta Delta.
- * @return {ol.Coordinate} The input coordinate adjusted by the given delta.
+ * @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
  */
-ol.coordinate.add = function(coordinate, delta) {
-  coordinate[0] += delta[0];
-  coordinate[1] += delta[1];
-  return coordinate;
+ol.geom.Geometry.prototype.transform = function(source, destination) {
+  var tmpTransform = this.tmpTransform_;
+  source = ol.proj.get(source);
+  var transformFn = source.getUnits() == ol.proj.Units.TILE_PIXELS ?
+    function(inCoordinates, outCoordinates, stride) {
+      var pixelExtent = source.getExtent();
+      var projectedExtent = source.getWorldExtent();
+      var scale = ol.extent.getHeight(projectedExtent) / ol.extent.getHeight(pixelExtent);
+      ol.transform.compose(tmpTransform,
+          projectedExtent[0], projectedExtent[3],
+          scale, -scale, 0,
+          0, 0);
+      ol.geom.flat.transform.transform2D(inCoordinates, 0, inCoordinates.length, stride,
+          tmpTransform, outCoordinates);
+      return ol.proj.getTransform(source, destination)(inCoordinates, outCoordinates, stride);
+    } :
+    ol.proj.getTransform(source, destination);
+  this.applyTransform(transformFn);
+  return this;
 };
 
+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');
+
 
 /**
- * Calculates the point closest to the passed coordinate on the passed circle.
+ * @classdesc
+ * Abstract base class; only used for creating subclasses; do not instantiate
+ * in apps, as cannot be rendered.
  *
- * @param {ol.Coordinate} coordinate The coordinate.
- * @param {ol.geom.Circle} circle The circle.
- * @return {ol.Coordinate} Closest point on the circumference
+ * @constructor
+ * @abstract
+ * @extends {ol.geom.Geometry}
+ * @api
  */
-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];
+ol.geom.SimpleGeometry = function() {
 
-  var dx = x1 - x0;
-  var dy = y1 - y0;
-  if (dx === 0 && dy === 0) {
-    dx = 1;
-  }
-  var d = Math.sqrt(dx * dx + dy * dy);
+  ol.geom.Geometry.call(this);
 
-  var x, y;
+  /**
+   * @protected
+   * @type {ol.geom.GeometryLayout}
+   */
+  this.layout = ol.geom.GeometryLayout.XY;
 
-  x = x0 + r * dx / d;
-  y = y0 + r * dy / d;
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.stride = 2;
+
+  /**
+   * @protected
+   * @type {Array.<number>}
+   */
+  this.flatCoordinates = null;
 
-  return [x, y];
 };
+ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
 
 
 /**
- * Calculates the point closest to the passed coordinate on the passed segment.
- * This is the foot of the perpendicular of the coordinate to the segment when
- * the foot is on the segment, or the closest segment coordinate when the foot
- * is outside the segment.
- *
- * @param {ol.Coordinate} coordinate The coordinate.
- * @param {Array.<ol.Coordinate>} segment The two coordinates of the segment.
- * @return {ol.Coordinate} The foot of the perpendicular of the coordinate to
- *     the segment.
+ * @param {number} stride Stride.
+ * @private
+ * @return {ol.geom.GeometryLayout} layout Layout.
  */
-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.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 [x, y];
+  return /** @type {ol.geom.GeometryLayout} */ (layout);
 };
 
 
 /**
- * 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
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @return {number} Stride.
  */
-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.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);
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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);
+ol.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE;
 
-  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;
 
-  if (sec >= 60) {
-    sec = 0;
-    min += 1;
-  }
+/**
+ * @inheritDoc
+ */
+ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
+  return ol.extent.createOrUpdateFromFlatCoordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+      extent);
+};
 
-  if (min >= 60) {
-    min = 0;
-    deg += 1;
-  }
 
-  return deg + '\u00b0 ' + ol.string.padNumber(min, 2) + '\u2032 ' +
-    ol.string.padNumber(sec, 2, dflPrecision) + '\u2033' +
-    (normalizedDegrees == 0 ? '' : ' ' + hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0));
-};
+/**
+ * @abstract
+ * @return {Array} Coordinates.
+ */
+ol.geom.SimpleGeometry.prototype.getCoordinates = function() {};
 
 
 /**
- * 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.
+ * Return the first coordinate of the geometry.
+ * @return {ol.Coordinate} First coordinate.
  * @api
  */
-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.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
+  return this.flatCoordinates.slice(0, this.stride);
 };
 
 
 /**
- * @param {ol.Coordinate} coordinate1 First coordinate.
- * @param {ol.Coordinate} coordinate2 Second coordinate.
- * @return {boolean} Whether the passed coordinates are equal.
+ * @return {Array.<number>} Flat coordinates.
  */
-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.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
+  return this.flatCoordinates;
 };
 
 
 /**
- * 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.
+ * Return the last coordinate of the geometry.
+ * @return {ol.Coordinate} Last point.
  * @api
  */
-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.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
+  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
 };
 
 
 /**
- * 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.
+ * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
+ * @return {ol.geom.GeometryLayout} Layout.
+ * @api
  */
-ol.coordinate.scale = function(coordinate, scale) {
-  coordinate[0] *= scale;
-  coordinate[1] *= scale;
-  return coordinate;
+ol.geom.SimpleGeometry.prototype.getLayout = function() {
+  return this.layout;
 };
 
 
 /**
- * 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.
+ * @inheritDoc
  */
-ol.coordinate.sub = function(coordinate, delta) {
-  coordinate[0] -= delta[0];
-  coordinate[1] -= delta[1];
-  return coordinate;
+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 {
+    var simplifiedGeometry =
+        this.getSimplifiedGeometryInternal(squaredTolerance);
+    var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates();
+    if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) {
+      this.simplifiedGeometryCache[key] = simplifiedGeometry;
+      return simplifiedGeometry;
+    } else {
+      // Simplification did not actually remove any coordinates.  We now know
+      // that any calls to getSimplifiedGeometry with a squaredTolerance less
+      // than or equal to the current squaredTolerance will also not have any
+      // effect.  This allows us to short circuit simplification (saving CPU
+      // cycles) and prevents the cache of simplified geometries from filling
+      // up with useless identical copies of this geometry (saving memory).
+      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
+      return this;
+    }
+  }
 };
 
 
 /**
- * @param {ol.Coordinate} coord1 First coordinate.
- * @param {ol.Coordinate} coord2 Second coordinate.
- * @return {number} Squared distance between coord1 and coord2.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.SimpleGeometry} Simplified geometry.
+ * @protected
  */
-ol.coordinate.squaredDistance = function(coord1, coord2) {
-  var dx = coord1[0] - coord2[0];
-  var dy = coord1[1] - coord2[1];
-  return dx * dx + dy * dy;
+ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
+  return this;
 };
 
 
 /**
- * @param {ol.Coordinate} coord1 First coordinate.
- * @param {ol.Coordinate} coord2 Second coordinate.
- * @return {number} Distance between coord1 and coord2.
+ * @return {number} Stride.
  */
-ol.coordinate.distance = function(coord1, coord2) {
-  return Math.sqrt(ol.coordinate.squaredDistance(coord1, coord2));
+ol.geom.SimpleGeometry.prototype.getStride = function() {
+  return this.stride;
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @protected
  */
-ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
-  return ol.coordinate.squaredDistance(coordinate,
-      ol.coordinate.closestOnSegment(coordinate, segment));
+ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) {
+  this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
+  this.layout = layout;
+  this.flatCoordinates = flatCoordinates;
 };
 
 
 /**
- * Format a geographic coordinate with the hemisphere, degrees, minutes, and
- * seconds.
- *
- * 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
+ * @abstract
+ * @param {Array} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
  */
-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);
+ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {};
+
+
+/**
+ * @param {ol.geom.GeometryLayout|undefined} layout Layout.
+ * @param {Array} coordinates Coordinates.
+ * @param {number} nesting Nesting.
+ * @protected
+ */
+ol.geom.SimpleGeometry.prototype.setLayout = function(layout, coordinates, nesting) {
+  /** @type {number} */
+  var stride;
+  if (layout) {
+    stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
   } else {
-    return '';
+    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;
 };
 
 
 /**
- * Format a coordinate as a comma delimited string.
- *
- * Example without specifying fractional digits:
- *
- *     var coord = [7.85, 47.983333];
- *     var out = ol.coordinate.toStringXY(coord);
- *     // out is now '8, 48'
- *
- * Example explicitly specifying 1 fractional digit:
- *
- *     var coord = [7.85, 47.983333];
- *     var out = ol.coordinate.toStringXY(coord, 1);
- *     // out is now '7.8, 48.0'
- *
- * @param {ol.Coordinate|undefined} coordinate Coordinate.
- * @param {number=} opt_fractionDigits The number of digits to include
- *    after the decimal point. Default is `0`.
- * @return {string} XY.
+ * @inheritDoc
  * @api
  */
-ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
-  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
+ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
+  if (this.flatCoordinates) {
+    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
+    this.changed();
+  }
 };
 
-goog.provide('ol.geom.GeometryType');
-
 
 /**
- * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
- * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
- * `'GeometryCollection'`, `'Circle'`.
- * @enum {string}
+ * @inheritDoc
+ * @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.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();
+  }
 };
 
-goog.provide('ol.geom.GeometryLayout');
-
 
 /**
- * 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}
+ * @inheritDoc
+ * @api
  */
-ol.geom.GeometryLayout = {
-  XY: 'XY',
-  XYZ: 'XYZ',
-  XYM: 'XYM',
-  XYZM: 'XYZM'
+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();
+  }
 };
 
-goog.provide('ol.functions');
 
 /**
- * Always returns true.
- * @returns {boolean} true.
+ * @inheritDoc
+ * @api
  */
-ol.functions.TRUE = function() {
-  return true;
+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();
+  }
 };
 
+
 /**
- * Always returns false.
- * @returns {boolean} false.
+ * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
+ * @param {ol.Transform} transform Transform.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Transformed flat coordinates.
  */
-ol.functions.FALSE = function() {
-  return false;
+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);
+  }
 };
 
-goog.provide('ol.geom.Geometry');
-
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.extent');
-goog.require('ol.functions');
-goog.require('ol.proj');
+goog.provide('ol.geom.flat.area');
 
 
 /**
- * @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
- * @abstract
- * @extends {ol.Object}
- * @api
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-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;
+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;
+};
 
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.simplifiedGeometryRevision = 0;
 
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @return {number} Area.
+ */
+ol.geom.flat.area.linearRings = function(flatCoordinates, offset, ends, stride) {
+  var area = 0;
+  var i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    var end = ends[i];
+    area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride);
+    offset = end;
+  }
+  return area;
 };
-ol.inherits(ol.geom.Geometry, ol.Object);
 
 
 /**
- * Make a complete copy of the geometry.
- * @abstract
- * @return {!ol.geom.Geometry} Clone.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @return {number} Area.
  */
-ol.geom.Geometry.prototype.clone = function() {};
+ol.geom.flat.area.linearRingss = function(flatCoordinates, offset, endss, stride) {
+  var area = 0;
+  var i, ii;
+  for (i = 0, ii = endss.length; i < ii; ++i) {
+    var ends = endss[i];
+    area +=
+        ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride);
+    offset = ends[ends.length - 1];
+  }
+  return area;
+};
+
+goog.provide('ol.geom.flat.closest');
+
+goog.require('ol.math');
 
 
 /**
- * @abstract
+ * 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 {ol.Coordinate} closestPoint Closest point.
- * @param {number} minSquaredDistance Minimum squared distance.
- * @return {number} Minimum squared distance.
+ * @param {Array.<number>} closestPoint Closest point.
  */
-ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {};
+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;
+    }
+  }
+  for (i = 0; i < stride; ++i) {
+    closestPoint[i] = flatCoordinates[offset + i];
+  }
+  closestPoint.length = stride;
+};
 
 
 /**
- * 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
+ * Return the squared of the largest distance between any pair of consecutive
+ * coordinates.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} maxSquaredDelta Max squared delta.
+ * @return {number} Max squared delta.
  */
-ol.geom.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.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;
 };
 
 
 /**
- * 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
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @param {number} maxSquaredDelta Max squared delta.
+ * @return {number} Max squared delta.
  */
-ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) {
-  return this.containsXY(coordinate[0], coordinate[1]);
+ol.geom.flat.closest.getsMaxSquaredDelta = function(flatCoordinates, offset, ends, stride, maxSquaredDelta) {
+  var i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    var end = ends[i];
+    maxSquaredDelta = ol.geom.flat.closest.getMaxSquaredDelta(
+        flatCoordinates, offset, end, stride, maxSquaredDelta);
+    offset = end;
+  }
+  return maxSquaredDelta;
 };
 
 
 /**
- * @abstract
- * @param {ol.Extent} extent Extent.
- * @protected
- * @return {ol.Extent} extent Extent.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @param {number} maxSquaredDelta Max squared delta.
+ * @return {number} Max squared delta.
  */
-ol.geom.Geometry.prototype.computeExtent = function(extent) {};
+ol.geom.flat.closest.getssMaxSquaredDelta = function(flatCoordinates, offset, endss, stride, maxSquaredDelta) {
+  var i, ii;
+  for (i = 0, ii = endss.length; i < ii; ++i) {
+    var ends = endss[i];
+    maxSquaredDelta = ol.geom.flat.closest.getsMaxSquaredDelta(
+        flatCoordinates, offset, ends, stride, maxSquaredDelta);
+    offset = ends[ends.length - 1];
+  }
+  return maxSquaredDelta;
+};
 
 
 /**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} maxDelta Max delta.
+ * @param {boolean} isRing Is ring.
  * @param {number} x X.
  * @param {number} y Y.
- * @return {boolean} Contains (x, y).
- */
-ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE;
-
-
-/**
- * Get the extent of the geometry.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} extent Extent.
- * @api
+ * @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.Geometry.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision_ != this.getRevision()) {
-    this.extent_ = this.computeExtent(this.extent_);
-    this.extentRevision_ = this.getRevision();
+ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end,
+    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
+    opt_tmpPoint) {
+  if (offset == end) {
+    return minSquaredDistance;
   }
-  return ol.extent.returnOrUpdate(this.extent_, opt_extent);
-};
-
-
-/**
- * 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
- */
-ol.geom.Geometry.prototype.rotate = function(angle, anchor) {};
-
-
-/**
- * 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
- */
-ol.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {};
-
-
-/**
- * Create a simplified version of this geometry.  For linestrings, this uses
- * the the {@link
- * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
- * Douglas Peucker} algorithm.  For polygons, a quantization-based
- * simplification is used to preserve topology.
- * @function
- * @param {number} tolerance The tolerance distance for simplification.
- * @return {ol.geom.Geometry} A new, simplified version of the original
- *     geometry.
- * @api
- */
-ol.geom.Geometry.prototype.simplify = function(tolerance) {
-  return this.getSimplifiedGeometry(tolerance * tolerance);
+  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;
 };
 
 
 /**
- * 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.
- */
-ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {};
-
-
-/**
- * Get the type of this geometry.
- * @abstract
- * @return {ol.geom.GeometryType} Geometry type.
- */
-ol.geom.Geometry.prototype.getType = function() {};
-
-
-/**
- * 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.
- */
-ol.geom.Geometry.prototype.applyTransform = function(transformFn) {};
-
-
-/**
- * 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) {};
-
-
-/**
- * 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.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @param {number} maxDelta Max delta.
+ * @param {boolean} isRing Is ring.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {Array.<number>} closestPoint Closest point.
+ * @param {number} minSquaredDistance Minimum squared distance.
+ * @param {Array.<number>=} opt_tmpPoint Temporary point object.
+ * @return {number} Minimum squared distance.
  */
-ol.geom.Geometry.prototype.translate = function(deltaX, deltaY) {};
+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;
+};
 
 
 /**
- * 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.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
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @param {number} maxDelta Max delta.
+ * @param {boolean} isRing Is ring.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {Array.<number>} closestPoint Closest point.
+ * @param {number} minSquaredDistance Minimum squared distance.
+ * @param {Array.<number>=} opt_tmpPoint Temporary point object.
+ * @return {number} Minimum squared distance.
  */
-ol.geom.Geometry.prototype.transform = function(source, destination) {
-  this.applyTransform(ol.proj.getTransform(source, destination));
-  return this;
+ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset,
+    endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
+    opt_tmpPoint) {
+  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
+  var i, ii;
+  for (i = 0, ii = endss.length; i < ii; ++i) {
+    var ends = endss[i];
+    minSquaredDistance = ol.geom.flat.closest.getsClosestPoint(
+        flatCoordinates, offset, ends, stride,
+        maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint);
+    offset = ends[ends.length - 1];
+  }
+  return minSquaredDistance;
 };
 
-goog.provide('ol.geom.flat.transform');
+goog.provide('ol.geom.flat.deflate');
 
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
- * @param {number} end End.
+ * @param {ol.Coordinate} coordinate Coordinate.
  * @param {number} stride Stride.
- * @param {ol.Transform} transform Transform.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed coordinates.
+ * @return {number} offset Offset.
  */
-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;
+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 dest;
+  return offset;
 };
 
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
- * @param {number} end End.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
  * @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.
+ * @return {number} offset Offset.
  */
-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];
+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];
     }
   }
-  if (opt_dest && dest.length != i) {
-    dest.length = i;
-  }
-  return dest;
+  return offset;
 };
 
 
 /**
- * Scale the coordinates.
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
- * @param {number} end End.
+ * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess.
  * @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.
+ * @param {Array.<number>=} opt_ends Ends.
+ * @return {Array.<number>} Ends.
  */
-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];
+ol.geom.flat.deflate.coordinatess = function(flatCoordinates, offset, coordinatess, stride, opt_ends) {
+  var ends = opt_ends ? opt_ends : [];
   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;
+  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;
   }
-  return dest;
+  ends.length = i;
+  return ends;
 };
 
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
- * @param {number} end End.
+ * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinatesss Coordinatesss.
  * @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 {Array.<Array.<number>>=} opt_endss Endss.
+ * @return {Array.<Array.<number>>} Endss.
  */
-ol.geom.flat.transform.translate = function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) {
-  var dest = opt_dest ? opt_dest : [];
+ol.geom.flat.deflate.coordinatesss = function(flatCoordinates, offset, coordinatesss, stride, opt_endss) {
+  var endss = opt_endss ? opt_endss : [];
   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;
+  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 dest;
+  endss.length = i;
+  return endss;
 };
 
-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');
+goog.provide('ol.geom.flat.inflate');
 
 
 /**
- * @classdesc
- * Abstract base class; only used for creating subclasses; do not instantiate
- * in apps, as cannot be rendered.
- *
- * @constructor
- * @abstract
- * @extends {ol.geom.Geometry}
- * @api
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {Array.<ol.Coordinate>=} opt_coordinates Coordinates.
+ * @return {Array.<ol.Coordinate>} Coordinates.
  */
-ol.geom.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.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;
 };
-ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
 
 
 /**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
  * @param {number} stride Stride.
- * @private
- * @return {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess.
+ * @return {Array.<Array.<ol.Coordinate>>} Coordinatess.
  */
-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;
+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;
   }
-  return /** @type {ol.geom.GeometryLayout} */ (layout);
+  coordinatess.length = i;
+  return coordinatess;
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @return {number} Stride.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss
+ *     Coordinatesss.
+ * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss.
  */
-ol.geom.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;
+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 /** @type {number} */ (stride);
+  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.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE;
+goog.require('ol.math');
 
 
 /**
- * @inheritDoc
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {boolean} highQuality Highest quality.
+ * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat
+ *     coordinates.
+ * @return {Array.<number>} Simplified line string.
  */
-ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
-  return ol.extent.createOrUpdateFromFlatCoordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      extent);
+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;
 };
 
 
 /**
- * @abstract
- * @return {Array} Coordinates.
- */
-ol.geom.SimpleGeometry.prototype.getCoordinates = function() {};
-
-
-/**
- * Return the first coordinate of the geometry.
- * @return {ol.Coordinate} First coordinate.
- * @api
- */
-ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
-  return this.flatCoordinates.slice(0, this.stride);
-};
-
-
-/**
- * @return {Array.<number>} Flat coordinates.
- */
-ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
-  return this.flatCoordinates;
-};
-
-
-/**
- * Return the last coordinate of the geometry.
- * @return {ol.Coordinate} Last point.
- * @api
- */
-ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
-  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
-};
-
-
-/**
- * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
- * @return {ol.geom.GeometryLayout} Layout.
- * @api
- */
-ol.geom.SimpleGeometry.prototype.getLayout = function() {
-  return this.layout;
-};
-
-
-/**
- * @inheritDoc
+ * @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.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 {
-    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.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;
   }
-};
-
-
-/**
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.SimpleGeometry} Simplified geometry.
- * @protected
- */
-ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
-  return this;
-};
-
-
-/**
- * @return {number} Stride.
- */
-ol.geom.SimpleGeometry.prototype.getStride = function() {
-  return this.stride;
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @protected
- */
-ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) {
-  this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
-  this.layout = layout;
-  this.flatCoordinates = flatCoordinates;
-};
-
-
-/**
- * @abstract
- * @param {Array} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- */
-ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {};
-
-
-/**
- * @param {ol.geom.GeometryLayout|undefined} layout Layout.
- * @param {Array} coordinates Coordinates.
- * @param {number} nesting Nesting.
- * @protected
- */
-ol.geom.SimpleGeometry.prototype.setLayout = function(layout, coordinates, nesting) {
-  /** @type {number} */
-  var stride;
-  if (layout) {
-    stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
-  } else {
-    var i;
-    for (i = 0; i < nesting; ++i) {
-      if (coordinates.length === 0) {
-        this.layout = ol.geom.GeometryLayout.XY;
-        this.stride = 2;
-        return;
-      } else {
-        coordinates = /** @type {Array} */ (coordinates[0]);
+  /** @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);
       }
     }
-    stride = coordinates.length;
-    layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride);
   }
-  this.layout = layout;
-  this.stride = stride;
+  for (i = 0; i < n; ++i) {
+    if (markers[i]) {
+      simplifiedFlatCoordinates[simplifiedOffset++] =
+          flatCoordinates[offset + i * stride];
+      simplifiedFlatCoordinates[simplifiedOffset++] =
+          flatCoordinates[offset + i * stride + 1];
+    }
+  }
+  return simplifiedOffset;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * @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.SimpleGeometry.prototype.applyTransform = function(transformFn) {
-  if (this.flatCoordinates) {
-    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
-    this.changed();
+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;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * @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.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();
+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;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * @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.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());
+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 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();
+  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;
+    }
   }
-};
-
-
-/**
- * @inheritDoc
- * @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();
+  if (x2 != x1 || y2 != y1) {
+    // copy last point
+    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
+    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
   }
+  return simplifiedOffset;
 };
 
 
 /**
- * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
- * @param {ol.Transform} transform Transform.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed flat coordinates.
+ * @param {number} value Value.
+ * @param {number} tolerance Tolerance.
+ * @return {number} Rounded value.
  */
-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);
-  }
+ol.geom.flat.simplify.snap = function(value, tolerance) {
+  return tolerance * Math.round(value / tolerance);
 };
 
-goog.provide('ol.geom.flat.area');
-
 
 /**
+ * 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.
- * @return {number} Area.
+ * @param {number} tolerance Tolerance.
+ * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
+ *     coordinates.
+ * @param {number} simplifiedOffset Simplified offset.
+ * @return {number} Simplified offset.
  */
-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;
+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;
   }
-  return twiceArea / 2;
+  // add the last point (P2)
+  simplifiedFlatCoordinates[simplifiedOffset++] = x2;
+  simplifiedFlatCoordinates[simplifiedOffset++] = y2;
+  return simplifiedOffset;
 };
 
 
@@ -14675,17 +14250,28 @@ ol.geom.flat.area.linearRing = function(flatCoordinates, offset, end, stride) {
  * @param {number} offset Offset.
  * @param {Array.<number>} ends Ends.
  * @param {number} stride Stride.
- * @return {number} Area.
+ * @param {number} tolerance Tolerance.
+ * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
+ *     coordinates.
+ * @param {number} simplifiedOffset Simplified offset.
+ * @param {Array.<number>} simplifiedEnds Simplified ends.
+ * @return {number} Simplified offset.
  */
-ol.geom.flat.area.linearRings = function(flatCoordinates, offset, ends, stride) {
-  var area = 0;
+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];
-    area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride);
+    simplifiedOffset = ol.geom.flat.simplify.quantize(
+        flatCoordinates, offset, end, stride,
+        tolerance,
+        simplifiedFlatCoordinates, simplifiedOffset);
+    simplifiedEnds.push(simplifiedOffset);
     offset = end;
   }
-  return area;
+  return simplifiedOffset;
 };
 
 
@@ -14694,584 +14280,510 @@ ol.geom.flat.area.linearRings = function(flatCoordinates, offset, ends, stride)
  * @param {number} offset Offset.
  * @param {Array.<Array.<number>>} endss Endss.
  * @param {number} stride Stride.
- * @return {number} Area.
+ * @param {number} tolerance Tolerance.
+ * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
+ *     coordinates.
+ * @param {number} simplifiedOffset Simplified offset.
+ * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
+ * @return {number} Simplified offset.
  */
-ol.geom.flat.area.linearRingss = function(flatCoordinates, offset, endss, stride) {
-  var area = 0;
+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];
-    area +=
-        ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride);
+    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 area;
+  return simplifiedOffset;
 };
 
-goog.provide('ol.geom.flat.closest');
+goog.provide('ol.geom.LinearRing');
 
-goog.require('ol.math');
+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');
 
 
 /**
- * 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.
+ * @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.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;
-    }
+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);
+
+
+/**
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.LinearRing} Clone.
+ * @override
+ * @api
+ */
+ol.geom.LinearRing.prototype.clone = function() {
+  var linearRing = new ol.geom.LinearRing(null);
+  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return linearRing;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.LinearRing.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
+  if (minSquaredDistance <
+      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
+    return minSquaredDistance;
   }
-  for (i = 0; i < stride; ++i) {
-    closestPoint[i] = flatCoordinates[offset + 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();
   }
-  closestPoint.length = stride;
+  return ol.geom.flat.closest.getClosestPoint(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
 };
 
 
 /**
- * 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.
+ * Return the area of the linear ring on projected plane.
+ * @return {number} Area (on projected plane).
+ * @api
  */
-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.geom.LinearRing.prototype.getArea = function() {
+  return ol.geom.flat.area.linearRing(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * @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.
+ * Return the coordinates of the linear ring.
+ * @return {Array.<ol.Coordinate>} Coordinates.
+ * @override
+ * @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;
-  }
-  return maxSquaredDelta;
+ol.geom.LinearRing.prototype.getCoordinates = function() {
+  return ol.geom.flat.inflate.coordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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.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 {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.
+ * @inheritDoc
+ * @api
  */
-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;
+ol.geom.LinearRing.prototype.getType = function() {
+  return ol.geom.GeometryType.LINEAR_RING;
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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;
+ol.geom.LinearRing.prototype.intersectsExtent = function(extent) {};
+
+
+/**
+ * Set the coordinates of the linear ring.
+ * @param {Array.<ol.Coordinate>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @api
+ */
+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 minSquaredDistance;
 };
 
 
 /**
+ * @param {ol.geom.GeometryLayout} layout Layout.
  * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {number} maxDelta Max delta.
- * @param {boolean} isRing Is ring.
- * @param {number} x X.
- * @param {number} y Y.
- * @param {Array.<number>} closestPoint Closest point.
- * @param {number} minSquaredDistance Minimum squared distance.
- * @param {Array.<number>=} opt_tmpPoint Temporary point object.
- * @return {number} Minimum squared distance.
  */
-ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset,
-    endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
-    opt_tmpPoint) {
-  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    minSquaredDistance = ol.geom.flat.closest.getsClosestPoint(
-        flatCoordinates, offset, ends, stride,
-        maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint);
-    offset = ends[ends.length - 1];
-  }
-  return minSquaredDistance;
+ol.geom.LinearRing.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
-goog.provide('ol.geom.flat.deflate');
+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');
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} stride Stride.
- * @return {number} offset Offset.
+ * @classdesc
+ * Point geometry.
+ *
+ * @constructor
+ * @extends {ol.geom.SimpleGeometry}
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @api
  */
-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;
+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);
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {number} stride Stride.
- * @return {number} offset Offset.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Point} Clone.
+ * @override
+ * @api
  */
-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];
+ol.geom.Point.prototype.clone = function() {
+  var point = new ol.geom.Point(null);
+  point.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return point;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.Point.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
+  var flatCoordinates = this.flatCoordinates;
+  var squaredDistance = ol.math.squaredDistance(
+      x, y, flatCoordinates[0], flatCoordinates[1]);
+  if (squaredDistance < minSquaredDistance) {
+    var stride = this.stride;
+    var i;
+    for (i = 0; i < stride; ++i) {
+      closestPoint[i] = flatCoordinates[i];
     }
+    closestPoint.length = stride;
+    return squaredDistance;
+  } else {
+    return minSquaredDistance;
   }
-  return 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.
+ * Return the coordinate of the point.
+ * @return {ol.Coordinate} Coordinates.
+ * @override
+ * @api
  */
-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.geom.Point.prototype.getCoordinates = function() {
+  return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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.geom.Point.prototype.computeExtent = function(extent) {
+  return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
 };
 
-goog.provide('ol.geom.flat.inflate');
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.geom.Point.prototype.getType = function() {
+  return ol.geom.GeometryType.POINT;
+};
 
 
 /**
- * @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.
+ * @inheritDoc
+ * @api
  */
-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.geom.Point.prototype.intersectsExtent = function(extent) {
+  return ol.extent.containsXY(extent,
+      this.flatCoordinates[0], this.flatCoordinates[1]);
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+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();
   }
-  coordinates.length = i;
-  return coordinates;
 };
 
 
 /**
+ * @param {ol.geom.GeometryLayout} layout Layout.
  * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess.
- * @return {Array.<Array.<ol.Coordinate>>} Coordinatess.
  */
-ol.geom.flat.inflate.coordinatess = function(flatCoordinates, offset, ends, stride, opt_coordinatess) {
-  var coordinatess = opt_coordinatess !== undefined ? opt_coordinatess : [];
-  var i = 0;
-  var j, jj;
-  for (j = 0, jj = ends.length; j < jj; ++j) {
-    var end = ends[j];
-    coordinatess[i++] = ol.geom.flat.inflate.coordinates(
-        flatCoordinates, offset, end, stride, coordinatess[i]);
-    offset = end;
-  }
-  coordinatess.length = i;
-  return coordinatess;
+ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
 };
 
+goog.provide('ol.geom.flat.contains');
+
+goog.require('ol.extent');
+
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} end End.
  * @param {number} stride Stride.
- * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss
- *     Coordinatesss.
- * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss.
+ * @param {ol.Extent} extent Extent.
+ * @return {boolean} Contains extent.
  */
-ol.geom.flat.inflate.coordinatesss = function(flatCoordinates, offset, endss, stride, opt_coordinatesss) {
-  var coordinatesss = opt_coordinatesss !== undefined ? opt_coordinatesss : [];
-  var i = 0;
-  var j, jj;
-  for (j = 0, jj = endss.length; j < jj; ++j) {
-    var ends = endss[j];
-    coordinatesss[i++] = ol.geom.flat.inflate.coordinatess(
-        flatCoordinates, offset, ends, stride, coordinatesss[i]);
-    offset = ends[ends.length - 1];
-  }
-  coordinatesss.length = i;
-  return coordinatesss;
+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;
 };
 
-// Based on simplify-js https://github.com/mourner/simplify-js
-// Copyright (c) 2012, Vladimir Agafonkin
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-//    1. Redistributions of source code must retain the above copyright notice,
-//       this list of conditions and the following disclaimer.
-//
-//    2. Redistributions in binary form must reproduce the above copyright
-//       notice, this list of conditions and the following disclaimer in the
-//       documentation and/or other materials provided with the distribution.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.geom.flat.simplify');
-
-goog.require('ol.math');
-
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
  * @param {number} end End.
  * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {boolean} highQuality Highest quality.
- * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @return {Array.<number>} Simplified line string.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {boolean} Contains (x, y).
  */
-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;
+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;
   }
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
-      flatCoordinates, offset, end, stride, squaredTolerance,
-      simplifiedFlatCoordinates, 0);
-  return simplifiedFlatCoordinates;
+  return wn !== 0;
 };
 
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
- * @param {number} end End.
+ * @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.
- * @return {number} Simplified offset.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {boolean} Contains (x, y).
  */
-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;
+ol.geom.flat.contains.linearRingsContainsXY = function(flatCoordinates, offset, ends, stride, x, y) {
+  if (ends.length === 0) {
+    return false;
   }
-  /** @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 (!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;
     }
   }
-  for (i = 0; i < n; ++i) {
-    if (markers[i]) {
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + i * stride];
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + i * stride + 1];
+  return true;
+};
+
+
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} stride Stride.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @return {boolean} Contains (x, y).
+ */
+ol.geom.flat.contains.linearRingssContainsXY = function(flatCoordinates, offset, endss, stride, x, y) {
+  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 simplifiedOffset;
+  return false;
 };
 
+goog.provide('ol.geom.flat.interiorpoint');
+
+goog.require('ol.array');
+goog.require('ol.geom.flat.contains');
+
 
 /**
+ * Calculates a point that is likely to lie in the interior of the linear rings.
+ * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint.
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
  * @param {Array.<number>} ends Ends.
  * @param {number} stride Stride.
- * @param {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.
+ * @param {Array.<number>} flatCenters Flat centers.
+ * @param {number} flatCentersOffset Flat center offset.
+ * @param {Array.<number>=} opt_dest Destination.
+ * @return {Array.<number>} Destination point as XYM coordinate, where M is the
+ * length of the horizontal intersection that the point belongs to.
  */
-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;
+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
+  for (var r = 0, rr = ends.length; r < rr; ++r) {
+    var end = ends[r];
+    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, maxSegmentLength);
+    return opt_dest;
+  } else {
+    return [pointX, y, maxSegmentLength];
   }
-  return simplifiedOffset;
 };
 
 
@@ -15280,179 +14792,99 @@ ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset,
  * @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.
+ * @param {Array.<number>} flatCenters Flat centers.
+ * @return {Array.<number>} Interior points as XYM coordinates, where M is the
+ * length of the horizontal intersection that the point belongs to.
  */
-ol.geom.flat.simplify.douglasPeuckerss = function(
-    flatCoordinates, offset, endss, stride, squaredTolerance,
-    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
+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];
-    var simplifiedEnds = [];
-    simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers(
-        flatCoordinates, offset, ends, stride, squaredTolerance,
-        simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
-    simplifiedEndss.push(simplifiedEnds);
+    interiorPoints = ol.geom.flat.interiorpoint.linearRings(flatCoordinates,
+        offset, ends, stride, flatCenters, 2 * i, interiorPoints);
     offset = ends[ends.length - 1];
   }
-  return simplifiedOffset;
+  return interiorPoints;
 };
 
+goog.provide('ol.geom.flat.segments');
+
 
 /**
+ * This function calls `callback` for each segment of the flat coordinates
+ * array. If the callback returns a truthy value the function returns that
+ * value immediately. Otherwise the function returns `false`.
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
  * @param {number} end End.
  * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @return {number} Simplified offset.
+ * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function
+ *     called for each segment.
+ * @param {S=} opt_this The object to be used as the value of 'this'
+ *     within callback.
+ * @return {T|boolean} Value.
+ * @template T,S
  */
-ol.geom.flat.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;
+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];
   }
-  if (x2 != x1 || y2 != y1) {
-    // copy last point
-    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
-  }
-  return simplifiedOffset;
+  return false;
 };
 
+goog.provide('ol.geom.flat.intersectsextent');
 
-/**
- * @param {number} value Value.
- * @param {number} tolerance Tolerance.
- * @return {number} Rounded value.
- */
-ol.geom.flat.simplify.snap = function(value, tolerance) {
-  return tolerance * Math.round(value / tolerance);
-};
+goog.require('ol.extent');
+goog.require('ol.geom.flat.contains');
+goog.require('ol.geom.flat.segments');
 
 
 /**
- * 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.Extent} extent Extent.
+ * @return {boolean} True if the geometry and the extent intersect.
  */
-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.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;
   }
-  // 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;
+  if (ol.extent.containsExtent(extent, coordinatesExtent)) {
+    return true;
   }
-  // add the last point (P2)
-  simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-  simplifiedFlatCoordinates[simplifiedOffset++] = y2;
-  return simplifiedOffset;
+  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);
+      });
 };
 
 
@@ -15461,2349 +14893,1754 @@ ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride,
  * @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.Extent} extent Extent.
+ * @return {boolean} True if the geometry and the extent intersect.
  */
-ol.geom.flat.simplify.quantizes = function(
-    flatCoordinates, offset, ends, stride,
-    tolerance,
-    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) {
+ol.geom.flat.intersectsextent.lineStrings = function(flatCoordinates, offset, ends, stride, extent) {
   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;
+    if (ol.geom.flat.intersectsextent.lineString(
+        flatCoordinates, offset, ends[i], stride, extent)) {
+      return true;
+    }
+    offset = ends[i];
   }
-  return simplifiedOffset;
+  return false;
 };
 
 
 /**
  * @param {Array.<number>} flatCoordinates Flat coordinates.
  * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
+ * @param {number} end End.
  * @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.
+ * @param {ol.Extent} extent Extent.
+ * @return {boolean} True if the geometry and the extent intersect.
  */
-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.geom.flat.intersectsextent.linearRing = function(flatCoordinates, offset, end, stride, extent) {
+  if (ol.geom.flat.intersectsextent.lineString(
+      flatCoordinates, offset, end, stride, extent)) {
+    return true;
   }
-  return simplifiedOffset;
+  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;
 };
 
-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');
-
 
 /**
- * @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
+ * @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.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.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;
 };
-ol.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry);
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.LinearRing} Clone.
- * @override
- * @api
+ * @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.LinearRing.prototype.clone = function() {
-  var linearRing = new ol.geom.LinearRing(null);
-  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return linearRing;
+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;
 };
 
+goog.provide('ol.geom.flat.reverse');
+
 
 /**
- * @inheritDoc
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  */
-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.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;
   }
-  return ol.geom.flat.closest.getClosestPoint(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
 };
 
+goog.provide('ol.geom.flat.orient');
 
-/**
- * Return the area of the linear ring on projected plane.
- * @return {number} Area (on projected plane).
- * @api
- */
-ol.geom.LinearRing.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRing(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
+goog.require('ol.geom.flat.reverse');
 
 
 /**
- * Return the coordinates of the linear ring.
- * @return {Array.<ol.Coordinate>} Coordinates.
- * @override
- * @api
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {boolean} Is clockwise.
  */
-ol.geom.LinearRing.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
+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;
 };
 
 
 /**
- * @inheritDoc
+ * Determines if linear rings are oriented.  By default, left-hand orientation
+ * is tested (first ring must be clockwise, remaining rings counter-clockwise).
+ * To test for right-hand orientation, use the `opt_right` argument.
+ *
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Array of end indexes.
+ * @param {number} stride Stride.
+ * @param {boolean=} opt_right Test for right-hand orientation
+ *     (counter-clockwise exterior ring and clockwise interior rings).
+ * @return {boolean} Rings are correctly oriented.
  */
-ol.geom.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.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;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * Determines if linear rings are oriented.  By default, left-hand orientation
+ * is tested (first ring must be clockwise, remaining rings counter-clockwise).
+ * To test for right-hand orientation, use the `opt_right` argument.
+ *
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Array of array of end indexes.
+ * @param {number} stride Stride.
+ * @param {boolean=} opt_right Test for right-hand orientation
+ *     (counter-clockwise exterior ring and clockwise interior rings).
+ * @return {boolean} Rings are correctly oriented.
  */
-ol.geom.LinearRing.prototype.getType = function() {
-  return ol.geom.GeometryType.LINEAR_RING;
+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;
 };
 
 
 /**
- * @inheritDoc
- */
-ol.geom.LinearRing.prototype.intersectsExtent = function(extent) {};
-
-
-/**
- * Set the coordinates of the linear ring.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @override
- * @api
+ * Orient coordinates in a flat array of linear rings.  By default, rings
+ * are oriented following the left-hand rule (clockwise for exterior and
+ * counter-clockwise for interior rings).  To orient according to the
+ * right-hand rule, use the `opt_right` argument.
+ *
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @param {boolean=} opt_right Follow the right-hand rule for orientation.
+ * @return {number} End.
  */
-ol.geom.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 = [];
+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);
     }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.changed();
+    offset = end;
   }
+  return offset;
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
+ * Orient coordinates in a flat array of linear rings.  By default, rings
+ * are oriented following the left-hand rule (clockwise for exterior and
+ * counter-clockwise for interior rings).  To orient according to the
+ * right-hand rule, use the `opt_right` argument.
+ *
  * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<Array.<number>>} endss Array of array of end indexes.
+ * @param {number} stride Stride.
+ * @param {boolean=} opt_right Follow the right-hand rule for orientation.
+ * @return {number} End.
  */
-ol.geom.LinearRing.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
+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.Point');
+goog.provide('ol.geom.Polygon');
 
 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');
 
 
 /**
  * @classdesc
- * Point geometry.
+ * Polygon geometry.
  *
  * @constructor
  * @extends {ol.geom.SimpleGeometry}
- * @param {ol.Coordinate} coordinates Coordinates.
+ * @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
  */
-ol.geom.Point = function(coordinates, opt_layout) {
+ol.geom.Polygon = function(coordinates, opt_layout) {
+
   ol.geom.SimpleGeometry.call(this);
-  this.setCoordinates(coordinates, opt_layout);
-};
-ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
 
+  /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.ends_ = [];
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.flatInteriorPointRevision_ = -1;
+
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.flatInteriorPoint_ = null;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDelta_ = -1;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxDeltaRevision_ = -1;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.orientedRevision_ = -1;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.orientedFlatCoordinates_ = null;
+
+  this.setCoordinates(coordinates, opt_layout);
 
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Point} Clone.
- * @override
- * @api
- */
-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.geom.Polygon, ol.geom.SimpleGeometry);
 
 
 /**
- * @inheritDoc
+ * Append the passed linear ring to this polygon.
+ * @param {ol.geom.LinearRing} linearRing Linear ring.
+ * @api
  */
-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;
+ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
+  if (!this.flatCoordinates) {
+    this.flatCoordinates = linearRing.getFlatCoordinates().slice();
   } else {
-    return minSquaredDistance;
+    ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
   }
+  this.ends_.push(this.flatCoordinates.length);
+  this.changed();
 };
 
 
 /**
- * Return the coordinate of the point.
- * @return {ol.Coordinate} Coordinates.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Polygon} Clone.
  * @override
  * @api
  */
-ol.geom.Point.prototype.getCoordinates = function() {
-  return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
+ol.geom.Polygon.prototype.clone = function() {
+  var polygon = new ol.geom.Polygon(null);
+  polygon.setFlatCoordinates(
+      this.layout, this.flatCoordinates.slice(), this.ends_.slice());
+  return polygon;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.geom.Point.prototype.computeExtent = function(extent) {
-  return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
+ol.geom.Polygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
+  if (minSquaredDistance <
+      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
+    return minSquaredDistance;
+  }
+  if (this.maxDeltaRevision_ != this.getRevision()) {
+    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta(
+        this.flatCoordinates, 0, this.ends_, this.stride, 0));
+    this.maxDeltaRevision_ = this.getRevision();
+  }
+  return ol.geom.flat.closest.getsClosestPoint(
+      this.flatCoordinates, 0, this.ends_, this.stride,
+      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
 };
 
 
 /**
  * @inheritDoc
- * @api
  */
-ol.geom.Point.prototype.getType = function() {
-  return ol.geom.GeometryType.POINT;
+ol.geom.Polygon.prototype.containsXY = function(x, y) {
+  return ol.geom.flat.contains.linearRingsContainsXY(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
 };
 
 
 /**
- * @inheritDoc
+ * Return the area of the polygon on projected plane.
+ * @return {number} Area (on projected plane).
  * @api
  */
-ol.geom.Point.prototype.intersectsExtent = function(extent) {
-  return ol.extent.containsXY(extent,
-      this.flatCoordinates[0], this.flatCoordinates[1]);
+ol.geom.Polygon.prototype.getArea = function() {
+  return ol.geom.flat.area.linearRings(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
 };
 
 
 /**
- * @inheritDoc
+ * 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.
+ * @override
  * @api
  */
-ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
+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 {
-    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();
+    flatCoordinates = this.flatCoordinates;
   }
+
+  return ol.geom.flat.inflate.coordinatess(
+      flatCoordinates, 0, this.ends_, this.stride);
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @return {Array.<number>} Ends.
  */
-ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
+ol.geom.Polygon.prototype.getEnds = function() {
+  return this.ends_;
 };
 
-goog.provide('ol.geom.flat.contains');
 
-goog.require('ol.extent');
+/**
+ * @return {Array.<number>} Interior point.
+ */
+ol.geom.Polygon.prototype.getFlatInteriorPoint = function() {
+  if (this.flatInteriorPointRevision_ != this.getRevision()) {
+    var flatCenter = ol.extent.getCenter(this.getExtent());
+    this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings(
+        this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride,
+        flatCenter, 0);
+    this.flatInteriorPointRevision_ = this.getRevision();
+  }
+  return this.flatInteriorPoint_;
+};
 
 
 /**
- * @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.
+ * Return an interior point of the polygon.
+ * @return {ol.geom.Point} Interior point as XYM coordinate, where M is the
+ * length of the horizontal intersection that the point belongs to.
+ * @api
  */
-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;
+ol.geom.Polygon.prototype.getInteriorPoint = function() {
+  return new ol.geom.Point(this.getFlatInteriorPoint(), ol.geom.GeometryLayout.XYM);
 };
 
 
 /**
- * @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).
+ * Return the number of rings of the polygon,  this includes the exterior
+ * ring and any interior rings.
+ *
+ * @return {number} Number of rings.
+ * @api
  */
-ol.geom.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;
+ol.geom.Polygon.prototype.getLinearRingCount = function() {
+  return this.ends_.length;
 };
 
 
 /**
- * @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).
+ * 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
  */
-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;
-    }
+ol.geom.Polygon.prototype.getLinearRing = function(index) {
+  if (index < 0 || this.ends_.length <= index) {
+    return null;
   }
-  return true;
+  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;
 };
 
 
 /**
- * @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).
+ * Return the linear rings of the polygon.
+ * @return {Array.<ol.geom.LinearRing>} Linear rings.
+ * @api
  */
-ol.geom.flat.contains.linearRingssContainsXY = function(flatCoordinates, offset, endss, stride, x, y) {
-  if (endss.length === 0) {
-    return false;
-  }
+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 = 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];
+  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 false;
+  return linearRings;
 };
 
-goog.provide('ol.geom.flat.interiorpoint');
-
-goog.require('ol.array');
-goog.require('ol.geom.flat.contains');
-
 
 /**
- * Calculates a point that is likely to lie in the interior of the linear rings.
- * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {Array.<number>} flatCenters Flat centers.
- * @param {number} flatCentersOffset Flat center offset.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Destination.
+ * @return {Array.<number>} Oriented flat coordinates.
  */
-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;
-      }
+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);
     }
-    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];
+    this.orientedRevision_ = this.getRevision();
   }
+  return this.orientedFlatCoordinates_;
 };
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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;
+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;
 };
 
-goog.provide('ol.geom.flat.segments');
-
 
 /**
- * This function calls `callback` for each segment of the flat coordinates
- * array. If the callback returns a truthy value the function returns that
- * value immediately. Otherwise the function returns `false`.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function
- *     called for each segment.
- * @param {S=} opt_this The object to be used as the value of 'this'
- *     within callback.
- * @return {T|boolean} Value.
- * @template T,S
+ * @inheritDoc
+ * @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;
-    }
-    point1[0] = point2[0];
-    point1[1] = point2[1];
-  }
-  return false;
+ol.geom.Polygon.prototype.getType = function() {
+  return ol.geom.GeometryType.POLYGON;
 };
 
-goog.provide('ol.geom.flat.intersectsextent');
 
-goog.require('ol.extent');
-goog.require('ol.geom.flat.contains');
-goog.require('ol.geom.flat.segments');
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
+  return ol.geom.flat.intersectsextent.linearRings(
+      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
+};
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} True if the geometry and the extent intersect.
+ * Set the coordinates of the polygon.
+ * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
+ * @param {ol.geom.GeometryLayout=} opt_layout Layout.
+ * @override
+ * @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;
+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();
   }
-  return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride,
-      /**
-       * @param {ol.Coordinate} point1 Start point.
-       * @param {ol.Coordinate} point2 End point.
-       * @return {boolean} `true` if the segment and the extent intersect,
-       *     `false` otherwise.
-       */
-      function(point1, point2) {
-        return ol.extent.intersectsSegment(extent, point1, point2);
-      });
 };
 
 
 /**
+ * @param {ol.geom.GeometryLayout} layout Layout.
  * @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.geom.Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.ends_ = ends;
+  this.changed();
 };
 
 
 /**
- * @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.
+ * 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.flat.intersectsextent.linearRing = function(flatCoordinates, offset, end, stride, extent) {
-  if (ol.geom.flat.intersectsextent.lineString(
-      flatCoordinates, offset, end, stride, extent)) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[0], extent[1])) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[0], extent[3])) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[2], extent[1])) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[2], extent[3])) {
-    return true;
+ol.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));
   }
-  return false;
+  flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
+  var polygon = new ol.geom.Polygon(null);
+  polygon.setFlatCoordinates(
+      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
+  return polygon;
 };
 
 
 /**
- * @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.
+ * 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.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;
+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;
 };
 
 
 /**
- * @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.
+ * 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.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];
+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;
   }
-  return false;
+  var ends = [flatCoordinates.length];
+  polygon.setFlatCoordinates(layout, flatCoordinates, ends);
+  ol.geom.Polygon.makeRegular(
+      polygon, circle.getCenter(), circle.getRadius(), opt_angle);
+  return polygon;
 };
 
-goog.provide('ol.geom.flat.reverse');
-
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
+ * 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.flat.reverse.coordinates = function(flatCoordinates, offset, end, stride) {
-  while (offset < end - stride) {
-    var i;
-    for (i = 0; i < stride; ++i) {
-      var tmp = flatCoordinates[offset + i];
-      flatCoordinates[offset + i] = flatCoordinates[end - stride + i];
-      flatCoordinates[end - stride + i] = tmp;
-    }
-    offset += stride;
-    end -= stride;
+ol.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.geom.flat.orient');
-
-goog.require('ol.geom.flat.reverse');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {boolean} Is clockwise.
- */
-ol.geom.flat.orient.linearRingIsClockwise = function(flatCoordinates, offset, end, stride) {
-  // http://tinyurl.com/clockwise-method
-  // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp
-  var edge = 0;
-  var x1 = flatCoordinates[end - stride];
-  var y1 = flatCoordinates[end - stride + 1];
-  for (; offset < end; offset += stride) {
-    var x2 = flatCoordinates[offset];
-    var y2 = flatCoordinates[offset + 1];
-    edge += (x2 - x1) * (y2 + y1);
-    x1 = x2;
-    y1 = y2;
-  }
-  return edge > 0;
-};
-
-
-/**
- * Determines if linear rings are oriented.  By default, left-hand orientation
- * is tested (first ring must be clockwise, remaining rings counter-clockwise).
- * To test for right-hand orientation, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Array of end indexes.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Test for right-hand orientation
- *     (counter-clockwise exterior ring and clockwise interior rings).
- * @return {boolean} Rings are correctly oriented.
- */
-ol.geom.flat.orient.linearRingsAreOriented = function(flatCoordinates, offset, ends, stride, opt_right) {
-  var right = opt_right !== undefined ? opt_right : false;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
-        flatCoordinates, offset, end, stride);
-    if (i === 0) {
-      if ((right && isClockwise) || (!right && !isClockwise)) {
-        return false;
-      }
-    } else {
-      if ((right && !isClockwise) || (!right && isClockwise)) {
-        return false;
-      }
-    }
-    offset = end;
-  }
-  return true;
-};
-
-
-/**
- * Determines if linear rings are oriented.  By default, left-hand orientation
- * is tested (first ring must be clockwise, remaining rings counter-clockwise).
- * To test for right-hand orientation, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Array of array of end indexes.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Test for right-hand orientation
- *     (counter-clockwise exterior ring and clockwise interior rings).
- * @return {boolean} Rings are correctly oriented.
- */
-ol.geom.flat.orient.linearRingssAreOriented = function(flatCoordinates, offset, endss, stride, opt_right) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    if (!ol.geom.flat.orient.linearRingsAreOriented(
-        flatCoordinates, offset, endss[i], stride, opt_right)) {
-      return false;
-    }
-  }
-  return true;
-};
-
-
-/**
- * Orient coordinates in a flat array of linear rings.  By default, rings
- * are oriented following the left-hand rule (clockwise for exterior and
- * counter-clockwise for interior rings).  To orient according to the
- * right-hand rule, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Follow the right-hand rule for orientation.
- * @return {number} End.
- */
-ol.geom.flat.orient.orientLinearRings = function(flatCoordinates, offset, ends, stride, opt_right) {
-  var right = opt_right !== undefined ? opt_right : false;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
-        flatCoordinates, offset, end, stride);
-    var reverse = i === 0 ?
-        (right && isClockwise) || (!right && !isClockwise) :
-        (right && !isClockwise) || (!right && isClockwise);
-    if (reverse) {
-      ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride);
-    }
-    offset = end;
-  }
-  return offset;
-};
-
-
-/**
- * Orient coordinates in a flat array of linear rings.  By default, rings
- * are oriented following the left-hand rule (clockwise for exterior and
- * counter-clockwise for interior rings).  To orient according to the
- * right-hand rule, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Array of array of end indexes.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Follow the right-hand rule for orientation.
- * @return {number} End.
- */
-ol.geom.flat.orient.orientLinearRingss = function(flatCoordinates, offset, endss, stride, opt_right) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    offset = ol.geom.flat.orient.orientLinearRings(
-        flatCoordinates, offset, endss[i], stride, opt_right);
-  }
-  return offset;
-};
-
-goog.provide('ol.geom.Polygon');
+goog.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.GeometryLayout');
 goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
 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');
+goog.require('ol.obj');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
 
 
 /**
  * @classdesc
- * Polygon geometry.
+ * 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.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.
+ * @extends {ol.Object}
+ * @param {olx.ViewOptions=} opt_options View options.
  * @api
  */
-ol.geom.Polygon = function(coordinates, opt_layout) {
+ol.View = function(opt_options) {
+  ol.Object.call(this);
 
-  ol.geom.SimpleGeometry.call(this);
+  var options = ol.obj.assign({}, opt_options);
 
   /**
+   * @private
    * @type {Array.<number>}
+   */
+  this.hints_ = [0, 0];
+
+  /**
    * @private
+   * @type {Array.<Array.<ol.ViewAnimation>>}
    */
-  this.ends_ = [];
+  this.animations_ = [];
 
   /**
    * @private
-   * @type {number}
+   * @type {number|undefined}
    */
-  this.flatInteriorPointRevision_ = -1;
+  this.updateAnimationKey_;
+
+  this.updateAnimations_ = this.updateAnimations_.bind(this);
 
   /**
    * @private
-   * @type {ol.Coordinate}
+   * @const
+   * @type {ol.proj.Projection}
    */
-  this.flatInteriorPoint_ = 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 {number}
    */
-  this.maxDelta_ = -1;
+  this.maxResolution_ = resolutionConstraintInfo.maxResolution;
 
   /**
    * @private
    * @type {number}
    */
-  this.maxDeltaRevision_ = -1;
+  this.minResolution_ = resolutionConstraintInfo.minResolution;
 
   /**
    * @private
    * @type {number}
    */
-  this.orientedRevision_ = -1;
+  this.zoomFactor_ = resolutionConstraintInfo.zoomFactor;
 
   /**
    * @private
-   * @type {Array.<number>}
+   * @type {Array.<number>|undefined}
    */
-  this.orientedFlatCoordinates_ = null;
+  this.resolutions_ = options.resolutions;
 
-  this.setCoordinates(coordinates, opt_layout);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.minZoom_ = resolutionConstraintInfo.minZoom;
 
-};
-ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
+  var centerConstraint = ol.View.createCenterConstraint_(options);
+  var resolutionConstraint = resolutionConstraintInfo.constraint;
+  var rotationConstraint = ol.View.createRotationConstraint_(options);
+
+  /**
+   * @private
+   * @type {ol.Constraints}
+   */
+  this.constraints_ = {
+    center: centerConstraint,
+    resolution: resolutionConstraint,
+    rotation: 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_);
 
-/**
- * Append the passed linear ring to this polygon.
- * @param {ol.geom.LinearRing} linearRing Linear ring.
- * @api
- */
-ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
-  if (!this.flatCoordinates) {
-    this.flatCoordinates = linearRing.getFlatCoordinates().slice();
-  } else {
-    ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
+    if (this.resolutions_) { // in case map zoom is out of min/max zoom range
+      properties[ol.ViewProperty.RESOLUTION] = ol.math.clamp(
+          Number(this.getResolution() || properties[ol.ViewProperty.RESOLUTION]),
+          this.minResolution_, this.maxResolution_);
+    }
   }
-  this.ends_.push(this.flatCoordinates.length);
-  this.changed();
-};
+  properties[ol.ViewProperty.ROTATION] =
+      options.rotation !== undefined ? options.rotation : 0;
+  this.setProperties(properties);
 
+  /**
+   * @private
+   * @type {olx.ViewOptions}
+   */
+  this.options_ = options;
 
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Polygon} Clone.
- * @override
- * @api
- */
-ol.geom.Polygon.prototype.clone = function() {
-  var polygon = new ol.geom.Polygon(null);
-  polygon.setFlatCoordinates(
-      this.layout, this.flatCoordinates.slice(), this.ends_.slice());
-  return polygon;
 };
 
-
 /**
- * @inheritDoc
+ * 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.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);
-};
+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();
+  }
 
-/**
- * @inheritDoc
- */
-ol.geom.Polygon.prototype.containsXY = function(x, y) {
-  return ol.geom.flat.contains.linearRingsContainsXY(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
-};
+  // preserve center
+  options.center = this.getCenter();
 
+  // preserve rotation
+  options.rotation = this.getRotation();
 
-/**
- * Return the area of the polygon on projected plane.
- * @return {number} Area (on projected plane).
- * @api
- */
-ol.geom.Polygon.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRings(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
+  return ol.obj.assign({}, options, newOptions);
 };
 
 
 /**
- * Get the coordinate array for this geometry.  This array has the structure
- * of a GeoJSON coordinate array for polygons.
+ * 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:
  *
- * @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
+ *     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.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;
+ol.View.prototype.animate = function(var_args) {
+  var animationCount = arguments.length;
+  var callback;
+  if (animationCount > 1 && typeof arguments[animationCount - 1] === 'function') {
+    callback = arguments[animationCount - 1];
+    --animationCount;
+  }
+  if (!this.isDef()) {
+    // if view properties are not yet set, shortcut to the final state
+    var state = arguments[animationCount - 1];
+    if (state.center) {
+      this.setCenter(state.center);
+    }
+    if (state.zoom !== undefined) {
+      this.setZoom(state.zoom);
+    }
+    if (state.rotation !== undefined) {
+      this.setRotation(state.rotation);
+    }
+    if (callback) {
+      callback(true);
+    }
+    return;
   }
+  var start = Date.now();
+  var center = this.getCenter().slice();
+  var resolution = this.getResolution();
+  var rotation = this.getRotation();
+  var series = [];
+  for (var i = 0; i < animationCount; ++i) {
+    var options = /** @type {olx.AnimationOptions} */ (arguments[i]);
 
-  return ol.geom.flat.inflate.coordinatess(
-      flatCoordinates, 0, this.ends_, this.stride);
-};
+    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;
+    }
 
-/**
- * @return {Array.<number>} Ends.
- */
-ol.geom.Polygon.prototype.getEnds = function() {
-  return this.ends_;
-};
+    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;
+      var delta = ol.math.modulo(options.rotation - rotation + Math.PI, 2 * Math.PI) - Math.PI;
+      animation.targetRotation = rotation + delta;
+      rotation = animation.targetRotation;
+    }
 
-/**
- * @return {Array.<number>} Interior point.
- */
-ol.geom.Polygon.prototype.getFlatInteriorPoint = function() {
-  if (this.flatInteriorPointRevision_ != this.getRevision()) {
-    var flatCenter = ol.extent.getCenter(this.getExtent());
-    this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings(
-        this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride,
-        flatCenter, 0);
-    this.flatInteriorPointRevision_ = this.getRevision();
+    animation.callback = callback;
+
+    // check if animation is a no-op
+    if (ol.View.isNoopAnimation(animation)) {
+      animation.complete = true;
+      // we still push it onto the series for callback handling
+    } else {
+      start += animation.duration;
+    }
+    series.push(animation);
   }
-  return this.flatInteriorPoint_;
+  this.animations_.push(series);
+  this.setHint(ol.ViewHint.ANIMATING, 1);
+  this.updateAnimations_();
 };
 
 
 /**
- * Return an interior point of the polygon.
- * @return {ol.geom.Point} Interior point.
+ * Determine if the view is being animated.
+ * @return {boolean} The view is being animated.
  * @api
  */
-ol.geom.Polygon.prototype.getInteriorPoint = function() {
-  return new ol.geom.Point(this.getFlatInteriorPoint());
+ol.View.prototype.getAnimating = function() {
+  return this.hints_[ol.ViewHint.ANIMATING] > 0;
 };
 
 
 /**
- * Return the number of rings of the polygon,  this includes the exterior
- * ring and any interior rings.
- *
- * @return {number} Number of rings.
+ * Determine if the user is interacting with the view, such as panning or zooming.
+ * @return {boolean} The view is being interacted with.
  * @api
  */
-ol.geom.Polygon.prototype.getLinearRingCount = function() {
-  return this.ends_.length;
+ol.View.prototype.getInteracting = function() {
+  return this.hints_[ol.ViewHint.INTERACTING] > 0;
 };
 
 
 /**
- * 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.
+ * Cancel any ongoing animations.
  * @api
  */
-ol.geom.Polygon.prototype.getLinearRing = function(index) {
-  if (index < 0 || this.ends_.length <= index) {
-    return null;
+ol.View.prototype.cancelAnimations = function() {
+  this.setHint(ol.ViewHint.ANIMATING, -this.hints_[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);
+    }
   }
-  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.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 ?
+          ol.math.modulo(animation.targetRotation + Math.PI, 2 * Math.PI) - Math.PI :
+          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_);
+  }
+};
 
 /**
- * Return the linear rings of the polygon.
- * @return {Array.<ol.geom.LinearRing>} Linear rings.
- * @api
+ * @param {number} rotation Target rotation.
+ * @param {ol.Coordinate} anchor Rotation anchor.
+ * @return {ol.Coordinate|undefined} Center for rotation and anchor.
  */
-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.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 linearRings;
+  return center;
 };
 
 
 /**
- * @return {Array.<number>} Oriented flat coordinates.
+ * @param {number} resolution Target resolution.
+ * @param {ol.Coordinate} anchor Zoom anchor.
+ * @return {ol.Coordinate|undefined} Center for resolution and anchor.
  */
-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();
+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 this.orientedFlatCoordinates_;
+  return center;
 };
 
 
 /**
- * @inheritDoc
+ * @private
+ * @return {ol.Size} Viewport size or `[100, 100]` when no viewport is found.
  */
-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.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;
 };
 
 
 /**
- * @inheritDoc
+ * Get the constrained center of this view.
+ * @param {ol.Coordinate|undefined} center Center.
+ * @return {ol.Coordinate|undefined} Constrained center.
  * @api
  */
-ol.geom.Polygon.prototype.getType = function() {
-  return ol.geom.GeometryType.POLYGON;
+ol.View.prototype.constrainCenter = function(center) {
+  return this.constraints_.center(center);
 };
 
 
 /**
- * @inheritDoc
+ * 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.geom.Polygon.prototype.intersectsExtent = function(extent) {
-  return ol.geom.flat.intersectsextent.linearRings(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
+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 coordinates of the polygon.
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @override
+ * 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.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.View.prototype.constrainRotation = function(rotation, opt_delta) {
+  var delta = opt_delta || 0;
+  return this.constraints_.rotation(rotation, delta);
 };
 
 
 /**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<number>} ends Ends.
+ * Get the view center.
+ * @return {ol.Coordinate|undefined} The center of the view.
+ * @observable
+ * @api
  */
-ol.geom.Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.ends_ = ends;
-  this.changed();
+ol.View.prototype.getCenter = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+    this.get(ol.ViewProperty.CENTER));
 };
 
 
 /**
- * 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
+ * @return {ol.Constraints} Constraints.
  */
-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));
+ol.View.prototype.getConstraints = function() {
+  return this.constraints_;
+};
+
+
+/**
+ * @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 {
+    return this.hints_.slice();
   }
-  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.
+ * 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.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.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);
 };
 
 
 /**
- * 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.
+ * Get the maximum resolution of the view.
+ * @return {number} The maximum resolution of the view.
  * @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;
+ol.View.prototype.getMaxResolution = function() {
+  return this.maxResolution_;
 };
 
 
 /**
- * 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.
+ * Get the minimum resolution of the view.
+ * @return {number} The minimum resolution of the view.
+ * @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();
-  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);
+ol.View.prototype.getMinResolution = function() {
+  return this.minResolution_;
 };
 
-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');
+/**
+ * Get the maximum zoom level for the view.
+ * @return {number} The maximum zoom level.
+ * @api
+ */
+ol.View.prototype.getMaxZoom = function() {
+  return /** @type {number} */ (this.getZoomForResolution(this.minResolution_));
+};
 
 
 /**
- * @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.
+ * Set a new maximum zoom level for the view.
+ * @param {number} zoom The maximum zoom level.
  * @api
  */
-ol.View = function(opt_options) {
-  ol.Object.call(this);
-
-  var options = ol.obj.assign({}, opt_options);
+ol.View.prototype.setMaxZoom = function(zoom) {
+  this.applyOptions_(this.getUpdatedOptions_({maxZoom: zoom}));
+};
 
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.hints_ = [0, 0];
 
-  /**
-   * @private
-   * @type {Array.<Array.<ol.ViewAnimation>>}
-   */
-  this.animations_ = [];
+/**
+ * Get the minimum zoom level for the view.
+ * @return {number} The minimum zoom level.
+ * @api
+ */
+ol.View.prototype.getMinZoom = function() {
+  return /** @type {number} */ (this.getZoomForResolution(this.maxResolution_));
+};
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.updateAnimationKey_;
 
-  this.updateAnimations_ = this.updateAnimations_.bind(this);
+/**
+ * Set a new minimum zoom level for the view.
+ * @param {number} zoom The minimum zoom level.
+ * @api
+ */
+ol.View.prototype.setMinZoom = function(zoom) {
+  this.applyOptions_(this.getUpdatedOptions_({minZoom: zoom}));
+};
 
-  /**
-   * @private
-   * @const
-   * @type {ol.proj.Projection}
-   */
-  this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857');
 
-  this.applyOptions_(options);
+/**
+ * Get the view projection.
+ * @return {ol.proj.Projection} The projection of the view.
+ * @api
+ */
+ol.View.prototype.getProjection = function() {
+  return this.projection_;
 };
-ol.inherits(ol.View, ol.Object);
 
 
 /**
- * Set up the view with the given options.
- * @param {olx.ViewOptions} options View options.
+ * Get the view resolution.
+ * @return {number|undefined} The resolution of the view.
+ * @observable
+ * @api
  */
-ol.View.prototype.applyOptions_ = function(options) {
-
-  /**
-   * @type {Object.<string, *>}
-   */
-  var properties = {};
-  properties[ol.ViewProperty.CENTER] = options.center !== undefined ?
-      options.center : null;
+ol.View.prototype.getResolution = function() {
+  return /** @type {number|undefined} */ (
+    this.get(ol.ViewProperty.RESOLUTION));
+};
 
-  var resolutionConstraintInfo = ol.View.createResolutionConstraint_(
-      options);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxResolution_ = resolutionConstraintInfo.maxResolution;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minResolution_ = resolutionConstraintInfo.minResolution;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.zoomFactor_ = resolutionConstraintInfo.zoomFactor;
-
-  /**
-   * @private
-   * @type {Array.<number>|undefined}
-   */
-  this.resolutions_ = options.resolutions;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minZoom_ = resolutionConstraintInfo.minZoom;
-
-  var centerConstraint = ol.View.createCenterConstraint_(options);
-  var resolutionConstraint = resolutionConstraintInfo.constraint;
-  var rotationConstraint = ol.View.createRotationConstraint_(options);
+/**
+ * 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.View.prototype.getResolutions = function() {
+  return this.resolutions_;
+};
 
-  /**
-   * @private
-   * @type {ol.Constraints}
-   */
-  this.constraints_ = {
-    center: centerConstraint,
-    resolution: resolutionConstraint,
-    rotation: 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);
+/**
+ * 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.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);
+};
 
-  /**
-   * @private
-   * @type {olx.ViewOptions}
-   */
-  this.options_ = options;
 
+/**
+ * Return a function that returns a value between 0 and 1 for a
+ * resolution. Exponential scaling is assumed.
+ * @param {number=} opt_power Power.
+ * @return {function(number): number} Resolution for value function.
+ */
+ol.View.prototype.getResolutionForValueFunction = function(opt_power) {
+  var power = opt_power || 2;
+  var maxResolution = this.maxResolution_;
+  var minResolution = this.minResolution_;
+  var max = Math.log(maxResolution / minResolution) / Math.log(power);
+  return (
+    /**
+     * @param {number} value Value.
+     * @return {number} Resolution.
+     */
+    function(value) {
+      var resolution = maxResolution / Math.pow(power, value * max);
+      return resolution;
+    });
 };
 
+
 /**
- * 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.
+ * Get the view rotation.
+ * @return {number} The rotation of the view in radians.
+ * @observable
+ * @api
  */
-ol.View.prototype.getUpdatedOptions_ = function(newOptions) {
-  var options = ol.obj.assign({}, this.options_);
+ol.View.prototype.getRotation = function() {
+  return /** @type {number} */ (this.get(ol.ViewProperty.ROTATION));
+};
 
-  // preserve resolution (or zoom)
-  if (options.resolution !== undefined) {
-    options.resolution = this.getResolution();
-  } else {
-    options.zoom = this.getZoom();
-  }
 
-  // preserve center
-  options.center = this.getCenter();
+/**
+ * Return a function that returns a resolution for a value between
+ * 0 and 1. Exponential scaling is assumed.
+ * @param {number=} opt_power Power.
+ * @return {function(number): number} Value for resolution function.
+ */
+ol.View.prototype.getValueForResolutionFunction = function(opt_power) {
+  var power = opt_power || 2;
+  var maxResolution = this.maxResolution_;
+  var minResolution = this.minResolution_;
+  var max = Math.log(maxResolution / minResolution) / Math.log(power);
+  return (
+    /**
+     * @param {number} resolution Resolution.
+     * @return {number} Value.
+     */
+    function(resolution) {
+      var value =
+            (Math.log(maxResolution / resolution) / Math.log(power)) / max;
+      return value;
+    });
+};
 
-  // preserve rotation
-  options.rotation = this.getRotation();
 
-  return ol.obj.assign({}, options, newOptions);
+/**
+ * @return {olx.ViewState} View state.
+ */
+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,
+    zoom: this.getZoom()
+  });
 };
 
 
 /**
- * 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.
+ * Get the current zoom level.  If you configured your view with a resolutions
+ * array (this is rare), this method may return non-integer zoom levels (so
+ * the zoom level is not safe to use as an index into a resolutions array).
+ * @return {number|undefined} Zoom.
  * @api
  */
-ol.View.prototype.animate = function(var_args) {
-  var start = Date.now();
-  var center = this.getCenter().slice();
+ol.View.prototype.getZoom = function() {
+  var zoom;
   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);
+  if (resolution !== undefined) {
+    zoom = this.getZoomForResolution(resolution);
   }
-  this.animations_.push(series);
-  this.setHint(ol.ViewHint.ANIMATING, 1);
-  this.updateAnimations_();
+  return zoom;
 };
 
 
 /**
- * Determine if the view is being animated.
- * @return {boolean} The view is being animated.
+ * 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.getAnimating = function() {
-  return this.getHints()[ol.ViewHint.ANIMATING] > 0;
+ol.View.prototype.getZoomForResolution = function(resolution) {
+  var offset = this.minZoom_ || 0;
+  var max, zoomFactor;
+  if (this.resolutions_) {
+    var nearest = ol.array.linearFindNearest(this.resolutions_, resolution, 1);
+    offset = nearest;
+    max = this.resolutions_[nearest];
+    if (nearest == this.resolutions_.length - 1) {
+      zoomFactor = 2;
+    } else {
+      zoomFactor = max / this.resolutions_[nearest + 1];
+    }
+  } else {
+    max = this.maxResolution_;
+    zoomFactor = this.zoomFactor_;
+  }
+  return offset + Math.log(max / resolution) / Math.log(zoomFactor);
 };
 
 
 /**
- * Determine if the user is interacting with the view, such as panning or zooming.
- * @return {boolean} The view is being interacted with.
+ * Get the resolution for a zoom level.
+ * @param {number} zoom Zoom level.
+ * @return {number} The view resolution for the provided zoom level.
  * @api
  */
-ol.View.prototype.getInteracting = function() {
-  return this.getHints()[ol.ViewHint.INTERACTING] > 0;
+ol.View.prototype.getResolutionForZoom = function(zoom) {
+  return /** @type {number} */ (this.constrainResolution(
+      this.maxResolution_, zoom - this.minZoom_, 0));
 };
 
 
 /**
- * Cancel any ongoing animations.
+ * 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.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);
-    }
+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 {
+    geometry = geometryOrExtent;
   }
-  this.animations_.length = 0;
-};
 
-/**
- * Update all animations.
- */
-ol.View.prototype.updateAnimations_ = function() {
-  if (this.updateAnimationKey_ !== undefined) {
-    cancelAnimationFrame(this.updateAnimationKey_);
-    this.updateAnimationKey_ = undefined;
+  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;
   }
-  if (!this.getAnimating()) {
-    return;
+  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);
   }
-  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);
-      }
+
+  // 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;
   }
-  // prune completed series
-  this.animations_ = this.animations_.filter(Boolean);
-  if (more && this.updateAnimationKey_ === undefined) {
-    this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_);
+
+  // 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);
   }
 };
 
+
 /**
- * @param {number} rotation Target rotation.
- * @param {ol.Coordinate} anchor Rotation anchor.
- * @return {ol.Coordinate|undefined} Center for rotation and anchor.
+ * Center on coordinate and view position.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.Size} size Box pixel size.
+ * @param {ol.Pixel} position Position on the view to center on.
+ * @api
  */
-ol.View.prototype.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.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]);
 };
 
 
 /**
- * @param {number} resolution Target resolution.
- * @param {ol.Coordinate} anchor Zoom anchor.
- * @return {ol.Coordinate|undefined} Center for resolution and anchor.
+ * @return {boolean} Is defined.
  */
-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.View.prototype.isDef = function() {
+  return !!this.getCenter() && this.getResolution() !== undefined;
 };
 
 
 /**
- * @private
- * @return {ol.Size} Viewport size or `[100, 100]` when no viewport is found.
+ * 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.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);
+ol.View.prototype.rotate = function(rotation, opt_anchor) {
+  if (opt_anchor !== undefined) {
+    var center = this.calculateCenterRotate(rotation, opt_anchor);
+    this.setCenter(center);
   }
-  return size;
+  this.setRotation(rotation);
 };
 
 
 /**
- * Get the constrained center of this view.
- * @param {ol.Coordinate|undefined} center Center.
- * @return {ol.Coordinate|undefined} Constrained center.
+ * Set the center of the current view.
+ * @param {ol.Coordinate|undefined} center The center of the view.
+ * @observable
  * @api
  */
-ol.View.prototype.constrainCenter = function(center) {
-  return this.constraints_.center(center);
-};
-
-
-/**
- * Get the constrained resolution of this view.
- * @param {number|undefined} resolution Resolution.
- * @param {number=} opt_delta Delta. Default is `0`.
- * @param {number=} opt_direction Direction. Default is `0`.
- * @return {number|undefined} Constrained resolution.
- * @api
- */
-ol.View.prototype.constrainResolution = function(
-    resolution, opt_delta, opt_direction) {
-  var delta = opt_delta || 0;
-  var direction = opt_direction || 0;
-  return this.constraints_.resolution(resolution, delta, direction);
-};
-
-
-/**
- * Get the constrained rotation of this view.
- * @param {number|undefined} rotation Rotation.
- * @param {number=} opt_delta Delta. Default is `0`.
- * @return {number|undefined} Constrained rotation.
- * @api
- */
-ol.View.prototype.constrainRotation = function(rotation, opt_delta) {
-  var delta = opt_delta || 0;
-  return this.constraints_.rotation(rotation, delta);
-};
-
-
-/**
- * Get the view center.
- * @return {ol.Coordinate|undefined} The center of the view.
- * @observable
- * @api
- */
-ol.View.prototype.getCenter = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.ViewProperty.CENTER));
-};
-
-
-/**
- * @return {ol.Constraints} Constraints.
- */
-ol.View.prototype.getConstraints = function() {
-  return this.constraints_;
-};
-
-
-/**
- * @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 {
-    return this.hints_.slice();
-  }
-};
-
-
-/**
- * Calculate the extent for the current view state and the passed size.
- * The size is the pixel dimensions of the box into which the calculated extent
- * should fit. In most cases you want to get the extent of the entire map,
- * that is `map.getSize()`.
- * @param {ol.Size=} 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.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);
-};
-
-
-/**
- * Get the maximum resolution of the view.
- * @return {number} The maximum resolution of the view.
- * @api
- */
-ol.View.prototype.getMaxResolution = function() {
-  return this.maxResolution_;
-};
-
-
-/**
- * Get the minimum resolution of the view.
- * @return {number} The minimum resolution of the view.
- * @api
- */
-ol.View.prototype.getMinResolution = function() {
-  return this.minResolution_;
-};
-
-
-/**
- * Get the maximum zoom level for the view.
- * @return {number} The maximum zoom level.
- * @api
- */
-ol.View.prototype.getMaxZoom = function() {
-  return /** @type {number} */ (this.getZoomForResolution(this.minResolution_));
-};
-
-
-/**
- * Set a new maximum zoom level for the view.
- * @param {number} zoom The maximum zoom level.
- * @api
- */
-ol.View.prototype.setMaxZoom = function(zoom) {
-  this.applyOptions_(this.getUpdatedOptions_({maxZoom: zoom}));
-};
-
-
-/**
- * Get the minimum zoom level for the view.
- * @return {number} The minimum zoom level.
- * @api
- */
-ol.View.prototype.getMinZoom = function() {
-  return /** @type {number} */ (this.getZoomForResolution(this.maxResolution_));
-};
-
-
-/**
- * Set a new minimum zoom level for the view.
- * @param {number} zoom The minimum zoom level.
- * @api
- */
-ol.View.prototype.setMinZoom = function(zoom) {
-  this.applyOptions_(this.getUpdatedOptions_({minZoom: zoom}));
-};
-
-
-/**
- * Get the view projection.
- * @return {ol.proj.Projection} The projection of the view.
- * @api
- */
-ol.View.prototype.getProjection = function() {
-  return this.projection_;
-};
-
-
-/**
- * Get the view resolution.
- * @return {number|undefined} The resolution of the view.
- * @observable
- * @api
- */
-ol.View.prototype.getResolution = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.ViewProperty.RESOLUTION));
-};
-
-
-/**
- * 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.View.prototype.getResolutions = function() {
-  return this.resolutions_;
-};
-
-
-/**
- * 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.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);
-};
-
-
-/**
- * Return a function that returns a value between 0 and 1 for a
- * resolution. Exponential scaling is assumed.
- * @param {number=} opt_power Power.
- * @return {function(number): number} Resolution for value function.
- */
-ol.View.prototype.getResolutionForValueFunction = function(opt_power) {
-  var power = opt_power || 2;
-  var maxResolution = this.maxResolution_;
-  var minResolution = this.minResolution_;
-  var max = Math.log(maxResolution / minResolution) / Math.log(power);
-  return (
-      /**
-       * @param {number} value Value.
-       * @return {number} Resolution.
-       */
-      function(value) {
-        var resolution = maxResolution / Math.pow(power, value * max);
-        return resolution;
-      });
-};
-
-
-/**
- * Get the view rotation.
- * @return {number} The rotation of the view in radians.
- * @observable
- * @api
- */
-ol.View.prototype.getRotation = function() {
-  return /** @type {number} */ (this.get(ol.ViewProperty.ROTATION));
-};
-
-
-/**
- * Return a function that returns a resolution for a value between
- * 0 and 1. Exponential scaling is assumed.
- * @param {number=} opt_power Power.
- * @return {function(number): number} Value for resolution function.
- */
-ol.View.prototype.getValueForResolutionFunction = function(opt_power) {
-  var power = opt_power || 2;
-  var maxResolution = this.maxResolution_;
-  var minResolution = this.minResolution_;
-  var max = Math.log(maxResolution / minResolution) / Math.log(power);
-  return (
-      /**
-       * @param {number} resolution Resolution.
-       * @return {number} Value.
-       */
-      function(resolution) {
-        var value =
-            (Math.log(maxResolution / resolution) / Math.log(power)) / max;
-        return value;
-      });
-};
-
-
-/**
- * @return {olx.ViewState} View state.
- */
-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
-  });
-};
-
-
-/**
- * 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.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_;
-    }
-    zoom = offset + Math.log(max / resolution) / Math.log(zoomFactor);
-  }
-  return zoom;
-};
-
-
-/**
- * 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.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 {
-    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);
-  }
-};
-
-
-/**
- * Center on coordinate and view position.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.Size} size Box pixel size.
- * @param {ol.Pixel} position Position on the view to center on.
- * @api
- */
-ol.View.prototype.centerOn = function(coordinate, size, position) {
-  // calculate rotated position
-  var rotation = this.getRotation();
-  var cosAngle = Math.cos(-rotation);
-  var sinAngle = Math.sin(-rotation);
-  var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
-  var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
-  var resolution = this.getResolution();
-  rotX += (size[0] / 2 - position[0]) * resolution;
-  rotY += (position[1] - size[1] / 2) * resolution;
-
-  // go back to original angle
-  sinAngle = -sinAngle; // go back to original rotation
-  var centerX = rotX * cosAngle - rotY * sinAngle;
-  var centerY = rotY * cosAngle + rotX * sinAngle;
-
-  this.setCenter([centerX, centerY]);
-};
-
-
-/**
- * @return {boolean} Is defined.
- */
-ol.View.prototype.isDef = function() {
-  return !!this.getCenter() && this.getResolution() !== undefined;
-};
-
-
-/**
- * Rotate the view around a given coordinate.
- * @param {number} rotation New rotation value for the view.
- * @param {ol.Coordinate=} opt_anchor The rotation center.
- * @api
- */
-ol.View.prototype.rotate = function(rotation, opt_anchor) {
-  if (opt_anchor !== undefined) {
-    var center = this.calculateCenterRotate(rotation, opt_anchor);
-    this.setCenter(center);
-  }
-  this.setRotation(rotation);
-};
-
-
-/**
- * Set the center of the current view.
- * @param {ol.Coordinate|undefined} center The center of the view.
- * @observable
- * @api
- */
-ol.View.prototype.setCenter = function(center) {
-  this.set(ol.ViewProperty.CENTER, center);
-  if (this.getAnimating()) {
-    this.cancelAnimations();
-  }
+ol.View.prototype.setCenter = function(center) {
+  this.set(ol.ViewProperty.CENTER, center);
+  if (this.getAnimating()) {
+    this.cancelAnimations();
+  }
 };
 
 
@@ -17853,9 +16690,7 @@ ol.View.prototype.setRotation = function(rotation) {
  * @api
  */
 ol.View.prototype.setZoom = function(zoom) {
-  var resolution = this.constrainResolution(
-      this.maxResolution_, zoom - this.minZoom_, 0);
-  this.setResolution(resolution);
+  this.setResolution(this.getResolutionForZoom(zoom));
 };
 
 
@@ -17890,18 +16725,19 @@ ol.View.createResolutionConstraint_ = function(options) {
   var defaultZoomFactor = 2;
 
   var minZoom = options.minZoom !== undefined ?
-      options.minZoom : ol.DEFAULT_MIN_ZOOM;
+    options.minZoom : ol.DEFAULT_MIN_ZOOM;
 
   var maxZoom = options.maxZoom !== undefined ?
-      options.maxZoom : defaultMaxZoom;
+    options.maxZoom : defaultMaxZoom;
 
   var zoomFactor = options.zoomFactor !== undefined ?
-      options.zoomFactor : defaultZoomFactor;
+    options.zoomFactor : defaultZoomFactor;
 
   if (options.resolutions !== undefined) {
     var resolutions = options.resolutions;
-    maxResolution = resolutions[0];
-    minResolution = resolutions[resolutions.length - 1];
+    maxResolution = resolutions[minZoom];
+    minResolution = resolutions[maxZoom] !== undefined ?
+      resolutions[maxZoom] : resolutions[resolutions.length - 1];
     resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions(
         resolutions);
   } else {
@@ -17909,10 +16745,10 @@ ol.View.createResolutionConstraint_ = function(options) {
     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] /
+      // 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));
+      Math.max(ol.extent.getWidth(extent), ol.extent.getHeight(extent));
 
     var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow(
         defaultZoomFactor, ol.DEFAULT_MIN_ZOOM);
@@ -17962,7 +16798,7 @@ ol.View.createResolutionConstraint_ = function(options) {
  */
 ol.View.createRotationConstraint_ = function(options) {
   var enableRotation = options.enableRotation !== undefined ?
-      options.enableRotation : true;
+    options.enableRotation : true;
   if (enableRotation) {
     var constrainRotation = options.constrainRotation;
     if (constrainRotation === undefined || constrainRotation === true) {
@@ -17979,9559 +16815,10154 @@ ol.View.createRotationConstraint_ = function(options) {
   }
 };
 
-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
+ * Determine if an animation involves no view change.
+ * @param {ol.ViewAnimation} animation The animation.
+ * @return {boolean} The animation involves no view change.
  */
-ol.Kinetic = function(decay, minVelocity, delay) {
+ol.View.isNoopAnimation = function(animation) {
+  if (animation.sourceCenter && animation.targetCenter) {
+    if (!ol.coordinate.equals(animation.sourceCenter, animation.targetCenter)) {
+      return false;
+    }
+  }
+  if (animation.sourceResolution !== animation.targetResolution) {
+    return false;
+  }
+  if (animation.sourceRotation !== animation.targetRotation) {
+    return false;
+  }
+  return true;
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.decay_ = decay;
+goog.provide('ol.dom');
 
-  /**
-   * @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;
-};
+/**
+ * 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.
+ */
+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');
+};
 
 
 /**
- * FIXME empty description for jsdoc
+ * 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.
  */
-ol.Kinetic.prototype.begin = function() {
-  this.points_.length = 0;
-  this.angle_ = 0;
-  this.initialVelocity_ = 0;
+ol.dom.outerWidth = function(element) {
+  var width = element.offsetWidth;
+  var style = getComputedStyle(element);
+  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
+
+  return width;
 };
 
 
 /**
- * @param {number} x X.
- * @param {number} y Y.
+ * 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.
  */
-ol.Kinetic.prototype.update = function(x, y) {
-  this.points_.push(x, y, Date.now());
-};
+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 we should do kinetic animation.
+ * @param {Node} newNode Node to replace old node
+ * @param {Node} oldNode The node to be replaced
  */
-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;
+ol.dom.replaceNode = function(newNode, oldNode) {
+  var parent = oldNode.parentNode;
+  if (parent) {
+    parent.replaceChild(newNode, oldNode);
   }
-
-  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_;
 };
 
-
 /**
- * @return {number} Total distance travelled (pixels).
+ * @param {Node} node The node to remove.
+ * @returns {Node} The node that was removed or null.
  */
-ol.Kinetic.prototype.getDistance = function() {
-  return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
+ol.dom.removeNode = function(node) {
+  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
 };
 
-
 /**
- * @return {number} Angle of the kinetic panning animation (radians).
+ * @param {Node} node The node to remove the children from.
  */
-ol.Kinetic.prototype.getAngle = function() {
-  return this.angle_;
+ol.dom.removeChildren = function(node) {
+  while (node.lastChild) {
+    node.removeChild(node.lastChild);
+  }
 };
 
-goog.provide('ol.interaction.Property');
+goog.provide('ol.layer.Property');
 
 /**
  * @enum {string}
  */
-ol.interaction.Property = {
-  ACTIVE: 'active'
+ol.layer.Property = {
+  OPACITY: 'opacity',
+  VISIBLE: 'visible',
+  EXTENT: 'extent',
+  Z_INDEX: 'zIndex',
+  MAX_RESOLUTION: 'maxResolution',
+  MIN_RESOLUTION: 'minResolution',
+  SOURCE: 'source'
 };
 
-// FIXME factor out key precondition (shift et. al)
-
-goog.provide('ol.interaction.Interaction');
+goog.provide('ol.layer.Base');
 
 goog.require('ol');
 goog.require('ol.Object');
-goog.require('ol.easing');
-goog.require('ol.interaction.Property');
+goog.require('ol.layer.Property');
+goog.require('ol.math');
+goog.require('ol.obj');
 
 
 /**
  * @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.
+ * 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
- * @param {olx.interaction.InteractionOptions} options Options.
+ * @abstract
  * @extends {ol.Object}
+ * @param {olx.layer.BaseOptions} options Layer options.
  * @api
  */
-ol.interaction.Interaction = function(options) {
+ol.layer.Base = function(options) {
 
   ol.Object.call(this);
 
   /**
-   * @private
-   * @type {ol.Map}
+   * @type {Object.<string, *>}
    */
-  this.map_ = null;
+  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.setActive(true);
+  this.setProperties(properties);
 
   /**
-   * @type {function(ol.MapBrowserEvent):boolean}
+   * @type {ol.LayerState}
+   * @private
    */
-  this.handleEvent = options.handleEvent;
+  this.state_ = /** @type {ol.LayerState} */ ({
+    layer: /** @type {ol.layer.Layer} */ (this),
+    managed: true
+  });
+
+  /**
+   * The layer type.
+   * @type {ol.LayerType}
+   * @protected;
+   */
+  this.type;
 
 };
-ol.inherits(ol.interaction.Interaction, ol.Object);
+ol.inherits(ol.layer.Base, ol.Object);
 
 
 /**
- * Return whether the interaction is currently active.
- * @return {boolean} `true` if the interaction is active, `false` otherwise.
- * @observable
- * @api
+ * Get the layer type (used when creating a layer renderer).
+ * @return {ol.LayerType} The layer type.
  */
-ol.interaction.Interaction.prototype.getActive = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.interaction.Property.ACTIVE));
+ol.layer.Base.prototype.getType = function() {
+  return this.type;
 };
 
 
 /**
- * Get the map associated with this interaction.
- * @return {ol.Map} Map.
- * @api
+ * @return {ol.LayerState} Layer state.
  */
-ol.interaction.Interaction.prototype.getMap = function() {
-  return this.map_;
+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_;
 };
 
 
 /**
- * Activate or deactivate the interaction.
- * @param {boolean} active Active.
- * @observable
- * @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.interaction.Interaction.prototype.setActive = function(active) {
-  this.set(ol.interaction.Property.ACTIVE, active);
-};
+ol.layer.Base.prototype.getLayersArray = function(opt_array) {};
 
 
 /**
- * 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.
+ * @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.interaction.Interaction.prototype.setMap = function(map) {
-  this.map_ = map;
-};
+ol.layer.Base.prototype.getLayerStatesArray = function(opt_states) {};
 
 
 /**
- * @param {ol.View} view View.
- * @param {ol.Coordinate} delta Delta.
- * @param {number=} opt_duration Duration.
+ * 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.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);
-    }
-  }
+ol.layer.Base.prototype.getExtent = function() {
+  return /** @type {ol.Extent|undefined} */ (
+    this.get(ol.layer.Property.EXTENT));
 };
 
 
 /**
- * @param {ol.View} view View.
- * @param {number|undefined} rotation Rotation.
- * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
+ * Return the maximum resolution of the layer.
+ * @return {number} The maximum resolution of the layer.
+ * @observable
+ * @api
  */
-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);
+ol.layer.Base.prototype.getMaxResolution = function() {
+  return /** @type {number} */ (
+    this.get(ol.layer.Property.MAX_RESOLUTION));
 };
 
 
 /**
- * @param {ol.View} view View.
- * @param {number|undefined} rotation Rotation.
- * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
+ * Return the minimum resolution of the layer.
+ * @return {number} The minimum resolution of the layer.
+ * @observable
+ * @api
  */
-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);
-    }
-  }
+ol.layer.Base.prototype.getMinResolution = function() {
+  return /** @type {number} */ (
+    this.get(ol.layer.Property.MIN_RESOLUTION));
 };
 
 
 /**
- * @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.
+ * Return the opacity of the layer (between 0 and 1).
+ * @return {number} The opacity of the layer.
+ * @observable
+ * @api
  */
-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);
+ol.layer.Base.prototype.getOpacity = function() {
+  return /** @type {number} */ (this.get(ol.layer.Property.OPACITY));
 };
 
 
 /**
- * @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.
+ * @abstract
+ * @return {ol.source.State} Source state.
  */
-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);
+ol.layer.Base.prototype.getSourceState = function() {};
 
-    opt_anchor = [
-      (resolution * currentCenter[0] - currentResolution * center[0]) /
-          (resolution - currentResolution),
-      (resolution * currentCenter[1] - currentResolution * center[1]) /
-          (resolution - currentResolution)
-    ];
-  }
 
-  ol.interaction.Interaction.zoomWithoutConstraints(
-      view, resolution, opt_anchor, opt_duration);
+/**
+ * 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));
 };
 
 
 /**
- * @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.
+ * 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.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);
-    }
-  }
+ol.layer.Base.prototype.getZIndex = function() {
+  return /** @type {number} */ (this.get(ol.layer.Property.Z_INDEX));
 };
 
-goog.provide('ol.interaction.DoubleClickZoom');
-
-goog.require('ol');
-goog.require('ol.MapBrowserEventType');
-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.
+ * 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.interaction.DoubleClickZoom = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = options.delta ? options.delta : 1;
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.DoubleClickZoom.handleEvent
-  });
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-
+ol.layer.Base.prototype.setExtent = function(extent) {
+  this.set(ol.layer.Property.EXTENT, extent);
 };
-ol.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}
+ * Set the maximum resolution at which the layer is visible.
+ * @param {number} maxResolution The maximum resolution of the layer.
+ * @observable
  * @api
  */
-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;
+ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
+  this.set(ol.layer.Property.MAX_RESOLUTION, maxResolution);
 };
 
-goog.provide('ol.events.condition');
-
-goog.require('ol.MapBrowserEventType');
-goog.require('ol.asserts');
-goog.require('ol.functions');
-goog.require('ol.has');
-
 
 /**
- * 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.
+ * Set the minimum resolution at which the layer is visible.
+ * @param {number} minResolution The minimum resolution of the layer.
+ * @observable
  * @api
  */
-ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      !originalEvent.shiftKey);
+ol.layer.Base.prototype.setMinResolution = function(minResolution) {
+  this.set(ol.layer.Property.MIN_RESOLUTION, minResolution);
 };
 
 
 /**
- * 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.
+ * Set the opacity of the layer, allowed values range from 0 to 1.
+ * @param {number} opacity The opacity of the layer.
+ * @observable
  * @api
  */
-ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      originalEvent.shiftKey);
+ol.layer.Base.prototype.setOpacity = function(opacity) {
+  this.set(ol.layer.Property.OPACITY, opacity);
 };
 
 
 /**
- * Return always true.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True.
- * @function
+ * Set the visibility of the layer (`true` or `false`).
+ * @param {boolean} visible The visibility of the layer.
+ * @observable
  * @api
  */
-ol.events.condition.always = ol.functions.TRUE;
+ol.layer.Base.prototype.setVisible = function(visible) {
+  this.set(ol.layer.Property.VISIBLE, visible);
+};
 
 
 /**
- * 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.
+ * Set Z-index of the layer, which is used to order layers before rendering.
+ * The default Z-index is 0.
+ * @param {number} zindex The z-index of the layer.
+ * @observable
  * @api
  */
-ol.events.condition.click = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == ol.MapBrowserEventType.CLICK;
+ol.layer.Base.prototype.setZIndex = function(zindex) {
+  this.set(ol.layer.Property.Z_INDEX, zindex);
 };
 
+goog.provide('ol.source.State');
+
 
 /**
- * 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.
+ * State of the source, one of 'undefined', 'loading', 'ready' or 'error'.
+ * @enum {string}
  */
-ol.events.condition.mouseActionButton = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return originalEvent.button == 0 &&
-      !(ol.has.WEBKIT && ol.has.MAC && originalEvent.ctrlKey);
+ol.source.State = {
+  UNDEFINED: 'undefined',
+  LOADING: 'loading',
+  READY: 'ready',
+  ERROR: 'error'
 };
 
 
-/**
- * Return always false.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} False.
- * @function
- * @api
- */
-ol.events.condition.never = ol.functions.FALSE;
+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');
 
 
 /**
- * Return `true` if the browser event is a `pointermove` event, `false`
- * otherwise.
+ * @classdesc
+ * A {@link ol.Collection} of layers that are handled together.
  *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the browser event is a `pointermove` event.
+ * 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.events.condition.pointerMove = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == 'pointermove';
-};
+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 `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.events.condition.singleClick = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == ol.MapBrowserEventType.SINGLECLICK;
 };
+ol.inherits(ol.layer.Group, ol.layer.Base);
 
 
 /**
- * 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
+ * @private
  */
-ol.events.condition.doubleClick = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == ol.MapBrowserEventType.DBLCLICK;
+ol.layer.Group.prototype.handleLayerChange_ = function() {
+  this.changed();
 };
 
 
 /**
- * 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
+ * @param {ol.events.Event} event Event.
+ * @private
  */
-ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      !originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      !originalEvent.shiftKey);
+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();
 };
 
 
 /**
- * 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
+ * @param {ol.Collection.Event} collectionEvent Collection event.
+ * @private
  */
-ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      !originalEvent.altKey &&
-      (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) &&
-      !originalEvent.shiftKey);
+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 `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
+ * @param {ol.Collection.Event} collectionEvent Collection event.
+ * @private
  */
-ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      !originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      originalEvent.shiftKey);
+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();
 };
 
 
 /**
- * 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.
+ * 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
  */
-ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
-  var target = mapBrowserEvent.originalEvent.target;
-  var tagName = target.tagName;
-  return (
-      tagName !== 'INPUT' &&
-      tagName !== 'SELECT' &&
-      tagName !== 'TEXTAREA');
+ol.layer.Group.prototype.getLayers = function() {
+  return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get(
+      ol.layer.Group.Property_.LAYERS));
 };
 
 
 /**
- * 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.
+ * 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.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';
+ol.layer.Group.prototype.setLayers = function(layers) {
+  this.set(ol.layer.Group.Property_.LAYERS, layers);
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.events.condition.primaryAction = function(mapBrowserEvent) {
-  var pointerEvent = mapBrowserEvent.pointerEvent;
-  return pointerEvent.isPrimary && pointerEvent.button === 0;
+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;
 };
 
-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');
-
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.interaction.Pointer = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) {
+  var states = opt_states !== undefined ? opt_states : [];
 
-  var handleEvent = options.handleEvent ?
-      options.handleEvent : ol.interaction.Pointer.handleEvent;
+  var pos = states.length;
 
-  ol.interaction.Interaction.call(this, {
-    handleEvent: handleEvent
+  this.getLayers().forEach(function(layer) {
+    layer.getLayerStatesArray(states);
   });
 
-  /**
-   * @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 = [];
+  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;
 };
-ol.inherits(ol.interaction.Pointer, ol.interaction.Interaction);
 
 
 /**
- * @param {Array.<ol.pointer.PointerEvent>} pointerEvents List of events.
- * @return {ol.Pixel} Centroid pixel.
+ * @inheritDoc
  */
-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];
+ol.layer.Group.prototype.getSourceState = function() {
+  return ol.source.State.READY;
 };
 
-
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Whether the event is a pointerdown, pointerdrag
- *     or pointerup event.
+ * @enum {string}
  * @private
  */
-ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = function(mapBrowserEvent) {
-  var type = mapBrowserEvent.type;
-  return (
-      type === ol.MapBrowserEventType.POINTERDOWN ||
-      type === ol.MapBrowserEventType.POINTERDRAG ||
-      type === ol.MapBrowserEventType.POINTERUP);
+ol.layer.Group.Property_ = {
+  LAYERS: 'layers'
 };
 
+goog.provide('ol.PluginType');
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @private
+ * A plugin type used when registering a plugin.  The supported plugin types are
+ * 'MAP_RENDERER', and 'LAYER_RENDERER'.
+ * @enum {string}
  */
-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_);
-  }
+ol.PluginType = {
+  MAP_RENDERER: 'MAP_RENDERER',
+  LAYER_RENDERER: 'LAYER_RENDERER'
 };
 
+goog.provide('ol.plugins');
+
+goog.require('ol.PluginType');
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.Pointer}
+ * The registry of map renderer plugins.
+ * @type {Array<olx.MapRendererPlugin>}
+ * @private
  */
-ol.interaction.Pointer.handleDragEvent = ol.nullFunction;
+ol.plugins.mapRendererPlugins_ = [];
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Capture dragging.
- * @this {ol.interaction.Pointer}
+ * Get all registered map renderer plugins.
+ * @return {Array<olx.MapRendererPlugin>} The registered map renderer plugins.
  */
-ol.interaction.Pointer.handleUpEvent = ol.functions.FALSE;
+ol.plugins.getMapRendererPlugins = function() {
+  return ol.plugins.mapRendererPlugins_;
+};
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Capture dragging.
- * @this {ol.interaction.Pointer}
+ * The registry of layer renderer plugins.
+ * @type {Array<olx.LayerRendererPlugin>}
+ * @private
  */
-ol.interaction.Pointer.handleDownEvent = ol.functions.FALSE;
+ol.plugins.layerRendererPlugins_ = [];
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.Pointer}
+ * Get all registered layer renderer plugins.
+ * @return {Array<olx.LayerRendererPlugin>} The registered layer renderer plugins.
  */
-ol.interaction.Pointer.handleMoveEvent = ol.nullFunction;
+ol.plugins.getLayerRendererPlugins = function() {
+  return ol.plugins.layerRendererPlugins_;
+};
 
 
 /**
- * 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
+ * Register a plugin.
+ * @param {ol.PluginType} type The plugin type.
+ * @param {*} plugin The plugin.
  */
-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.MapBrowserEventType.POINTERDRAG) {
-      this.handleDragEvent_(mapBrowserEvent);
-    } else if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERUP) {
-      var handledUp = this.handleUpEvent_(mapBrowserEvent);
-      this.handlingDownUpSequence = handledUp && this.targetPointers.length > 0;
+ol.plugins.register = function(type, plugin) {
+  var plugins;
+  switch (type) {
+    case ol.PluginType.MAP_RENDERER: {
+      plugins = ol.plugins.mapRendererPlugins_;
+      plugins.push(/** @type {olx.MapRendererPlugin} */ (plugin));
+      break;
     }
-  } 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);
+    case ol.PluginType.LAYER_RENDERER: {
+      plugins = ol.plugins.layerRendererPlugins_;
+      plugins.push(/** @type {olx.LayerRendererPlugin} */ (plugin));
+      break;
+    }
+    default: {
+      throw new Error('Unsupported plugin type: ' + type);
     }
   }
-  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
+ * Register multiple plugins.
+ * @param {ol.PluginType} type The plugin type.
+ * @param {Array} plugins The plugins.
  */
-ol.interaction.Pointer.prototype.shouldStopEvent = function(handled) {
-  return handled;
+ol.plugins.registerMultiple = function(type, plugins) {
+  for (var i = 0, ii = plugins.length; i < ii; ++i) {
+    ol.plugins.register(type, plugins[i]);
+  }
 };
 
-goog.provide('ol.interaction.DragPan');
+goog.provide('ol.renderer.Type');
+
+
+/**
+ * Available renderers: `'canvas'` or `'webgl'`.
+ * @enum {string}
+ */
+ol.renderer.Type = {
+  CANVAS: 'canvas',
+  WEBGL: 'webgl'
+};
+
+goog.provide('ol.PluggableMap');
 
 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.coordinate');
-goog.require('ol.easing');
-goog.require('ol.events.condition');
+goog.require('ol.asserts');
+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.interaction.Pointer');
+goog.require('ol.has');
+goog.require('ol.layer.Group');
+goog.require('ol.obj');
+goog.require('ol.plugins');
+goog.require('ol.renderer.Type');
+goog.require('ol.size');
+goog.require('ol.structs.PriorityQueue');
+goog.require('ol.transform');
 
 
 /**
- * @classdesc
- * Allows the user to pan the map by dragging the map.
- *
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.DragPanOptions=} opt_options Options.
+ * @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
  */
-ol.interaction.DragPan = function(opt_options) {
+ol.PluggableMap = function(options) {
 
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.DragPan.handleDownEvent_,
-    handleDragEvent: ol.interaction.DragPan.handleDragEvent_,
-    handleUpEvent: ol.interaction.DragPan.handleUpEvent_
-  });
+  ol.Object.call(this);
 
-  var options = opt_options ? opt_options : {};
+  var optionsInternal = ol.PluggableMap.createOptionsInternal(options);
 
   /**
+   * @type {boolean}
    * @private
-   * @type {ol.Kinetic|undefined}
    */
-  this.kinetic_ = options.kinetic;
+  this.loadTilesWhileAnimating_ =
+      options.loadTilesWhileAnimating !== undefined ?
+        options.loadTilesWhileAnimating : false;
 
   /**
-   * @type {ol.Pixel}
+   * @type {boolean}
+   * @private
    */
-  this.lastCentroid = null;
+  this.loadTilesWhileInteracting_ =
+      options.loadTilesWhileInteracting !== undefined ?
+        options.loadTilesWhileInteracting : false;
 
   /**
+   * @private
    * @type {number}
    */
-  this.lastPointersCount_;
+  this.pixelRatio_ = options.pixelRatio !== undefined ?
+    options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;
 
   /**
    * @private
-   * @type {ol.EventsConditionType}
+   * @type {Object.<string, string>}
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.noModifierKeys;
+  this.logos_ = optionsInternal.logos;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {number|undefined}
    */
-  this.noKinetic_ = false;
+  this.animationDelayKey_;
 
-};
-ol.inherits(ol.interaction.DragPan, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragPan}
- * @private
- */
-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();
-  }
-  this.lastCentroid = centroid;
-  this.lastPointersCount_ = targetPointers.length;
-};
-
-
-/**
- * @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 = /** @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;
-  }
-};
-
-
-/**
- * @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);
-    }
-    // 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;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-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');
-
-
-/**
- * @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.interaction.DragRotate = function(opt_options) {
-
-  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
+   */
+  this.animationDelay_ = function() {
+    this.animationDelayKey_ = undefined;
+    this.renderFrame_.call(this, Date.now());
+  }.bind(this);
 
   /**
    * @private
-   * @type {ol.EventsConditionType}
+   * @type {ol.Transform}
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.altShiftKeysOnly;
+  this.coordinateToPixelTransform_ = ol.transform.create();
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {ol.Transform}
    */
-  this.lastAngle_ = undefined;
+  this.pixelToCoordinateTransform_ = ol.transform.create();
 
   /**
    * @private
    * @type {number}
    */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-};
-ol.inherits(ol.interaction.DragRotate, ol.interaction.Pointer);
+  this.frameIndex_ = 0;
 
+  /**
+   * @private
+   * @type {?olx.FrameState}
+   */
+  this.frameState_ = null;
 
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragRotate}
- * @private
- */
-ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return;
-  }
+  /**
+   * The extent at the previous 'moveend' event.
+   * @private
+   * @type {ol.Extent}
+   */
+  this.previousExtent_ = null;
 
-  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;
-};
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.viewPropertyListenerKey_ = null;
 
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.viewChangeListenerKey_ = null;
 
-/**
- * @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;
-  }
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.layerGroupPropertyListenerKeys_ = null;
 
-  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;
-};
+  /**
+   * @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';
 
+  /**
+   * @private
+   * @type {!Element}
+   */
+  this.overlayContainer_ = document.createElement('DIV');
+  this.overlayContainer_.className = 'ol-overlaycontainer';
+  this.viewport_.appendChild(this.overlayContainer_);
 
-/**
- * @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;
+  /**
+   * @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_);
 
-  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;
+  /**
+   * @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);
   }
-};
 
+  /**
+   * @private
+   * @type {Element|Document}
+   */
+  this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
 
-/**
- * @inheritDoc
- */
-ol.interaction.DragRotate.prototype.shouldStopEvent = ol.functions.FALSE;
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.keyHandlerKeys_ = null;
 
-// FIXME add rotation
+  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.provide('ol.render.Box');
+  /**
+   * @type {ol.Collection.<ol.control.Control>}
+   * @protected
+   */
+  this.controls = optionsInternal.controls || new ol.Collection();
 
-goog.require('ol');
-goog.require('ol.Disposable');
-goog.require('ol.geom.Polygon');
+  /**
+   * @type {ol.Collection.<ol.interaction.Interaction>}
+   * @protected
+   */
+  this.interactions = optionsInternal.interactions || new ol.Collection();
 
+  /**
+   * @type {ol.Collection.<ol.Overlay>}
+   * @private
+   */
+  this.overlays_ = optionsInternal.overlays;
 
-/**
- * @constructor
- * @extends {ol.Disposable}
- * @param {string} className CSS class name.
- */
-ol.render.Box = function(className) {
+  /**
+   * A lookup of overlays by id.
+   * @private
+   * @type {Object.<string, ol.Overlay>}
+   */
+  this.overlayIdIndex_ = {};
 
   /**
-   * @type {ol.geom.Polygon}
+   * @type {ol.renderer.Map}
    * @private
    */
-  this.geometry_ = null;
+  this.renderer_ = optionsInternal.mapRendererPlugin['create'](this.viewport_, this);
 
   /**
-   * @type {HTMLDivElement}
+   * @type {function(Event)|undefined}
    * @private
    */
-  this.element_ = /** @type {HTMLDivElement} */ (document.createElement('div'));
-  this.element_.style.position = 'absolute';
-  this.element_.className = 'ol-box ' + className;
+  this.handleResize_;
 
   /**
    * @private
-   * @type {ol.Map}
+   * @type {ol.Coordinate}
    */
-  this.map_ = null;
+  this.focus_ = null;
 
   /**
    * @private
-   * @type {ol.Pixel}
+   * @type {Array.<ol.PostRenderFunction>}
    */
-  this.startPixel_ = null;
+  this.postRenderFunctions_ = [];
 
   /**
    * @private
-   * @type {ol.Pixel}
+   * @type {ol.TileQueue}
    */
-  this.endPixel_ = null;
+  this.tileQueue_ = new ol.TileQueue(
+      this.getTilePriority.bind(this),
+      this.handleTileChange_.bind(this));
+
+  /**
+   * Uids of features to skip at rendering time.
+   * @type {Object.<string, boolean>}
+   * @private
+   */
+  this.skippedFeatureUids_ = {};
+
+  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);
+
+  // 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.PluggableMap}
+       */
+      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);
+
+  ol.events.listen(this.controls, ol.CollectionEventType.REMOVE,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(null);
+      }, this);
+
+  this.interactions.forEach(
+      /**
+       * @param {ol.interaction.Interaction} interaction Interaction.
+       * @this {ol.PluggableMap}
+       */
+      function(interaction) {
+        interaction.setMap(this);
+      }, this);
+
+  ol.events.listen(this.interactions, ol.CollectionEventType.ADD,
+      /**
+       * @param {ol.Collection.Event} event Collection event.
+       */
+      function(event) {
+        event.element.setMap(this);
+      }, this);
+
+  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);
+
+  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);
+
+  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);
 
 };
-ol.inherits(ol.render.Box, ol.Disposable);
+ol.inherits(ol.PluggableMap, ol.Object);
 
 
 /**
- * @inheritDoc
+ * Add the given control to the map.
+ * @param {ol.control.Control} control Control.
+ * @api
  */
-ol.render.Box.prototype.disposeInternal = function() {
-  this.setMap(null);
+ol.PluggableMap.prototype.addControl = function(control) {
+  this.getControls().push(control);
 };
 
 
 /**
- * @private
+ * Add the given interaction to the map.
+ * @param {ol.interaction.Interaction} interaction Interaction to add.
+ * @api
  */
-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;
+ol.PluggableMap.prototype.addInteraction = function(interaction) {
+  this.getInteractions().push(interaction);
 };
 
 
 /**
- * @param {ol.Map} map Map.
+ * 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
  */
-ol.render.Box.prototype.setMap = function(map) {
-  if (this.map_) {
-    this.map_.getOverlayContainer().removeChild(this.element_);
-    var style = this.element_.style;
-    style.left = style.top = style.width = style.height = 'inherit';
-  }
-  this.map_ = map;
-  if (this.map_) {
-    this.map_.getOverlayContainer().appendChild(this.element_);
-  }
+ol.PluggableMap.prototype.addLayer = function(layer) {
+  var layers = this.getLayerGroup().getLayers();
+  layers.push(layer);
 };
 
 
 /**
- * @param {ol.Pixel} startPixel Start pixel.
- * @param {ol.Pixel} endPixel End pixel.
+ * Add the given overlay to the map.
+ * @param {ol.Overlay} overlay Overlay.
+ * @api
  */
-ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
-  this.startPixel_ = startPixel;
-  this.endPixel_ = endPixel;
-  this.createOrUpdateGeometry();
-  this.render_();
+ol.PluggableMap.prototype.addOverlay = function(overlay) {
+  this.getOverlays().push(overlay);
 };
 
 
 /**
- * Creates or updates the cached geometry.
+ * This deals with map's overlay collection changes.
+ * @param {ol.Overlay} overlay Overlay.
+ * @private
  */
-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]);
+ol.PluggableMap.prototype.addOverlayInternal_ = function(overlay) {
+  var id = overlay.getId();
+  if (id !== undefined) {
+    this.overlayIdIndex_[id.toString()] = overlay;
   }
+  overlay.setMap(this);
 };
 
 
 /**
- * @return {ol.geom.Polygon} Geometry.
+ *
+ * @inheritDoc
  */
-ol.render.Box.prototype.getGeometry = function() {
-  return this.geometry_;
+ol.PluggableMap.prototype.disposeInternal = function() {
+  this.mapBrowserEventHandler_.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);
 };
 
-// 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
- * 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.interaction.DragBox.Event
- * @param {olx.interaction.DragBoxOptions=} opt_options Options.
+ * 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 the `layerFilter` option in `opt_options`.
+ * @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
  */
-ol.interaction.DragBox = function(opt_options) {
-
-  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.minArea_ = options.minArea !== undefined ? options.minArea : 64;
-
-  /**
-   * @type {ol.Pixel}
-   * @private
-   */
-  this.startPixel_ = null;
-
-  /**
-   * @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;
+ol.PluggableMap.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);
 };
-ol.inherits(ol.interaction.DragBox, ol.interaction.Pointer);
 
 
 /**
- * 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}
+ * Get all features that intersect a pixel on the viewport.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {olx.AtPixelOptions=} opt_options Optional options.
+ * @return {Array.<ol.Feature|ol.render.Feature>} The detected features or
+ * `null` if none were found.
+ * @api
  */
-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_;
+ol.PluggableMap.prototype.getFeaturesAtPixel = function(pixel, opt_options) {
+  var features = null;
+  this.forEachFeatureAtPixel(pixel, function(feature) {
+    if (!features) {
+      features = [];
+    }
+    features.push(feature);
+  }, opt_options);
+  return features;
 };
 
-
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragBox}
- * @private
+ * 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
  */
-ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+ol.PluggableMap.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);
+};
 
-  this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);
 
-  this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType_.BOXDRAG,
-    mapBrowserEvent.coordinate, mapBrowserEvent));
+/**
+ * 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
+ */
+ol.PluggableMap.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);
 };
 
 
 /**
- * Returns geometry of last drawn box.
- * @return {ol.geom.Polygon} Geometry.
+ * Returns the coordinate in view projection for a browser event.
+ * @param {Event} event Event.
+ * @return {ol.Coordinate} Coordinate.
  * @api
  */
-ol.interaction.DragBox.prototype.getGeometry = function() {
-  return this.box_.getGeometry();
+ol.PluggableMap.prototype.getEventCoordinate = function(event) {
+  return this.getCoordinateFromPixel(this.getEventPixel(event));
 };
 
 
 /**
- * To be overridden by child classes.
- * FIXME: use constructor option instead of relying on overriding.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @protected
+ * Returns the map pixel position for a browser event relative to the viewport.
+ * @param {Event} event Event.
+ * @return {ol.Pixel} Pixel.
+ * @api
  */
-ol.interaction.DragBox.prototype.onBoxEnd = ol.nullFunction;
+ol.PluggableMap.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
+  ];
+};
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.DragBox}
- * @private
+ * 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
  */
-ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
-  }
+ol.PluggableMap.prototype.getTarget = function() {
+  return /** @type {Element|string|undefined} */ (
+    this.get(ol.MapProperty.TARGET));
+};
 
-  this.box_.setMap(null);
 
-  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));
+/**
+ * Get the DOM element into which this map is rendered. In contrast to
+ * `getTarget` this method always return an `Element`, or `null` if the
+ * map has no target.
+ * @return {Element} The element that the map is rendered in.
+ * @api
+ */
+ol.PluggableMap.prototype.getTargetElement = function() {
+  var target = this.getTarget();
+  if (target !== undefined) {
+    return typeof target === 'string' ?
+      document.getElementById(target) :
+      target;
+  } else {
+    return null;
   }
-  return false;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.DragBox}
- * @private
+ * Get the coordinate for a given pixel.  This returns a coordinate in the
+ * map view projection.
+ * @param {ol.Pixel} pixel Pixel position in the map viewport.
+ * @return {ol.Coordinate} The coordinate for the pixel position.
+ * @api
  */
-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;
+ol.PluggableMap.prototype.getCoordinateFromPixel = function(pixel) {
+  var frameState = this.frameState_;
+  if (!frameState) {
+    return null;
   } else {
-    return false;
+    return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice());
   }
 };
 
 
 /**
- * @enum {string}
- * @private
+ * Get the map controls. Modifying this collection changes the controls
+ * associated with the map.
+ * @return {ol.Collection.<ol.control.Control>} Controls.
+ * @api
  */
-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'
+ol.PluggableMap.prototype.getControls = function() {
+  return this.controls;
 };
 
 
 /**
- * @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.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Originating event.
- * @extends {ol.events.Event}
- * @constructor
- * @implements {oli.DragBoxEvent}
+ * Get the map overlays. Modifying this collection changes the overlays
+ * associated with the map.
+ * @return {ol.Collection.<ol.Overlay>} Overlays.
+ * @api
  */
-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;
-
+ol.PluggableMap.prototype.getOverlays = function() {
+  return this.overlays_;
 };
-ol.inherits(ol.interaction.DragBox.Event, ol.events.Event);
 
-goog.provide('ol.interaction.DragZoom');
 
-goog.require('ol');
-goog.require('ol.easing');
-goog.require('ol.events.condition');
-goog.require('ol.extent');
-goog.require('ol.interaction.DragBox');
+/**
+ * Get an overlay by its identifier (the value returned by overlay.getId()).
+ * Note that the index treats string and numeric identifiers as the same. So
+ * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`.
+ * @param {string|number} id Overlay identifier.
+ * @return {ol.Overlay} Overlay.
+ * @api
+ */
+ol.PluggableMap.prototype.getOverlayById = function(id) {
+  var overlay = this.overlayIdIndex_[id.toString()];
+  return overlay !== undefined ? overlay : null;
+};
 
 
 /**
- * @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`.
+ * Get the map interactions. Modifying this collection changes the interactions
+ * associated with the map.
  *
- * @constructor
- * @extends {ol.interaction.DragBox}
- * @param {olx.interaction.DragZoomOptions=} opt_options Options.
+ * Interactions are used for e.g. pan, zoom and rotate.
+ * @return {ol.Collection.<ol.interaction.Interaction>} Interactions.
  * @api
  */
-ol.interaction.DragZoom = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  var condition = options.condition ?
-      options.condition : ol.events.condition.shiftKeyOnly;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 200;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.out_ = options.out !== undefined ? options.out : false;
-
-  ol.interaction.DragBox.call(this, {
-    condition: condition,
-    className: options.className || 'ol-dragzoom'
-  });
-
+ol.PluggableMap.prototype.getInteractions = function() {
+  return this.interactions;
 };
-ol.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
 
 
 /**
- * @inheritDoc
+ * Get the layergroup associated with this map.
+ * @return {ol.layer.Group} A layer group containing the layers in this map.
+ * @observable
+ * @api
  */
-ol.interaction.DragZoom.prototype.onBoxEnd = function() {
-  var map = this.getMap();
-
-  var view = /** @type {!ol.View} */ (map.getView());
+ol.PluggableMap.prototype.getLayerGroup = function() {
+  return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
+};
 
-  var size = /** @type {!ol.Size} */ (map.getSize());
 
-  var extent = this.getGeometry().getExtent();
+/**
+ * Get the collection of layers associated with this map.
+ * @return {!ol.Collection.<ol.layer.Base>} Layers.
+ * @api
+ */
+ol.PluggableMap.prototype.getLayers = function() {
+  var layers = this.getLayerGroup().getLayers();
+  return layers;
+};
 
-  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);
 
-    ol.extent.scaleFromCenter(mapExtent, 1 / factor);
-    extent = mapExtent;
+/**
+ * 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
+ */
+ol.PluggableMap.prototype.getPixelFromCoordinate = function(coordinate) {
+  var frameState = this.frameState_;
+  if (!frameState) {
+    return null;
+  } else {
+    return ol.transform.apply(frameState.coordinateToPixelTransform,
+        coordinate.slice(0, 2));
   }
+};
 
-  var resolution = view.constrainResolution(
-      view.getResolutionForExtent(extent, size));
 
-  var center = ol.extent.getCenter(extent);
-  center = view.constrainCenter(center);
+/**
+ * Get the map renderer.
+ * @return {ol.renderer.Map} Renderer
+ */
+ol.PluggableMap.prototype.getRenderer = function() {
+  return this.renderer_;
+};
 
-  view.animate({
-    resolution: resolution,
-    center: center,
-    duration: this.duration_,
-    easing: ol.easing.easeOut
-  });
 
+/**
+ * Get the size of this map.
+ * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.getSize = function() {
+  return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
 };
 
-goog.provide('ol.events.KeyCode');
 
 /**
- * @enum {number}
- * @const
+ * 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
  */
-ol.events.KeyCode = {
-  LEFT: 37,
-  UP: 38,
-  RIGHT: 39,
-  DOWN: 40
+ol.PluggableMap.prototype.getView = function() {
+  return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
 };
 
-goog.provide('ol.interaction.KeyboardPan');
 
-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');
+/**
+ * Get the element that serves as the map viewport.
+ * @return {Element} Viewport.
+ * @api
+ */
+ol.PluggableMap.prototype.getViewport = function() {
+  return this.viewport_;
+};
 
 
 /**
- * @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
+ * Get the element that serves as the container for overlays.  Elements added to
+ * this container will let mousedown and touchstart events through to the map,
+ * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent}
+ * events.
+ * @return {!Element} The map's overlay container.
  */
-ol.interaction.KeyboardPan = function(opt_options) {
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.KeyboardPan.handleEvent
-  });
+ol.PluggableMap.prototype.getOverlayContainer = function() {
+  return this.overlayContainer_;
+};
 
-  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);
-  };
+/**
+ * Get the element that serves as a container for overlays that don't allow
+ * event propagation. Elements added to this container won't let mousedown and
+ * touchstart events through to the map, so clicks and gestures on an overlay
+ * don't trigger any {@link ol.MapBrowserEvent}.
+ * @return {!Element} The map's overlay container that stops events.
+ */
+ol.PluggableMap.prototype.getOverlayContainerStopEvent = function() {
+  return this.overlayContainerStopEvent_;
+};
 
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition !== undefined ?
-      options.condition : this.defaultCondition_;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 100;
+/**
+ * @param {ol.Tile} tile Tile.
+ * @param {string} tileSourceKey Tile source key.
+ * @param {ol.Coordinate} tileCenter Tile center.
+ * @param {number} tileResolution Tile resolution.
+ * @return {number} Tile priority.
+ */
+ol.PluggableMap.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;
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelDelta_ = options.pixelDelta !== undefined ?
-      options.pixelDelta : 128;
 
+/**
+ * @param {Event} browserEvent Browser event.
+ * @param {string=} opt_type Type.
+ */
+ol.PluggableMap.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
+  var type = opt_type || browserEvent.type;
+  var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent);
+  this.handleMapBrowserEvent(mapBrowserEvent);
 };
-ol.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
+ * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
  */
-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;
+ol.PluggableMap.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 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;
       }
-      var delta = [deltaX, deltaY];
-      ol.coordinate.rotate(delta, view.getRotation());
-      ol.interaction.Interaction.pan(view, delta, this.duration_);
-      mapBrowserEvent.preventDefault();
-      stopEvent = true;
     }
   }
-  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');
-
 
 /**
- * @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
+ * @protected
  */
-ol.interaction.KeyboardZoom = function(opt_options) {
+ol.PluggableMap.prototype.handlePostRender = function() {
 
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.KeyboardZoom.handleEvent
-  });
-
-  var options = opt_options ? opt_options : {};
+  var frameState = this.frameState_;
 
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ? options.condition :
-          ol.events.condition.targetNotEditable;
+  // 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);
+    }
+  }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = options.delta ? options.delta : 1;
+  var postRenderFunctions = this.postRenderFunctions_;
+  var i, ii;
+  for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
+    postRenderFunctions[i](this, frameState);
+  }
+  postRenderFunctions.length = 0;
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 100;
 
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleSizeChanged_ = function() {
+  this.render();
 };
-ol.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
+ * @private
  */
-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;
+ol.PluggableMap.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();
+  }
+
+  if (this.keyHandlerKeys_) {
+    for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
+      ol.events.unlistenByKey(this.keyHandlerKeys_[i]);
     }
+    this.keyHandlerKeys_ = null;
   }
-  return !stopEvent;
-};
 
-goog.provide('ol.interaction.MouseWheelZoom');
+  if (!targetElement) {
+    this.renderer_.removeLayerRenderers();
+    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_);
 
-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');
+    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)
+    ];
+
+    if (!this.handleResize_) {
+      this.handleResize_ = this.updateSize.bind(this);
+      window.addEventListener(ol.events.EventType.RESIZE,
+          this.handleResize_, false);
+    }
+  }
+
+  this.updateSize();
+  // updateSize calls setSize, so no need to call this.render
+  // ourselves here.
+};
 
 
 /**
- * @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
+ * @private
  */
-ol.interaction.MouseWheelZoom = function(opt_options) {
+ol.PluggableMap.prototype.handleTileChange_ = function() {
+  this.render();
+};
 
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.MouseWheelZoom.handleEvent
-  });
 
-  var options = opt_options || {};
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleViewPropertyChanged_ = function() {
+  this.render();
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = 0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
+/**
+ * @private
+ */
+ol.PluggableMap.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();
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.timeout_ = options.timeout !== undefined ? options.timeout : 80;
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;
+/**
+ * @private
+ */
+ol.PluggableMap.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();
+};
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.constrainResolution_ = options.constrainResolution || false;
 
-  /**
-   * @private
-   * @type {?ol.Coordinate}
-   */
-  this.lastAnchor_ = null;
+/**
+ * @return {boolean} Is rendered.
+ */
+ol.PluggableMap.prototype.isRendered = function() {
+  return !!this.frameState_;
+};
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.startTime_ = undefined;
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.timeoutId_ = undefined;
+/**
+ * Requests an immediate render in a synchronous manner.
+ * @api
+ */
+ol.PluggableMap.prototype.renderSync = function() {
+  if (this.animationDelayKey_) {
+    cancelAnimationFrame(this.animationDelayKey_);
+  }
+  this.animationDelay_();
+};
 
-  /**
-   * @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;
+/**
+ * Request a map rendering (at the next animation frame).
+ * @api
+ */
+ol.PluggableMap.prototype.render = function() {
+  if (this.animationDelayKey_ === undefined) {
+    this.animationDelayKey_ = requestAnimationFrame(
+        this.animationDelay_);
+  }
+};
 
-  /**
-   * @type {number|undefined}
-   */
-  this.trackpadTimeoutId_ = undefined;
 
-  /**
-   * The number of delta values per zoom level
-   * @private
-   * @type {number}
-   */
-  this.trackpadDeltaPerZoom_ = 300;
+/**
+ * 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
+ */
+ol.PluggableMap.prototype.removeControl = function(control) {
+  return this.getControls().remove(control);
+};
 
-  /**
-   * The zoom factor by which scroll zooming is allowed to exceed the limits.
-   * @private
-   * @type {number}
-   */
-  this.trackpadZoomBuffer_ = 1.5;
 
+/**
+ * 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
+ */
+ol.PluggableMap.prototype.removeInteraction = function(interaction) {
+  return this.getInteractions().remove(interaction);
 };
-ol.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} Allow event propagation.
- * @this {ol.interaction.MouseWheelZoom}
+ * 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
  */
-ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) {
-  var type = mapBrowserEvent.type;
-  if (type !== ol.events.EventType.WHEEL && type !== ol.events.EventType.MOUSEWHEEL) {
-    return true;
-  }
+ol.PluggableMap.prototype.removeLayer = function(layer) {
+  var layers = this.getLayerGroup().getLayers();
+  return layers.remove(layer);
+};
 
-  mapBrowserEvent.preventDefault();
 
-  var map = mapBrowserEvent.map;
-  var wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent);
+/**
+ * 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
+ */
+ol.PluggableMap.prototype.removeOverlay = function(overlay) {
+  return this.getOverlays().remove(overlay);
+};
 
-  if (this.useAnchor_) {
-    this.lastAnchor_ = mapBrowserEvent.coordinate;
-  }
 
-  // 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;
-    }
-  }
+/**
+ * @param {number} time Time.
+ * @private
+ */
+ol.PluggableMap.prototype.renderFrame_ = function(time) {
+  var i, ii, viewState;
 
-  if (delta === 0) {
-    return false;
+  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];
+    }
+    viewState = view.getState();
+    var center = viewState.center;
+    var pixelResolution = viewState.resolution / this.pixelRatio_;
+    center[0] = Math.round(center[0] / pixelResolution) * pixelResolution;
+    center[1] = Math.round(center[1] / pixelResolution) * pixelResolution;
+    frameState = /** @type {olx.FrameState} */ ({
+      animate: false,
+      coordinateToPixelTransform: this.coordinateToPixelTransform_,
+      extent: extent,
+      focus: !this.focus_ ? 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: {}
+    });
   }
 
-  var now = Date.now();
-
-  if (this.startTime_ === undefined) {
-    this.startTime_ = now;
+  if (frameState) {
+    frameState.extent = ol.extent.getForViewAndSize(viewState.center,
+        viewState.resolution, viewState.rotation, frameState.size, extent);
   }
 
-  if (!this.mode_ || now - this.startTime_ > this.trackpadEventGap_) {
-    this.mode_ = Math.abs(delta) < 4 ?
-        ol.interaction.MouseWheelZoom.Mode_.TRACKPAD :
-        ol.interaction.MouseWheelZoom.Mode_.WHEEL;
-  }
+  this.frameState_ = frameState;
+  this.renderer_.renderFrame(frameState);
 
-  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));
+  if (frameState) {
+    if (frameState.animate) {
+      this.render();
     }
-    view.setResolution(resolution);
+    Array.prototype.push.apply(
+        this.postRenderFunctions_, frameState.postRenderFunctions);
 
-    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_
-      });
+    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_);
+      }
     }
 
-    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
-      });
+    var idle = this.previousExtent_ &&
+        !frameState.viewHints[ol.ViewHint.ANIMATING] &&
+        !frameState.viewHints[ol.ViewHint.INTERACTING] &&
+        !ol.extent.equals(frameState.extent, this.previousExtent_);
+
+    if (idle) {
+      this.dispatchEvent(
+          new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
+      ol.extent.clone(frameState.extent, this.previousExtent_);
     }
-    this.startTime_ = now;
-    return false;
   }
 
-  this.delta_ += delta;
-
-  var timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0);
+  this.dispatchEvent(
+      new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));
 
-  clearTimeout(this.timeoutId_);
-  this.timeoutId_ = setTimeout(this.handleWheelZoom_.bind(this, map), timeLeft);
+  setTimeout(this.handlePostRender.bind(this), 0);
 
-  return false;
 };
 
 
 /**
- * @private
+ * Sets the layergroup of this map.
+ * @param {ol.layer.Group} layerGroup A layer group containing the layers in
+ *     this map.
+ * @observable
+ * @api
  */
-ol.interaction.MouseWheelZoom.prototype.decrementInteractingHint_ = function() {
-  this.trackpadTimeoutId_ = undefined;
-  var view = this.getMap().getView();
-  view.setHint(ol.ViewHint.INTERACTING, -1);
+ol.PluggableMap.prototype.setLayerGroup = function(layerGroup) {
+  this.set(ol.MapProperty.LAYERGROUP, layerGroup);
 };
 
 
 /**
- * @private
- * @param {ol.Map} map Map.
+ * Set the size of this map.
+ * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
+ * @observable
+ * @api
  */
-ol.interaction.MouseWheelZoom.prototype.handleWheelZoom_ = function(map) {
-  var view = map.getView();
-  if (view.getAnimating()) {
-    view.cancelAnimations();
-  }
-  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;
+ol.PluggableMap.prototype.setSize = function(size) {
+  this.set(ol.MapProperty.SIZE, size);
 };
 
 
 /**
- * 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
+ * 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
  */
-ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) {
-  this.useAnchor_ = useAnchor;
-  if (!useAnchor) {
-    this.lastAnchor_ = null;
-  }
+ol.PluggableMap.prototype.setTarget = function(target) {
+  this.set(ol.MapProperty.TARGET, target);
 };
 
 
 /**
- * @enum {string}
- * @private
+ * Set the view for this map.
+ * @param {ol.View} view The view that controls this map.
+ * @observable
+ * @api
  */
-ol.interaction.MouseWheelZoom.Mode_ = {
-  TRACKPAD: 'trackpad',
-  WHEEL: 'wheel'
+ol.PluggableMap.prototype.setView = function(view) {
+  this.set(ol.MapProperty.VIEW, view);
 };
 
-goog.provide('ol.interaction.PinchRotate');
 
-goog.require('ol');
-goog.require('ol.ViewHint');
-goog.require('ol.functions');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.RotationConstraint');
+/**
+ * @param {ol.Feature} feature Feature.
+ */
+ol.PluggableMap.prototype.skipFeature = function(feature) {
+  var featureUid = ol.getUid(feature).toString();
+  this.skippedFeatureUids_[featureUid] = true;
+  this.render();
+};
 
 
 /**
- * @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.
+ * Force a recalculation of the map viewport size.  This should be called when
+ * third-party code changes the size of the map viewport.
  * @api
  */
-ol.interaction.PinchRotate = function(opt_options) {
+ol.PluggableMap.prototype.updateSize = function() {
+  var targetElement = this.getTargetElement();
 
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_,
-    handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_,
-    handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_
-  });
+  if (!targetElement) {
+    this.setSize(undefined);
+  } else {
+    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'])
+    ]);
+  }
+};
 
-  var options = opt_options || {};
 
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.anchor_ = null;
+/**
+ * @param {ol.Feature} feature Feature.
+ */
+ol.PluggableMap.prototype.unskipFeature = function(feature) {
+  var featureUid = ol.getUid(feature).toString();
+  delete this.skippedFeatureUids_[featureUid];
+  this.render();
+};
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastAngle_ = undefined;
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.rotating_ = false;
+/**
+ * @type {Array.<ol.renderer.Type>}
+ * @const
+ */
+ol.PluggableMap.DEFAULT_RENDERER_TYPES = [
+  ol.renderer.Type.CANVAS,
+  ol.renderer.Type.WEBGL
+];
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.rotationDelta_ = 0.0;
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.PluggableMap.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';
+
+
+/**
+ * @param {olx.MapOptions} options Map options.
+ * @return {ol.MapOptionsInternal} Internal map options.
+ */
+ol.PluggableMap.createOptionsInternal = function(options) {
 
   /**
-   * @private
-   * @type {number}
+   * @type {Element|Document}
    */
-  this.threshold_ = options.threshold !== undefined ? options.threshold : 0.3;
+  var keyboardEventTarget = null;
+  if (options.keyboardEventTarget !== undefined) {
+    keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ?
+      document.getElementById(options.keyboardEventTarget) :
+      options.keyboardEventTarget;
+  }
 
   /**
-   * @private
-   * @type {number}
+   * @type {Object.<string, *>}
    */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
+  var values = {};
 
-};
-ol.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);
+  var logos = {};
+  if (options.logo === undefined ||
+      (typeof options.logo === 'boolean' && options.logo)) {
+    logos[ol.PluggableMap.LOGO_URL] = 'https://openlayers.org/';
+  } 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;
+    }
+  }
 
+  var layerGroup = (options.layers instanceof ol.layer.Group) ?
+    options.layers : new ol.layer.Group({layers: options.layers});
+  values[ol.MapProperty.LAYERGROUP] = layerGroup;
 
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.PinchRotate}
- * @private
- */
-ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) {
-  var rotationDelta = 0.0;
+  values[ol.MapProperty.TARGET] = options.target;
 
-  var touch0 = this.targetPointers[0];
-  var touch1 = this.targetPointers[1];
+  values[ol.MapProperty.VIEW] = options.view !== undefined ?
+    options.view : new ol.View();
 
-  // angle between touches
-  var angle = Math.atan2(
-      touch1.clientY - touch0.clientY,
-      touch1.clientX - touch0.clientX);
+  /**
+   * @type {Array.<ol.renderer.Type>}
+   */
+  var rendererTypes;
 
-  if (this.lastAngle_ !== undefined) {
-    var delta = angle - this.lastAngle_;
-    this.rotationDelta_ += delta;
-    if (!this.rotating_ &&
-        Math.abs(this.rotationDelta_) > this.threshold_) {
-      this.rotating_ = true;
+  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
     }
-    rotationDelta = delta;
-  }
-  this.lastAngle_ = angle;
-
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  if (view.getConstraints().rotation === ol.RotationConstraint.disable) {
-    return;
+    if (rendererTypes.indexOf(/** @type {ol.renderer.Type} */ ('dom')) >= 0) {
+      rendererTypes = rendererTypes.concat(ol.PluggableMap.DEFAULT_RENDERER_TYPES);
+    }
+  } else {
+    rendererTypes = ol.PluggableMap.DEFAULT_RENDERER_TYPES;
   }
 
-  // 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);
+  /**
+   * @type {olx.MapRendererPlugin}
+   */
+  var mapRendererPlugin;
 
-  // rotate
-  if (this.rotating_) {
-    var rotation = view.getRotation();
-    map.render();
-    ol.interaction.Interaction.rotateWithoutConstraints(view,
-        rotation + rotationDelta, this.anchor_);
+  var mapRendererPlugins = ol.plugins.getMapRendererPlugins();
+  outer: for (var i = 0, ii = rendererTypes.length; i < ii; ++i) {
+    var rendererType = rendererTypes[i];
+    for (var j = 0, jj = mapRendererPlugins.length; j < jj; ++j) {
+      var candidate = mapRendererPlugins[j];
+      if (candidate['handles'](rendererType)) {
+        mapRendererPlugin = candidate;
+        break outer;
+      }
+    }
   }
-};
 
+  if (!mapRendererPlugin) {
+    throw new Error('Unable to create a map renderer for types: ' +  rendererTypes.join(', '));
+  }
 
-/**
- * @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(
-          view, rotation, this.anchor_, this.duration_);
+  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;
     }
-    return false;
-  } else {
-    return true;
   }
-};
 
+  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;
+    }
+  }
 
-/**
- * @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);
+  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;
     }
-    return true;
   } else {
-    return false;
+    overlays = new ol.Collection();
   }
-};
 
+  return {
+    controls: controls,
+    interactions: interactions,
+    keyboardEventTarget: keyboardEventTarget,
+    logos: logos,
+    overlays: overlays,
+    mapRendererPlugin: mapRendererPlugin,
+    values: values
+  };
 
-/**
- * @inheritDoc
- */
-ol.interaction.PinchRotate.prototype.shouldStopEvent = ol.functions.FALSE;
+};
 
-goog.provide('ol.interaction.PinchZoom');
+goog.provide('ol.control.Control');
 
 goog.require('ol');
-goog.require('ol.ViewHint');
-goog.require('ol.functions');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
+goog.require('ol.MapEventType');
+goog.require('ol.Object');
+goog.require('ol.dom');
+goog.require('ol.events');
 
 
 /**
  * @classdesc
- * Allows the user to zoom the map by pinching with two fingers
- * on a touch screen.
+ * 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.
  *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.PinchZoomOptions=} opt_options Options.
- * @api
- */
-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_
-  });
+ * 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) {
 
-  var options = opt_options ? opt_options : {};
+  ol.Object.call(this);
 
   /**
-   * @private
-   * @type {boolean}
+   * @protected
+   * @type {Element}
    */
-  this.constrainResolution_ = options.constrainResolution || false;
+  this.element = options.element ? options.element : null;
 
   /**
    * @private
-   * @type {ol.Coordinate}
+   * @type {Element}
    */
-  this.anchor_ = null;
+  this.target_ = null;
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.PluggableMap}
    */
-  this.duration_ = options.duration !== undefined ? options.duration : 400;
+  this.map_ = null;
 
   /**
-   * @private
-   * @type {number|undefined}
+   * @protected
+   * @type {!Array.<ol.EventsKey>}
    */
-  this.lastDistance_ = undefined;
+  this.listenerKeys = [];
 
   /**
-   * @private
-   * @type {number}
+   * @type {function(ol.MapEvent)}
    */
-  this.lastScaleDelta_ = 1;
+  this.render = options.render ? options.render : ol.nullFunction;
+
+  if (options.target) {
+    this.setTarget(options.target);
+  }
 
 };
-ol.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer);
+ol.inherits(ol.control.Control, ol.Object);
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.PinchZoom}
- * @private
+ * @inheritDoc
  */
-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;
-  }
-  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_);
+ol.control.Control.prototype.disposeInternal = function() {
+  ol.dom.removeNode(this.element);
+  ol.Object.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.PinchZoom}
- * @private
+ * Get the map associated with this control.
+ * @return {ol.PluggableMap} Map.
+ * @api
  */
-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;
-  }
+ol.control.Control.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.PinchZoom}
- * @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.PluggableMap} map Map.
+ * @override
+ * @api
  */
-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);
+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]);
+  }
+  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));
     }
-    return true;
-  } else {
-    return false;
+    map.render();
   }
 };
 
 
 /**
- * @inheritDoc
+ * This function is used to set a target element for the control. It has no
+ * effect if it is called after the control has been added to the map (i.e.
+ * after `setMap` is called on the control). If no `target` is set in the
+ * options passed to the control constructor and if `setTarget` is not called
+ * then the control is added to the map's overlay container.
+ * @param {Element|string} target Target.
+ * @api
  */
-ol.interaction.PinchZoom.prototype.shouldStopEvent = ol.functions.FALSE;
-
-goog.provide('ol.interaction');
+ol.control.Control.prototype.setTarget = function(target) {
+  this.target_ = typeof target === 'string' ?
+    document.getElementById(target) :
+    target;
+};
 
-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');
+goog.provide('ol.css');
 
 
 /**
- * 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}
+ * The CSS class for hidden feature.
  *
- * @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
+ * @const
+ * @type {string}
  */
-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());
-  }
+ol.css.CLASS_HIDDEN = 'ol-hidden';
 
-  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
-    }));
-  }
+/**
+ * The CSS class that we'll give the DOM elements to have them selectable.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_SELECTABLE = 'ol-selectable';
 
-  var pinchRotate = options.pinchRotate !== undefined ? options.pinchRotate :
-      true;
-  if (pinchRotate) {
-    interactions.push(new ol.interaction.PinchRotate());
-  }
+/**
+ * The CSS class that we'll give the DOM elements to have them unselectable.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
 
-  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
-    }));
-  }
+/**
+ * The CSS class for unsupported feature.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
 
-  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
-    }));
-  }
+/**
+ * The CSS class for controls.
+ *
+ * @const
+ * @type {string}
+ */
+ol.css.CLASS_CONTROL = 'ol-control';
 
-  return interactions;
 
-};
+/**
+ * Get the list of font families from a font spec.  Note that this doesn't work
+ * for font families that have commas in them.
+ * @param {string} The CSS font property.
+ * @return {Object.<string>} The font families (or null if the input spec is invalid).
+ */
+ol.css.getFontFamilies = (function() {
+  var style;
+  var cache = {};
+  return function(font) {
+    if (!style) {
+      style = document.createElement('div').style;
+    }
+    if (!(font in cache)) {
+      style.font = font;
+      var family = style.fontFamily;
+      style.font = '';
+      if (!family) {
+        return null;
+      }
+      cache[font] = family.split(/,\s?/);
+    }
+    return cache[font];
+  };
+})();
 
-goog.provide('ol.layer.Property');
+goog.provide('ol.render.EventType');
 
 /**
  * @enum {string}
  */
-ol.layer.Property = {
-  OPACITY: 'opacity',
-  VISIBLE: 'visible',
-  EXTENT: 'extent',
-  Z_INDEX: 'zIndex',
-  MAX_RESOLUTION: 'maxResolution',
-  MIN_RESOLUTION: 'minResolution',
-  SOURCE: 'source'
+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'
 };
 
-goog.provide('ol.layer.Base');
+goog.provide('ol.layer.Layer');
 
+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.math');
 goog.require('ol.obj');
+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.
- * 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.
+ * 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.Object}
- * @param {olx.layer.BaseOptions} options Layer options.
+ * @extends {ol.layer.Base}
+ * @fires ol.render.Event
+ * @param {olx.layer.LayerOptions} options Layer options.
  * @api
  */
-ol.layer.Base = function(options) {
+ol.layer.Layer = function(options) {
 
-  ol.Object.call(this);
+  var baseOptions = ol.obj.assign({}, options);
+  delete baseOptions.source;
+
+  ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions));
 
   /**
-   * @type {Object.<string, *>}
+   * @private
+   * @type {?ol.EventsKey}
    */
-  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);
+  this.mapPrecomposeKey_ = null;
 
   /**
-   * @type {ol.LayerState}
    * @private
+   * @type {?ol.EventsKey}
    */
-  this.state_ = /** @type {ol.LayerState} */ ({
-    layer: /** @type {ol.layer.Layer} */ (this),
-    managed: true
-  });
-
-};
-ol.inherits(ol.layer.Base, ol.Object);
-
+  this.mapRenderKey_ = null;
 
-/**
- * Create a renderer for this layer.
- * @abstract
- * @param {ol.renderer.Map} mapRenderer The map renderer.
- * @return {ol.renderer.Layer} A layer renderer.
- */
-ol.layer.Base.prototype.createRenderer = function(mapRenderer) {};
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.sourceChangeKey_ = null;
 
+  if (options.map) {
+    this.setMap(options.map);
+  }
 
-/**
- * @return {ol.LayerState} Layer state.
- */
-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);
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.Property.SOURCE),
+      this.handleSourcePropertyChange_, this);
 
-  return this.state_;
+  var source = options.source ? options.source : null;
+  this.setSource(source);
 };
+ol.inherits(ol.layer.Layer, ol.layer.Base);
 
 
 /**
- * @abstract
- * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
- *     modified in place).
- * @return {Array.<ol.layer.Layer>} Array of layers.
+ * 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.layer.Base.prototype.getLayersArray = function(opt_array) {};
+ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
+  return layerState.visible && resolution >= layerState.minResolution &&
+      resolution < layerState.maxResolution;
+};
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-ol.layer.Base.prototype.getLayerStatesArray = function(opt_states) {};
+ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
+  var array = opt_array ? opt_array : [];
+  array.push(this);
+  return array;
+};
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.layer.Base.prototype.getExtent = function() {
-  return /** @type {ol.Extent|undefined} */ (
-      this.get(ol.layer.Property.EXTENT));
+ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
+  var states = opt_states ? opt_states : [];
+  states.push(this.getLayerState());
+  return states;
 };
 
 
 /**
- * Return the maximum resolution of the layer.
- * @return {number} The maximum resolution of the layer.
+ * Get the layer source.
+ * @return {ol.source.Source} The layer source (or `null` if not yet set).
  * @observable
  * @api
  */
-ol.layer.Base.prototype.getMaxResolution = function() {
-  return /** @type {number} */ (
-      this.get(ol.layer.Property.MAX_RESOLUTION));
+ol.layer.Layer.prototype.getSource = function() {
+  var source = this.get(ol.layer.Property.SOURCE);
+  return /** @type {ol.source.Source} */ (source) || null;
 };
 
 
 /**
- * Return the minimum resolution of the layer.
- * @return {number} The minimum resolution of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.getMinResolution = function() {
-  return /** @type {number} */ (
-      this.get(ol.layer.Property.MIN_RESOLUTION));
+  * @inheritDoc
+  */
+ol.layer.Layer.prototype.getSourceState = function() {
+  var source = this.getSource();
+  return !source ? ol.source.State.UNDEFINED : source.getState();
 };
 
 
 /**
- * Return the opacity of the layer (between 0 and 1).
- * @return {number} The opacity of the layer.
- * @observable
- * @api
+ * @private
  */
-ol.layer.Base.prototype.getOpacity = function() {
-  return /** @type {number} */ (this.get(ol.layer.Property.OPACITY));
+ol.layer.Layer.prototype.handleSourceChange_ = function() {
+  this.changed();
 };
 
 
 /**
- * @abstract
- * @return {ol.source.State} Source state.
+ * @private
  */
-ol.layer.Base.prototype.getSourceState = function() {};
-
-
-/**
- * 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));
+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();
 };
 
 
 /**
- * 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
+ * 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.PluggableMap} map Map.
  * @api
  */
-ol.layer.Base.prototype.getZIndex = function() {
-  return /** @type {number} */ (this.get(ol.layer.Property.Z_INDEX));
+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();
+  }
 };
 
 
 /**
- * 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.
+ * Set the layer source.
+ * @param {ol.source.Source} source The layer source.
  * @observable
  * @api
  */
-ol.layer.Base.prototype.setExtent = function(extent) {
-  this.set(ol.layer.Property.EXTENT, extent);
+ol.layer.Layer.prototype.setSource = function(source) {
+  this.set(ol.layer.Property.SOURCE, source);
 };
 
+// FIXME handle date line wrap
 
-/**
- * Set the maximum resolution at which the layer is visible.
- * @param {number} maxResolution The maximum resolution of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
-  this.set(ol.layer.Property.MAX_RESOLUTION, maxResolution);
-};
+goog.provide('ol.control.Attribution');
+
+goog.require('ol');
+goog.require('ol.array');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.layer.Layer');
+goog.require('ol.obj');
 
 
 /**
- * Set the minimum resolution at which the layer is visible.
- * @param {number} minResolution The minimum resolution of the layer.
- * @observable
+ * @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
  */
-ol.layer.Base.prototype.setMinResolution = function(minResolution) {
-  this.set(ol.layer.Property.MIN_RESOLUTION, minResolution);
-};
+ol.control.Attribution = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * Set the opacity of the layer, allowed values range from 0 to 1.
- * @param {number} opacity The opacity of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.setOpacity = function(opacity) {
-  this.set(ol.layer.Property.OPACITY, opacity);
-};
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.ulElement_ = document.createElement('UL');
 
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.logoLi_ = document.createElement('LI');
 
-/**
- * Set the visibility of the layer (`true` or `false`).
- * @param {boolean} visible The visibility of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.setVisible = function(visible) {
-  this.set(ol.layer.Property.VISIBLE, visible);
-};
+  this.ulElement_.appendChild(this.logoLi_);
+  this.logoLi_.style.display = 'none';
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
 
-/**
- * Set Z-index of the layer, which is used to order layers before rendering.
- * The default Z-index is 0.
- * @param {number} zindex The z-index of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.setZIndex = function(zindex) {
-  this.set(ol.layer.Property.Z_INDEX, zindex);
-};
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.collapsible_ = options.collapsible !== undefined ?
+    options.collapsible : true;
 
-goog.provide('ol.source.State');
+  if (!this.collapsible_) {
+    this.collapsed_ = false;
+  }
 
+  var className = options.className !== undefined ? options.className : 'ol-attribution';
 
-/**
- * State of the source, one of 'undefined', 'loading', 'ready' or 'error'.
- * @enum {string}
- */
-ol.source.State = {
-  UNDEFINED: 'undefined',
-  LOADING: 'loading',
-  READY: 'ready',
-  ERROR: 'error'
-};
+  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions';
 
+  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB';
 
-goog.provide('ol.layer.Group');
+  if (typeof collapseLabel === 'string') {
+    /**
+     * @private
+     * @type {Node}
+     */
+    this.collapseLabel_ = document.createElement('span');
+    this.collapseLabel_.textContent = collapseLabel;
+  } else {
+    this.collapseLabel_ = collapseLabel;
+  }
 
-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');
+  var label = options.label !== undefined ? options.label : 'i';
 
+  if (typeof label === 'string') {
+    /**
+     * @private
+     * @type {Node}
+     */
+    this.label_ = document.createElement('span');
+    this.label_.textContent = label;
+  } else {
+    this.label_ = label;
+  }
 
-/**
- * @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.layer.Group = function(opt_options) {
 
-  var options = opt_options || {};
-  var baseOptions = /** @type {olx.layer.GroupOptions} */
-      (ol.obj.assign({}, options));
-  delete baseOptions.layers;
+  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);
 
-  var layers = options.layers;
+  ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this);
 
-  ol.layer.Base.call(this, baseOptions);
+  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;
+
+  ol.control.Control.call(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
 
   /**
+   * A list of currently rendered resolutions.
+   * @type {Array.<string>}
    * @private
-   * @type {Array.<ol.EventsKey>}
    */
-  this.layersListenerKeys_ = [];
+  this.renderedAttributions_ = [];
 
   /**
    * @private
-   * @type {Object.<string, Array.<ol.EventsKey>>}
+   * @type {boolean}
    */
-  this.listenerKeys_ = {};
+  this.renderedVisible_ = true;
 
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.Group.Property_.LAYERS),
-      this.handleLayersChanged_, this);
+  /**
+   * @private
+   * @type {Object.<string, Element>}
+   */
+  this.logoElements_ = {};
 
-  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;
+};
+ol.inherits(ol.control.Attribution, ol.control.Control);
+
+
+/**
+ * Get a list of visible attributions.
+ * @param {olx.FrameState} frameState Frame state.
+ * @return {Array.<string>} Attributions.
+ * @private
+ */
+ol.control.Attribution.prototype.getSourceAttributions_ = function(frameState) {
+  /**
+   * Used to determine if an attribution already exists.
+   * @type {Object.<string, boolean>}
+   */
+  var lookup = {};
+
+  /**
+   * A list of visible attributions.
+   * @type {Array.<string>}
+   */
+  var visibleAttributions = [];
+
+  var layerStatesArray = frameState.layerStatesArray;
+  var resolution = frameState.viewState.resolution;
+  for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) {
+    var layerState = layerStatesArray[i];
+    if (!ol.layer.Layer.visibleAtResolution(layerState, resolution)) {
+      continue;
     }
-  } else {
-    layers = new ol.Collection(undefined, {unique: true});
-  }
 
-  this.setLayers(layers);
+    var source = layerState.layer.getSource();
+    if (!source) {
+      continue;
+    }
+
+    var attributionGetter = source.getAttributions2();
+    if (!attributionGetter) {
+      continue;
+    }
 
+    var attributions = attributionGetter(frameState);
+    if (!attributions) {
+      continue;
+    }
+
+    if (Array.isArray(attributions)) {
+      for (var j = 0, jj = attributions.length; j < jj; ++j) {
+        if (!(attributions[j] in lookup)) {
+          visibleAttributions.push(attributions[j]);
+          lookup[attributions[j]] = true;
+        }
+      }
+    } else {
+      if (!(attributions in lookup)) {
+        visibleAttributions.push(attributions);
+        lookup[attributions] = true;
+      }
+    }
+  }
+  return visibleAttributions;
 };
-ol.inherits(ol.layer.Group, ol.layer.Base);
 
 
 /**
- * @inheritDoc
+ * Update the attribution element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Attribution}
+ * @api
  */
-ol.layer.Group.prototype.createRenderer = function(mapRenderer) {};
+ol.control.Attribution.render = function(mapEvent) {
+  this.updateElement_(mapEvent.frameState);
+};
 
 
 /**
  * @private
+ * @param {?olx.FrameState} frameState Frame state.
  */
-ol.layer.Group.prototype.handleLayerChange_ = function() {
-  if (this.getVisible()) {
-    this.changed();
+ol.control.Attribution.prototype.updateElement_ = function(frameState) {
+  if (!frameState) {
+    if (this.renderedVisible_) {
+      this.element.style.display = 'none';
+      this.renderedVisible_ = false;
+    }
+    return;
+  }
+
+  var attributions = this.getSourceAttributions_(frameState);
+  if (ol.array.equals(attributions, this.renderedAttributions_)) {
+    return;
+  }
+
+  // remove everything but the logo
+  while (this.ulElement_.lastChild !== this.logoLi_) {
+    this.ulElement_.removeChild(this.ulElement_.lastChild);
+  }
+
+  // append the attributions
+  for (var i = 0, ii = attributions.length; i < ii; ++i) {
+    var element = document.createElement('LI');
+    element.innerHTML = attributions[i];
+    this.ulElement_.appendChild(element);
+  }
+
+
+  if (attributions.length === 0 && this.renderedAttributions_.length > 0) {
+    this.element.classList.add('ol-logo-only');
+  } else if (this.renderedAttributions_.length === 0 && attributions.length > 0) {
+    this.element.classList.remove('ol-logo-only');
   }
+
+  var visible = attributions.length > 0 || !ol.obj.isEmpty(frameState.logos);
+  if (this.renderedVisible_ != visible) {
+    this.element.style.display = visible ? '' : 'none';
+    this.renderedVisible_ = visible;
+  }
+
+  this.renderedAttributions_ = attributions;
+  this.insertLogos_(frameState);
 };
 
 
 /**
- * @param {ol.events.Event} event Event.
+ * @param {?olx.FrameState} frameState Frame state.
  * @private
  */
-ol.layer.Group.prototype.handleLayersChanged_ = function(event) {
-  this.layersListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.layersListenerKeys_.length = 0;
+ol.control.Attribution.prototype.insertLogos_ = function(frameState) {
 
-  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));
+  var logo;
+  var logos = frameState.logos;
+  var logoElements = this.logoElements_;
 
-  for (var id in this.listenerKeys_) {
-    this.listenerKeys_[id].forEach(ol.events.unlistenByKey);
+  for (logo in logoElements) {
+    if (!(logo in logos)) {
+      ol.dom.removeNode(logoElements[logo]);
+      delete logoElements[logo];
+    }
   }
-  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)
-    ];
+  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.changed();
+  this.logoLi_.style.display = !ol.obj.isEmpty(logos) ? '' : 'none';
+
 };
 
 
 /**
- * @param {ol.Collection.Event} collectionEvent Collection event.
+ * @param {Event} event The event to handle
  * @private
  */
-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();
+ol.control.Attribution.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleToggle_();
 };
 
 
 /**
- * @param {ol.Collection.Event} collectionEvent Collection event.
  * @private
  */
-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();
+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_;
 };
 
 
 /**
- * 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
+ * Return `true` if the attribution is collapsible, `false` otherwise.
+ * @return {boolean} True if the widget is collapsible.
  * @api
  */
-ol.layer.Group.prototype.getLayers = function() {
-  return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get(
-      ol.layer.Group.Property_.LAYERS));
+ol.control.Attribution.prototype.getCollapsible = function() {
+  return this.collapsible_;
 };
 
 
 /**
- * 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
+ * Set whether the attribution should be collapsible.
+ * @param {boolean} collapsible True if the widget is collapsible.
  * @api
  */
-ol.layer.Group.prototype.setLayers = function(layers) {
-  this.set(ol.layer.Group.Property_.LAYERS, layers);
+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_();
+  }
 };
 
 
 /**
- * @inheritDoc
+ * 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
  */
-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;
+ol.control.Attribution.prototype.setCollapsed = function(collapsed) {
+  if (!this.collapsible_ || this.collapsed_ === collapsed) {
+    return;
+  }
+  this.handleToggle_();
 };
 
 
 /**
- * @inheritDoc
+ * Return `true` when the attribution is currently collapsed or `false`
+ * otherwise.
+ * @return {boolean} True if the widget is collapsed.
+ * @api
  */
-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;
+ol.control.Attribution.prototype.getCollapsed = function() {
+  return this.collapsed_;
 };
 
-
-/**
- * @inheritDoc
- */
-ol.layer.Group.prototype.getSourceState = function() {
-  return ol.source.State.READY;
-};
-
-/**
- * @enum {string}
- * @private
- */
-ol.layer.Group.Property_ = {
-  LAYERS: 'layers'
-};
-
-goog.provide('ol.render.EventType');
-
-/**
- * @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'
-};
-
-goog.provide('ol.layer.Layer');
+goog.provide('ol.control.Rotate');
 
 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');
+goog.require('ol.control.Control');
+goog.require('ol.css');
+goog.require('ol.easing');
 
 
 /**
  * @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.
+ * 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
- * @abstract
- * @extends {ol.layer.Base}
- * @fires ol.render.Event
- * @param {olx.layer.LayerOptions} options Layer options.
+ * @extends {ol.control.Control}
+ * @param {olx.control.RotateOptions=} opt_options Rotate options.
  * @api
  */
-ol.layer.Layer = function(options) {
+ol.control.Rotate = function(opt_options) {
 
-  var baseOptions = ol.obj.assign({}, options);
-  delete baseOptions.source;
+  var options = opt_options ? opt_options : {};
 
-  ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions));
+  var className = options.className !== undefined ? options.className : 'ol-rotate';
+
+  var label = options.label !== undefined ? options.label : '\u21E7';
 
   /**
+   * @type {Element}
    * @private
-   * @type {?ol.EventsKey}
    */
-  this.mapPrecomposeKey_ = null;
+  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');
+  }
+
+  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_);
+
+  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);
+
+  var render = options.render ? options.render : ol.control.Rotate.render;
+
+  this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
+
+  ol.control.Control.call(this, {
+    element: element,
+    render: render,
+    target: options.target
+  });
 
   /**
+   * @type {number}
    * @private
-   * @type {?ol.EventsKey}
    */
-  this.mapRenderKey_ = null;
+  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
   /**
+   * @type {boolean}
    * @private
-   * @type {?ol.EventsKey}
    */
-  this.sourceChangeKey_ = null;
+  this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
 
-  if (options.map) {
-    this.setMap(options.map);
-  }
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.rotation_ = undefined;
 
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.Property.SOURCE),
-      this.handleSourcePropertyChange_, this);
+  if (this.autoHide_) {
+    this.element.classList.add(ol.css.CLASS_HIDDEN);
+  }
 
-  var source = options.source ? options.source : null;
-  this.setSource(source);
 };
-ol.inherits(ol.layer.Layer, ol.layer.Base);
+ol.inherits(ol.control.Rotate, ol.control.Control);
 
 
 /**
- * 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.
+ * @param {Event} event The event to handle
+ * @private
  */
-ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
-  return layerState.visible && resolution >= layerState.minResolution &&
-      resolution < layerState.maxResolution;
+ol.control.Rotate.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  if (this.callResetNorth_ !== undefined) {
+    this.callResetNorth_();
+  } else {
+    this.resetNorth_();
+  }
 };
 
 
 /**
- * @inheritDoc
+ * @private
  */
-ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
-  var array = opt_array ? opt_array : [];
-  array.push(this);
-  return array;
+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 (view.getRotation() !== undefined) {
+    if (this.duration_ > 0) {
+      view.animate({
+        rotation: 0,
+        duration: this.duration_,
+        easing: ol.easing.easeOut
+      });
+    } else {
+      view.setRotation(0);
+    }
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Update the rotate control element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.Rotate}
+ * @api
  */
-ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
-  var states = opt_states ? opt_states : [];
-  states.push(this.getLayerState());
-  return states;
+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_) {
+      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');
+
+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');
+
 
 /**
- * Get the layer source.
- * @return {ol.source.Source} The layer source (or `null` if not yet set).
- * @observable
+ * @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
  */
-ol.layer.Layer.prototype.getSource = function() {
-  var source = this.get(ol.layer.Property.SOURCE);
-  return /** @type {ol.source.Source} */ (source) || null;
-};
+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;
 
-/**
-  * @inheritDoc
-  */
-ol.layer.Layer.prototype.getSourceState = function() {
-  var source = this.getSource();
-  return !source ? ol.source.State.UNDEFINED : source.getState();
 };
+ol.inherits(ol.control.Zoom, ol.control.Control);
 
 
 /**
+ * @param {number} delta Zoom delta.
+ * @param {Event} event The event to handle
  * @private
  */
-ol.layer.Layer.prototype.handleSourceChange_ = function() {
-  this.changed();
+ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
+  event.preventDefault();
+  this.zoomByDelta_(delta);
 };
 
 
 /**
+ * @param {number} delta Zoom delta.
  * @private
  */
-ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() {
-  if (this.sourceChangeKey_) {
-    ol.events.unlistenByKey(this.sourceChangeKey_);
-    this.sourceChangeKey_ = null;
+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 source = this.getSource();
-  if (source) {
-    this.sourceChangeKey_ = ol.events.listen(source,
-        ol.events.EventType.CHANGE, this.handleSourceChange_, this);
+  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);
+    }
   }
-  this.changed();
 };
 
+goog.provide('ol.control');
+
+goog.require('ol.Collection');
+goog.require('ol.control.Attribution');
+goog.require('ol.control.Rotate');
+goog.require('ol.control.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)`.
+ * 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}
  *
- * 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.
+ * @param {olx.control.DefaultsOptions=} opt_options Defaults options.
+ * @return {ol.Collection.<ol.control.Control>} Controls.
  * @api
  */
-ol.layer.Layer.prototype.setMap = function(map) {
-  if (this.mapPrecomposeKey_) {
-    ol.events.unlistenByKey(this.mapPrecomposeKey_);
-    this.mapPrecomposeKey_ = null;
-  }
-  if (!map) {
-    this.changed();
+ol.control.defaults = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  var controls = new ol.Collection();
+
+  var zoomControl = options.zoom !== undefined ? options.zoom : true;
+  if (zoomControl) {
+    controls.push(new ol.control.Zoom(options.zoomOptions));
   }
-  if (this.mapRenderKey_) {
-    ol.events.unlistenByKey(this.mapRenderKey_);
-    this.mapRenderKey_ = null;
+
+  var rotateControl = options.rotate !== undefined ? options.rotate : true;
+  if (rotateControl) {
+    controls.push(new ol.control.Rotate(options.rotateOptions));
   }
-  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 attributionControl = options.attribution !== undefined ?
+    options.attribution : true;
+  if (attributionControl) {
+    controls.push(new ol.control.Attribution(options.attributionOptions));
   }
-};
 
+  return controls;
 
-/**
- * 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);
 };
 
-goog.provide('ol.style.IconImageCache');
-
-goog.require('ol.color');
+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.style.IconImageCache = function() {
+ol.Kinetic = function(decay, minVelocity, delay) {
 
   /**
-   * @type {Object.<string, ol.style.IconImage>}
    * @private
+   * @type {number}
    */
-  this.cache_ = {};
+  this.decay_ = decay;
 
   /**
-   * @type {number}
    * @private
+   * @type {number}
    */
-  this.cacheSize_ = 0;
+  this.minVelocity_ = minVelocity;
 
   /**
-   * @const
+   * @private
    * @type {number}
+   */
+  this.delay_ = delay;
+
+  /**
    * @private
+   * @type {Array.<number>}
    */
-  this.maxCacheSize_ = 32;
-};
+  this.points_ = [];
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.angle_ = 0;
 
-/**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Color} color Color.
- * @return {string} Cache key.
- */
-ol.style.IconImageCache.getKey = function(src, crossOrigin, color) {
-  var colorString = color ? ol.color.asString(color) : 'null';
-  return crossOrigin + ':' + src + ':' + colorString;
+  /**
+   * @private
+   * @type {number}
+   */
+  this.initialVelocity_ = 0;
 };
 
 
 /**
  * FIXME empty description for jsdoc
  */
-ol.style.IconImageCache.prototype.clear = function() {
-  this.cache_ = {};
-  this.cacheSize_ = 0;
+ol.Kinetic.prototype.begin = function() {
+  this.points_.length = 0;
+  this.angle_ = 0;
+  this.initialVelocity_ = 0;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @param {number} x X.
+ * @param {number} y Y.
  */
-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_;
-      }
-    }
-  }
+ol.Kinetic.prototype.update = function(x, y) {
+  this.points_.push(x, y, Date.now());
 };
 
 
 /**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Color} color Color.
- * @return {ol.style.IconImage} Icon image.
+ * @return {boolean} Whether we should do kinetic animation.
  */
-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;
-};
+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;
+  }
 
-/**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Color} color Color.
- * @param {ol.style.IconImage} iconImage Icon image.
- */
-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');
+  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;
+  }
 
-goog.require('ol.asserts');
+  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_;
+};
 
 
 /**
- * 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 ]
- * ```
+ * @return {number} Total distance travelled (pixels).
  */
+ol.Kinetic.prototype.getDistance = function() {
+  return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
+};
 
 
 /**
- * @private
- * @type {ol.Transform}
+ * @return {number} Angle of the kinetic panning animation (radians).
  */
-ol.transform.tmp_ = new Array(6);
+ol.Kinetic.prototype.getAngle = function() {
+  return this.angle_;
+};
 
+goog.provide('ol.interaction.Property');
 
 /**
- * Create an identity transform.
- * @return {!ol.Transform} Identity transform.
+ * @enum {string}
  */
-ol.transform.create = function() {
-  return [1, 0, 0, 1, 0, 0];
+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');
+goog.require('ol.math');
+
 
 /**
- * Resets the given transform to an identity transform.
- * @param {!ol.Transform} transform Transform.
- * @return {!ol.Transform} Transform.
+ * @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.transform.reset = function(transform) {
-  return ol.transform.set(transform, 1, 0, 0, 1, 0, 0);
+ol.interaction.Interaction = function(options) {
+
+  ol.Object.call(this);
+
+  /**
+   * @private
+   * @type {ol.PluggableMap}
+   */
+  this.map_ = null;
+
+  this.setActive(true);
+
+  /**
+   * @type {function(ol.MapBrowserEvent):boolean}
+   */
+  this.handleEvent = options.handleEvent;
+
 };
+ol.inherits(ol.interaction.Interaction, ol.Object);
 
 
 /**
- * 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.
+ * Return whether the interaction is currently active.
+ * @return {boolean} `true` if the interaction is active, `false` otherwise.
+ * @observable
+ * @api
  */
-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];
-
-  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;
+ol.interaction.Interaction.prototype.getActive = function() {
+  return /** @type {boolean} */ (
+    this.get(ol.interaction.Property.ACTIVE));
 };
 
+
 /**
- * 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.
+ * Get the map associated with this interaction.
+ * @return {ol.PluggableMap} Map.
+ * @api
  */
-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;
+ol.interaction.Interaction.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
- * 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.
+ * Activate or deactivate the interaction.
+ * @param {boolean} active Active.
+ * @observable
+ * @api
  */
-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;
+ol.interaction.Interaction.prototype.setActive = function(active) {
+  this.set(ol.interaction.Property.ACTIVE, active);
 };
 
 
 /**
- * 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.
+ * 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.PluggableMap} map Map.
  */
-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;
+ol.interaction.Interaction.prototype.setMap = function(map) {
+  this.map_ = map;
 };
 
 
 /**
- * Applies rotation to the given transform.
- * @param {!ol.Transform} transform Transform.
- * @param {number} angle Angle in radians.
- * @return {!ol.Transform} The rotated transform.
+ * @param {ol.View} view View.
+ * @param {ol.Coordinate} delta Delta.
+ * @param {number=} opt_duration Duration.
  */
-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));
+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);
+    }
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.View} view View.
+ * @param {number|undefined} rotation Rotation.
+ * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
+ * @param {number=} opt_duration Duration.
  */
-ol.transform.scale = function(transform, x, y) {
-  return ol.transform.multiply(transform,
-      ol.transform.set(ol.transform.tmp_, x, 0, 0, y, 0, 0));
+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);
 };
 
 
 /**
- * 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.
+ * @param {ol.View} view View.
+ * @param {number|undefined} rotation Rotation.
+ * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
+ * @param {number=} opt_duration Duration.
  */
-ol.transform.translate = function(transform, dx, dy) {
-  return ol.transform.multiply(transform,
-      ol.transform.set(ol.transform.tmp_, 1, 0, 0, 1, dx, dy));
+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);
+    }
+  }
 };
 
 
 /**
- * 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.
+ * @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.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;
+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);
 };
 
 
 /**
- * Invert the given transform.
- * @param {!ol.Transform} transform Transform.
- * @return {!ol.Transform} Inverse of the transform.
+ * @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.transform.invert = function(transform) {
-  var det = ol.transform.determinant(transform);
-  ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted
+ol.interaction.Interaction.zoomByDelta = function(view, delta, opt_anchor, opt_duration) {
+  var currentResolution = view.getResolution();
+  var resolution = view.constrainResolution(currentResolution, delta, 0);
 
-  var a = transform[0];
-  var b = transform[1];
-  var c = transform[2];
-  var d = transform[3];
-  var e = transform[4];
-  var f = transform[5];
+  if (resolution !== undefined) {
+    var resolutions = view.getResolutions();
+    resolution = ol.math.clamp(
+        resolution,
+        view.getMinResolution() || resolutions[resolutions.length - 1],
+        view.getMaxResolution() || resolutions[0]);
+  }
 
-  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;
+  // 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);
 
-  return transform;
+    opt_anchor = [
+      (resolution * currentCenter[0] - currentResolution * center[0]) /
+          (resolution - currentResolution),
+      (resolution * currentCenter[1] - currentResolution * center[1]) /
+          (resolution - currentResolution)
+    ];
+  }
+
+  ol.interaction.Interaction.zoomWithoutConstraints(
+      view, resolution, opt_anchor, opt_duration);
 };
 
 
 /**
- * Returns the determinant of the given matrix.
- * @param {!ol.Transform} mat Matrix.
- * @return {number} Determinant.
+ * @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.transform.determinant = function(mat) {
-  return mat[0] * mat[3] - mat[1] * mat[2];
+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);
+    }
+  }
 };
 
-goog.provide('ol.renderer.Map');
+goog.provide('ol.interaction.DoubleClickZoom');
 
 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');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.interaction.Interaction');
 
 
 /**
+ * @classdesc
+ * Allows the user to zoom by double-clicking on the map.
+ *
  * @constructor
- * @abstract
- * @extends {ol.Disposable}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
- * @struct
+ * @extends {ol.interaction.Interaction}
+ * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options.
+ * @api
  */
-ol.renderer.Map = function(container, map) {
-
-  ol.Disposable.call(this);
+ol.interaction.DoubleClickZoom = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
   /**
    * @private
-   * @type {ol.Map}
+   * @type {number}
    */
-  this.map_ = map;
+  this.delta_ = options.delta ? options.delta : 1;
 
-  /**
-   * @private
-   * @type {Object.<string, ol.renderer.Layer>}
-   */
-  this.layerRenderers_ = {};
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.DoubleClickZoom.handleEvent
+  });
 
   /**
    * @private
-   * @type {Object.<string, ol.EventsKey>}
+   * @type {number}
    */
-  this.layerRendererListeners_ = {};
+  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
 };
-ol.inherits(ol.renderer.Map, ol.Disposable);
+ol.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction);
 
 
 /**
- * @param {olx.FrameState} frameState FrameState.
- * @protected
+ * 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.renderer.Map.prototype.calculateMatrices2D = function(frameState) {
-  var viewState = frameState.viewState;
-  var coordinateToPixelTransform = frameState.coordinateToPixelTransform;
-  var pixelToCoordinateTransform = frameState.pixelToCoordinateTransform;
+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;
+};
 
-  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]);
+goog.provide('ol.events.condition');
 
-  ol.transform.invert(
-      ol.transform.setFromArray(pixelToCoordinateTransform, coordinateToPixelTransform));
-};
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.asserts');
+goog.require('ol.functions');
+goog.require('ol.has');
 
 
 /**
- * @inheritDoc
+ * 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.renderer.Map.prototype.disposeInternal = function() {
-  for (var id in this.layerRenderers_) {
-    this.layerRenderers_[id].dispose();
-  }
+ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+    originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      !originalEvent.shiftKey);
 };
 
 
 /**
- * @param {ol.Map} map Map.
- * @param {olx.FrameState} frameState Frame state.
- * @private
+ * 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.renderer.Map.expireIconCache_ = function(map, frameState) {
-  var cache = ol.style.iconImageCache;
-  cache.expire();
+ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+    originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      originalEvent.shiftKey);
 };
 
 
 /**
- * @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
+ * Return always true.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True.
+ * @function
+ * @api
  */
-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;
-};
-
-
-/**
- * @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.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
-        layerFilter, thisArg2) {};
+ol.events.condition.always = ol.functions.TRUE;
 
 
 /**
- * @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
+ * 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.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;
+ol.events.condition.click = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEventType.CLICK;
 };
 
 
 /**
- * @param {ol.layer.Layer} layer Layer.
- * @protected
- * @return {ol.renderer.Layer} Layer renderer.
+ * 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.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;
-  }
+ol.events.condition.mouseActionButton = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return originalEvent.button == 0 &&
+      !(ol.has.WEBKIT && ol.has.MAC && originalEvent.ctrlKey);
 };
 
 
 /**
- * @param {string} layerKey Layer key.
- * @protected
- * @return {ol.renderer.Layer} Layer renderer.
+ * Return always false.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} False.
+ * @function
+ * @api
  */
-ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) {
-  return this.layerRenderers_[layerKey];
-};
+ol.events.condition.never = ol.functions.FALSE;
 
 
 /**
- * @protected
- * @return {Object.<string, ol.renderer.Layer>} Layer renderers.
+ * 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.renderer.Map.prototype.getLayerRenderers = function() {
-  return this.layerRenderers_;
+ol.events.condition.pointerMove = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == 'pointermove';
 };
 
 
 /**
- * @return {ol.Map} Map.
+ * 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.renderer.Map.prototype.getMap = function() {
-  return this.map_;
+ol.events.condition.singleClick = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEventType.SINGLECLICK;
 };
 
 
 /**
- * @abstract
- * @return {string} Type
- */
-ol.renderer.Map.prototype.getType = function() {};
-
-
-/**
- * Handle changes in a layer renderer.
- * @private
+ * 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.renderer.Map.prototype.handleLayerRendererChange_ = function() {
-  this.map_.render();
+ol.events.condition.doubleClick = function(mapBrowserEvent) {
+  return mapBrowserEvent.type == ol.MapBrowserEventType.DBLCLICK;
 };
 
 
 /**
- * @param {string} layerKey Layer key.
- * @return {ol.renderer.Layer} Layer renderer.
- * @private
+ * 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.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) {
-  var layerRenderer = this.layerRenderers_[layerKey];
-  delete this.layerRenderers_[layerKey];
-
-  ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]);
-  delete this.layerRendererListeners_[layerKey];
-
-  return layerRenderer;
+ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+    !originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      !originalEvent.shiftKey);
 };
 
 
 /**
- * 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
+ * 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.renderer.Map.prototype.removeUnusedLayerRenderers_ = function(map, frameState) {
-  var layerKey;
-  for (layerKey in this.layerRenderers_) {
-    if (!frameState || !(layerKey in frameState.layerStates)) {
-      this.removeLayerRendererByKey_(layerKey).dispose();
-    }
-  }
+ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+    !originalEvent.altKey &&
+      (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) &&
+      !originalEvent.shiftKey);
 };
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @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.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
-  frameState.postRenderFunctions.push(
-    /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_)
-  );
+ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
+  var originalEvent = mapBrowserEvent.originalEvent;
+  return (
+    !originalEvent.altKey &&
+      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
+      originalEvent.shiftKey);
 };
 
 
 /**
- * @param {!olx.FrameState} frameState Frame state.
- * @protected
+ * Return `true` if the target element is not editable, i.e. not a `<input>`-,
+ * `<select>`- or `<textarea>`-element, `false` otherwise.
+ *
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} True only if the target element is not editable.
+ * @api
  */
-ol.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;
-    }
-  }
+ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
+  var target = mapBrowserEvent.originalEvent.target;
+  var tagName = target.tagName;
+  return (
+    tagName !== 'INPUT' &&
+      tagName !== 'SELECT' &&
+      tagName !== 'TEXTAREA');
 };
 
 
 /**
- * @param {ol.LayerState} state1 First layer state.
- * @param {ol.LayerState} state2 Second layer state.
- * @return {number} The zIndex difference.
+ * 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.renderer.Map.sortByZIndex = function(state1, state2) {
-  return state1.zIndex - state2.zIndex;
+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';
 };
 
-goog.provide('ol.renderer.Type');
-
 
 /**
- * Available renderers: `'canvas'` or `'webgl'`.
- * @enum {string}
+ * 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.renderer.Type = {
-  CANVAS: 'canvas',
-  WEBGL: 'webgl'
+ol.events.condition.primaryAction = function(mapBrowserEvent) {
+  var pointerEvent = mapBrowserEvent.pointerEvent;
+  return pointerEvent.isPrimary && pointerEvent.button === 0;
 };
 
-goog.provide('ol.render.Event');
+goog.provide('ol.interaction.Pointer');
 
 goog.require('ol');
-goog.require('ol.events.Event');
+goog.require('ol.functions');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapBrowserPointerEvent');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.obj');
 
 
 /**
+ * @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
- * @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.
+ * @param {olx.interaction.PointerOptions=} opt_options Options.
+ * @extends {ol.interaction.Interaction}
+ * @api
  */
-ol.render.Event = function(
-    type, opt_vectorContext, opt_frameState, opt_context,
-    opt_glContext) {
+ol.interaction.Pointer = function(opt_options) {
 
-  ol.events.Event.call(this, type);
+  var options = opt_options ? opt_options : {};
+
+  var handleEvent = options.handleEvent ?
+    options.handleEvent : ol.interaction.Pointer.handleEvent;
+
+  ol.interaction.Interaction.call(this, {
+    handleEvent: handleEvent
+  });
 
   /**
-   * For canvas, this is an instance of {@link ol.render.canvas.Immediate}.
-   * @type {ol.render.VectorContext|undefined}
-   * @api
+   * @type {function(ol.MapBrowserPointerEvent):boolean}
+   * @private
    */
-  this.vectorContext = opt_vectorContext;
+  this.handleDownEvent_ = options.handleDownEvent ?
+    options.handleDownEvent : ol.interaction.Pointer.handleDownEvent;
 
   /**
-   * An object representing the current render frame state.
-   * @type {olx.FrameState|undefined}
-   * @api
+   * @type {function(ol.MapBrowserPointerEvent)}
+   * @private
    */
-  this.frameState = opt_frameState;
+  this.handleDragEvent_ = options.handleDragEvent ?
+    options.handleDragEvent : ol.interaction.Pointer.handleDragEvent;
 
   /**
-   * Canvas context. Only available when a Canvas renderer is used, null
-   * otherwise.
-   * @type {CanvasRenderingContext2D|null|undefined}
-   * @api
+   * @type {function(ol.MapBrowserPointerEvent)}
+   * @private
    */
-  this.context = opt_context;
+  this.handleMoveEvent_ = options.handleMoveEvent ?
+    options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent;
 
   /**
-   * WebGL context. Only available when a WebGL renderer is used, null
-   * otherwise.
-   * @type {ol.webgl.Context|null|undefined}
-   * @api
+   * @type {function(ol.MapBrowserPointerEvent):boolean}
+   * @private
    */
-  this.glContext = opt_glContext;
+  this.handleUpEvent_ = options.handleUpEvent ?
+    options.handleUpEvent : ol.interaction.Pointer.handleUpEvent;
 
-};
-ol.inherits(ol.render.Event, ol.events.Event);
+  /**
+   * @type {boolean}
+   * @protected
+   */
+  this.handlingDownUpSequence = false;
 
-goog.provide('ol.render.canvas');
+  /**
+   * @type {Object.<string, ol.pointer.PointerEvent>}
+   * @private
+   */
+  this.trackedPointers_ = {};
 
+  /**
+   * @type {Array.<ol.pointer.PointerEvent>}
+   * @protected
+   */
+  this.targetPointers = [];
 
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultFont = '10px sans-serif';
+};
+ol.inherits(ol.interaction.Pointer, ol.interaction.Interaction);
 
 
 /**
- * @const
- * @type {ol.Color}
+ * @param {Array.<ol.pointer.PointerEvent>} pointerEvents List of events.
+ * @return {ol.Pixel} Centroid pixel.
  */
-ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
+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];
+};
 
 
 /**
- * @const
- * @type {string}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Whether the event is a pointerdown, pointerdrag
+ *     or pointerup event.
+ * @private
  */
-ol.render.canvas.defaultLineCap = 'round';
+ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = function(mapBrowserEvent) {
+  var type = mapBrowserEvent.type;
+  return (
+    type === ol.MapBrowserEventType.POINTERDOWN ||
+      type === ol.MapBrowserEventType.POINTERDRAG ||
+      type === ol.MapBrowserEventType.POINTERUP);
+};
 
 
 /**
- * @const
- * @type {Array.<number>}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @private
  */
-ol.render.canvas.defaultLineDash = [];
+ol.interaction.Pointer.prototype.updateTrackedPointers_ = function(mapBrowserEvent) {
+  if (this.isPointerDraggingEvent_(mapBrowserEvent)) {
+    var event = mapBrowserEvent.pointerEvent;
+
+    var id = event.pointerId.toString();
+    if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERUP) {
+      delete this.trackedPointers_[id];
+    } else if (mapBrowserEvent.type ==
+        ol.MapBrowserEventType.POINTERDOWN) {
+      this.trackedPointers_[id] = event;
+    } else if (id in this.trackedPointers_) {
+      // update only when there was a pointerdown event for this pointer
+      this.trackedPointers_[id] = event;
+    }
+    this.targetPointers = ol.obj.getValues(this.trackedPointers_);
+  }
+};
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
  */
-ol.render.canvas.defaultLineDashOffset = 0;
+ol.interaction.Pointer.handleDragEvent = ol.nullFunction;
 
 
 /**
- * @const
- * @type {string}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
  */
-ol.render.canvas.defaultLineJoin = 'round';
+ol.interaction.Pointer.handleUpEvent = ol.functions.FALSE;
 
 
 /**
- * @const
- * @type {number}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Capture dragging.
+ * @this {ol.interaction.Pointer}
  */
-ol.render.canvas.defaultMiterLimit = 10;
+ol.interaction.Pointer.handleDownEvent = ol.functions.FALSE;
 
 
 /**
- * @const
- * @type {ol.Color}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.Pointer}
  */
-ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
+ol.interaction.Pointer.handleMoveEvent = ol.nullFunction;
 
 
 /**
- * @const
- * @type {string}
+ * 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.render.canvas.defaultTextAlign = 'center';
+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.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;
+};
 
 
 /**
- * @const
- * @type {string}
+ * This method is used to determine if "down" events should be propagated to
+ * other interactions or should be stopped.
+ *
+ * The method receives the return code of the "handleDownEvent" function.
+ *
+ * By default this function is the "identity" function. It's overidden in
+ * child classes.
+ *
+ * @param {boolean} handled Was the event handled by the interaction?
+ * @return {boolean} Should the event be stopped?
+ * @protected
  */
-ol.render.canvas.defaultTextBaseline = 'middle';
+ol.interaction.Pointer.prototype.shouldStopEvent = function(handled) {
+  return handled;
+};
 
+goog.provide('ol.interaction.DragPan');
 
-/**
- * @const
- * @type {number}
- */
-ol.render.canvas.defaultLineWidth = 1;
+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 {CanvasRenderingContext2D} context Context.
- * @param {number} rotation Rotation.
- * @param {number} offsetX X offset.
- * @param {number} offsetY Y offset.
+ * @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.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) {
-  if (rotation !== 0) {
-    context.translate(offsetX, offsetY);
-    context.rotate(rotation);
-    context.translate(-offsetX, -offsetY);
-  }
-};
+ol.interaction.DragPan = function(opt_options) {
 
-goog.provide('ol.render.VectorContext');
+  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);
 
 
 /**
- * Context for drawing geometries.  A vector context is available on render
- * events and does not need to be constructed directly.
- * @constructor
- * @abstract
- * @struct
- * @api
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-ol.render.VectorContext = function() {
+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();
+  }
+  this.lastCentroid = centroid;
+  this.lastPointersCount_ = targetPointers.length;
 };
 
 
 /**
- * Render a geometry.
- *
- * @param {ol.geom.Geometry} geometry The geometry to render.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-ol.render.VectorContext.prototype.drawGeometry = function(geometry) {};
+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;
+  }
+};
 
 
 /**
- * Set the rendering style.
- *
- * @param {ol.style.Style} style The rendering style.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragPan}
+ * @private
  */
-ol.render.VectorContext.prototype.setStyle = function(style) {};
+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.getAnimating()) {
+      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;
+  }
+};
 
 
 /**
- * @param {ol.geom.Circle} circleGeometry Circle geometry.
- * @param {ol.Feature} feature Feature.
+ * @inheritDoc
  */
-ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {};
+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 {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
+ * @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.render.VectorContext.prototype.drawFeature = function(feature, style) {};
+ol.interaction.DragRotate = function(opt_options) {
+
+  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 {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
- *     collection.
- * @param {ol.Feature} feature Feature.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragRotate}
+ * @private
  */
-ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {};
+ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return;
+  }
+
+  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 {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line
- *     string geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragRotate}
+ * @private
  */
-ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {};
+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(view, rotation,
+      undefined, this.duration_);
+  return false;
+};
 
 
 /**
- * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry
- *     MultiLineString geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotate}
+ * @private
  */
-ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {};
+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;
+  }
+};
 
 
 /**
- * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint
- *     geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @inheritDoc
  */
-ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {};
+ol.interaction.DragRotate.prototype.shouldStopEvent = ol.functions.FALSE;
+
+// FIXME add rotation
+
+goog.provide('ol.render.Box');
+
+goog.require('ol');
+goog.require('ol.Disposable');
+goog.require('ol.geom.Polygon');
 
 
 /**
- * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @constructor
+ * @extends {ol.Disposable}
+ * @param {string} className CSS class name.
  */
-ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {};
+ol.render.Box = function(className) {
+
+  /**
+   * @type {ol.geom.Polygon}
+   * @private
+   */
+  this.geometry_ = null;
+
+  /**
+   * @type {HTMLDivElement}
+   * @private
+   */
+  this.element_ = /** @type {HTMLDivElement} */ (document.createElement('div'));
+  this.element_.style.position = 'absolute';
+  this.element_.className = 'ol-box ' + className;
+
+  /**
+   * @private
+   * @type {ol.PluggableMap}
+   */
+  this.map_ = null;
+
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.startPixel_ = null;
+
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.endPixel_ = null;
+
+};
+ol.inherits(ol.render.Box, ol.Disposable);
 
 
 /**
- * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @inheritDoc
  */
-ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {};
+ol.render.Box.prototype.disposeInternal = function() {
+  this.setMap(null);
+};
 
 
 /**
- * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon
- *     geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
  */
-ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {};
+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;
+};
 
 
 /**
- * @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.
+ * @param {ol.PluggableMap} map Map.
  */
-ol.render.VectorContext.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {};
+ol.render.Box.prototype.setMap = function(map) {
+  if (this.map_) {
+    this.map_.getOverlayContainer().removeChild(this.element_);
+    var style = this.element_.style;
+    style.left = style.top = style.width = style.height = 'inherit';
+  }
+  this.map_ = map;
+  if (this.map_) {
+    this.map_.getOverlayContainer().appendChild(this.element_);
+  }
+};
 
 
 /**
- * @param {ol.style.Fill} fillStyle Fill style.
- * @param {ol.style.Stroke} strokeStyle Stroke style.
+ * @param {ol.Pixel} startPixel Start pixel.
+ * @param {ol.Pixel} endPixel End pixel.
  */
-ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {};
+ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
+  this.startPixel_ = startPixel;
+  this.endPixel_ = endPixel;
+  this.createOrUpdateGeometry();
+  this.render_();
+};
 
 
 /**
- * @param {ol.style.Image} imageStyle Image style.
+ * Creates or updates the cached geometry.
  */
-ol.render.VectorContext.prototype.setImageStyle = function(imageStyle) {};
+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]);
+  }
+};
 
 
 /**
- * @param {ol.style.Text} textStyle Text style.
+ * @return {ol.geom.Polygon} Geometry.
  */
-ol.render.VectorContext.prototype.setTextStyle = function(textStyle) {};
-
-// 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?
+ol.render.Box.prototype.getGeometry = function() {
+  return this.geometry_;
+};
 
-goog.provide('ol.render.canvas.Immediate');
+// FIXME draw drag box
+goog.provide('ol.interaction.DragBox');
 
+goog.require('ol.events.Event');
 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');
+goog.require('ol.events.condition');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.render.Box');
 
 
 /**
  * @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.
+ * 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.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
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.DragBox.Event
+ * @param {olx.interaction.DragBoxOptions=} opt_options Options.
+ * @api
  */
-ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform, viewRotation) {
-  ol.render.VectorContext.call(this);
+ol.interaction.DragBox = function(opt_options) {
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = context;
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.DragBox.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragBox.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragBox.handleUpEvent_
+  });
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = pixelRatio;
+  var options = opt_options ? opt_options : {};
 
   /**
+   * @type {ol.render.Box}
    * @private
-   * @type {ol.Extent}
    */
-  this.extent_ = extent;
+  this.box_ = new ol.render.Box(options.className || 'ol-dragbox');
 
   /**
+   * @type {number}
    * @private
-   * @type {ol.Transform}
    */
-  this.transform_ = transform;
+  this.minArea_ = options.minArea !== undefined ? options.minArea : 64;
 
   /**
+   * @type {ol.Pixel}
    * @private
-   * @type {number}
    */
-  this.viewRotation_ = viewRotation;
+  this.startPixel_ = null;
 
   /**
    * @private
-   * @type {?ol.CanvasFillState}
+   * @type {ol.EventsConditionType}
    */
-  this.contextFillState_ = null;
+  this.condition_ = options.condition ?
+    options.condition : ol.events.condition.always;
 
   /**
    * @private
-   * @type {?ol.CanvasStrokeState}
+   * @type {ol.DragBoxEndConditionType}
    */
-  this.contextStrokeState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasTextState}
+  this.boxEndCondition_ = options.boxEndCondition ?
+    options.boxEndCondition : ol.interaction.DragBox.defaultBoxEndCondition;
+};
+ol.inherits(ol.interaction.DragBox, ol.interaction.Pointer);
+
+
+/**
+ * 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.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_;
+};
+
+
+/**
+ * @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);
+
+  this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType_.BOXDRAG,
+      mapBrowserEvent.coordinate, mapBrowserEvent));
+};
+
+
+/**
+ * Returns geometry of last drawn box.
+ * @return {ol.geom.Polygon} Geometry.
+ * @api
+ */
+ol.interaction.DragBox.prototype.getGeometry = function() {
+  return this.box_.getGeometry();
+};
+
+
+/**
+ * To be overridden by child classes.
+ * FIXME: use constructor option instead of relying on overriding.
+ * @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);
+
+  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.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;
+  }
+
+  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;
+  }
+};
+
+
+/**
+ * @enum {string}
+ * @private
+ */
+ol.interaction.DragBox.EventType_ = {
+  /**
+   * Triggered upon drag box start.
+   * @event ol.interaction.DragBox.Event#boxstart
+   * @api
    */
-  this.contextTextState_ = null;
+  BOXSTART: 'boxstart',
 
   /**
-   * @private
-   * @type {?ol.CanvasFillState}
+   * Triggered on drag when box is active.
+   * @event ol.interaction.DragBox.Event#boxdrag
+   * @api
    */
-  this.fillState_ = null;
+  BOXDRAG: 'boxdrag',
 
   /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
+   * Triggered upon drag box end.
+   * @event ol.interaction.DragBox.Event#boxend
+   * @api
    */
-  this.strokeState_ = null;
+  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.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Originating event.
+ * @extends {ol.events.Event}
+ * @constructor
+ * @implements {oli.DragBoxEvent}
+ */
+ol.interaction.DragBox.Event = function(type, coordinate, mapBrowserEvent) {
+  ol.events.Event.call(this, type);
 
   /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
+   * The coordinate of the drag event.
+   * @const
+   * @type {ol.Coordinate}
+   * @api
    */
-  this.image_ = null;
+  this.coordinate = coordinate;
 
   /**
-   * @private
-   * @type {number}
+   * @const
+   * @type {ol.MapBrowserEvent}
+   * @api
    */
-  this.imageAnchorX_ = 0;
+  this.mapBrowserEvent = mapBrowserEvent;
+
+};
+ol.inherits(ol.interaction.DragBox.Event, ol.events.Event);
+
+goog.provide('ol.interaction.DragZoom');
+
+goog.require('ol');
+goog.require('ol.easing');
+goog.require('ol.events.condition');
+goog.require('ol.extent');
+goog.require('ol.interaction.DragBox');
+
+
+/**
+ * @classdesc
+ * Allows the user to zoom the map by clicking and dragging on the map,
+ * normally combined with an {@link ol.events.condition} that limits
+ * it to when a key, shift by default, is held down.
+ *
+ * To change the style of the box, use CSS and the `.ol-dragzoom` selector, or
+ * your custom one configured with `className`.
+ *
+ * @constructor
+ * @extends {ol.interaction.DragBox}
+ * @param {olx.interaction.DragZoomOptions=} opt_options Options.
+ * @api
+ */
+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.imageAnchorY_ = 0;
+  this.duration_ = options.duration !== undefined ? options.duration : 200;
 
   /**
    * @private
-   * @type {number}
+   * @type {boolean}
    */
-  this.imageHeight_ = 0;
+  this.out_ = options.out !== undefined ? options.out : false;
+
+  ol.interaction.DragBox.call(this, {
+    condition: condition,
+    className: options.className || 'ol-dragzoom'
+  });
+
+};
+ol.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.DragZoom.prototype.onBoxEnd = function() {
+  var map = this.getMap();
+
+  var view = /** @type {!ol.View} */ (map.getView());
+
+  var size = /** @type {!ol.Size} */ (map.getSize());
+
+  var extent = this.getGeometry().getExtent();
+
+  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);
+
+    ol.extent.scaleFromCenter(mapExtent, 1 / factor);
+    extent = mapExtent;
+  }
+
+  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
+  });
+
+};
+
+goog.provide('ol.events.KeyCode');
+
+/**
+ * @enum {number}
+ * @const
+ */
+ol.events.KeyCode = {
+  LEFT: 37,
+  UP: 38,
+  RIGHT: 39,
+  DOWN: 40
+};
+
+goog.provide('ol.interaction.KeyboardPan');
+
+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');
+
+
+/**
+ * @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.interaction.KeyboardPan = function(opt_options) {
+
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.KeyboardPan.handleEvent
+  });
+
+  var options = opt_options || {};
 
   /**
    * @private
-   * @type {number}
+   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
+   * @return {boolean} Combined condition result.
    */
-  this.imageOpacity_ = 0;
+  this.defaultCondition_ = function(mapBrowserEvent) {
+    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
+      ol.events.condition.targetNotEditable(mapBrowserEvent);
+  };
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.EventsConditionType}
    */
-  this.imageOriginX_ = 0;
+  this.condition_ = options.condition !== undefined ?
+    options.condition : this.defaultCondition_;
 
   /**
    * @private
    * @type {number}
    */
-  this.imageOriginY_ = 0;
+  this.duration_ = options.duration !== undefined ? options.duration : 100;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {number}
    */
-  this.imageRotateWithView_ = false;
+  this.pixelDelta_ = options.pixelDelta !== undefined ?
+    options.pixelDelta : 128;
+
+};
+ol.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 == 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;
+    }
+  }
+  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');
+
+
+/**
+ * @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.interaction.KeyboardZoom = function(opt_options) {
+
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.KeyboardZoom.handleEvent
+  });
+
+  var options = opt_options ? opt_options : {};
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.EventsConditionType}
    */
-  this.imageRotation_ = 0;
+  this.condition_ = options.condition ? options.condition :
+    ol.events.condition.targetNotEditable;
 
   /**
    * @private
    * @type {number}
    */
-  this.imageScale_ = 0;
+  this.delta_ = options.delta ? options.delta : 1;
 
   /**
    * @private
-   * @type {boolean}
+   * @type {number}
    */
-  this.imageSnapToPixel_ = false;
+  this.duration_ = options.duration !== undefined ? options.duration : 100;
+
+};
+ol.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 == 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;
+    }
+  }
+  return !stopEvent;
+};
+
+goog.provide('ol.interaction.MouseWheelZoom');
+
+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');
+
+
+/**
+ * @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.interaction.MouseWheelZoom = function(opt_options) {
+
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.MouseWheelZoom.handleEvent
+  });
+
+  var options = opt_options || {};
 
   /**
    * @private
    * @type {number}
    */
-  this.imageWidth_ = 0;
+  this.delta_ = 0;
 
   /**
    * @private
-   * @type {string}
+   * @type {number}
    */
-  this.text_ = '';
+  this.duration_ = options.duration !== undefined ? options.duration : 250;
 
   /**
    * @private
    * @type {number}
    */
-  this.textOffsetX_ = 0;
+  this.timeout_ = options.timeout !== undefined ? options.timeout : 80;
 
   /**
    * @private
-   * @type {number}
+   * @type {boolean}
    */
-  this.textOffsetY_ = 0;
+  this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;
 
   /**
    * @private
    * @type {boolean}
    */
-  this.textRotateWithView_ = false;
+  this.constrainResolution_ = options.constrainResolution || false;
 
   /**
    * @private
-   * @type {number}
+   * @type {?ol.Coordinate}
    */
-  this.textRotation_ = 0;
+  this.lastAnchor_ = null;
 
   /**
    * @private
-   * @type {number}
+   * @type {number|undefined}
    */
-  this.textScale_ = 0;
+  this.startTime_ = undefined;
 
   /**
    * @private
-   * @type {?ol.CanvasFillState}
+   * @type {number|undefined}
    */
-  this.textFillState_ = null;
+  this.timeoutId_ = undefined;
 
   /**
    * @private
-   * @type {?ol.CanvasStrokeState}
+   * @type {ol.interaction.MouseWheelZoom.Mode_|undefined}
    */
-  this.textStrokeState_ = null;
+  this.mode_ = undefined;
 
   /**
-   * @private
-   * @type {?ol.CanvasTextState}
+   * Trackpad events separated by this delay will be considered separate
+   * interactions.
+   * @type {number}
    */
-  this.textState_ = null;
+  this.trackpadEventGap_ = 400;
 
   /**
-   * @private
-   * @type {Array.<number>}
+   * @type {number|undefined}
    */
-  this.pixelCoordinates_ = [];
+  this.trackpadTimeoutId_ = undefined;
 
   /**
+   * The number of delta values per zoom level
    * @private
-   * @type {ol.Transform}
+   * @type {number}
    */
-  this.tmpLocalTransform_ = ol.transform.create();
+  this.trackpadDeltaPerZoom_ = 300;
 
-};
-ol.inherits(ol.render.canvas.Immediate, ol.render.VectorContext);
+  /**
+   * The zoom factor by which scroll zooming is allowed to exceed the limits.
+   * @private
+   * @type {number}
+   */
+  this.trackpadZoomBuffer_ = 1.5;
+
+};
+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.
- * @private
+ * 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.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_;
+ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) {
+  var type = mapBrowserEvent.type;
+  if (type !== ol.events.EventType.WHEEL && type !== ol.events.EventType.MOUSEWHEEL) {
+    return true;
   }
-  var rotation = this.imageRotation_;
-  if (this.imageRotateWithView_) {
-    rotation += this.viewRotation_;
+
+  mapBrowserEvent.preventDefault();
+
+  var map = mapBrowserEvent.map;
+  var wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent);
+
+  if (this.useAnchor_) {
+    this.lastAnchor_ = mapBrowserEvent.coordinate;
   }
-  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);
+
+  // 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 (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);
+    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;
     }
-    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;
+
+  if (delta === 0) {
+    return false;
   }
-};
 
+  var now = Date.now();
 
-/**
- * @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_);
+  if (this.startTime_ === undefined) {
+    this.startTime_ = now;
   }
-  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_;
+
+  if (!this.mode_ || now - this.startTime_ > this.trackpadEventGap_) {
+    this.mode_ = Math.abs(delta) < 4 ?
+      ol.interaction.MouseWheelZoom.Mode_.TRACKPAD :
+      ol.interaction.MouseWheelZoom.Mode_.WHEEL;
   }
-  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.mode_ === ol.interaction.MouseWheelZoom.Mode_.TRACKPAD) {
+    var view = map.getView();
+    if (this.trackpadTimeoutId_) {
+      clearTimeout(this.trackpadTimeoutId_);
+    } else {
+      view.setHint(ol.ViewHint.INTERACTING, 1);
     }
-    if (this.textStrokeState_) {
-      context.strokeText(this.text_, x, y);
+    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.textFillState_) {
-      context.fillText(this.text_, x, y);
+    if (this.lastAnchor_) {
+      var center = view.calculateCenterZoom(resolution, this.lastAnchor_);
+      view.setCenter(view.constrainCenter(center));
     }
+    view.setResolution(resolution);
+
+    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_
+      });
+    }
+
+    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;
   }
-  if (rotation !== 0 || this.textScale_ != 1) {
-    context.setTransform(1, 0, 0, 1, 0, 0);
-  }
+
+  this.delta_ += delta;
+
+  var timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0);
+
+  clearTimeout(this.timeoutId_);
+  this.timeoutId_ = setTimeout(this.handleWheelZoom_.bind(this, map), timeLeft);
+
+  return false;
 };
 
 
 /**
- * @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 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;
+ol.interaction.MouseWheelZoom.prototype.decrementInteractingHint_ = function() {
+  this.trackpadTimeoutId_ = undefined;
+  var view = this.getMap().getView();
+  view.setHint(ol.ViewHint.INTERACTING, -1);
 };
 
 
 /**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
  * @private
- * @return {number} End.
+ * @param {ol.PluggableMap} map Map.
  */
-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);
+ol.interaction.MouseWheelZoom.prototype.handleWheelZoom_ = function(map) {
+  var view = map.getView();
+  if (view.getAnimating()) {
+    view.cancelAnimations();
   }
-  return offset;
+  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;
 };
 
 
 /**
- * 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
+ * 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.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);
+ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) {
+  this.useAnchor_ = useAnchor;
+  if (!useAnchor) {
+    this.lastAnchor_ = null;
   }
 };
 
 
 /**
- * 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
+ * @enum {string}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.setStyle = function(style) {
-  this.setFillStrokeStyle(style.getFill(), style.getStroke());
-  this.setImageStyle(style.getImage());
-  this.setTextStyle(style.getText());
+ol.interaction.MouseWheelZoom.Mode_ = {
+  TRACKPAD: 'trackpad',
+  WHEEL: 'wheel'
 };
 
+goog.provide('ol.interaction.PinchRotate');
+
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.functions');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.RotationConstraint');
+
 
 /**
- * Render a geometry into the canvas.  Call
- * {@link ol.render.canvas.Immediate#setStyle} first to set the rendering style.
+ * @classdesc
+ * Allows the user to rotate the map by twisting with two fingers
+ * on a touch screen.
  *
- * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render.
- * @override
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.PinchRotateOptions=} opt_options Options.
  * @api
  */
-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:
-  }
+ol.interaction.PinchRotate = function(opt_options) {
+
+  ol.interaction.Pointer.call(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;
+
 };
+ol.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);
 
 
 /**
- * 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
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.PinchRotate}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) {
-  var geometry = style.getGeometryFunction()(feature);
-  if (!geometry ||
-      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
+ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) {
+  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;
+  var view = map.getView();
+  if (view.getConstraints().rotation === ol.RotationConstraint.disable) {
     return;
   }
-  this.setStyle(style);
-  this.drawGeometry(geometry);
-};
 
+  // 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);
 
-/**
- * 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
- */
-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]);
+  // rotate
+  if (this.rotating_) {
+    var rotation = view.getRotation();
+    map.render();
+    ol.interaction.Interaction.rotateWithoutConstraints(view,
+        rotation + rotationDelta, this.anchor_);
   }
 };
 
 
 /**
- * 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
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.PinchRotate}
+ * @private
  */
-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);
+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 {
+    return true;
   }
 };
 
 
 /**
- * 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
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchRotate}
+ * @private
  */
-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);
+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;
   }
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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);
-  }
-};
+ol.interaction.PinchRotate.prototype.shouldStopEvent = ol.functions.FALSE;
 
+goog.provide('ol.interaction.PinchZoom');
 
-/**
- * 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
- */
-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);
-  }
-};
+goog.require('ol');
+goog.require('ol.ViewHint');
+goog.require('ol.functions');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.Pointer');
 
 
 /**
- * Render a Polygon geometry into the canvas.  Rendering is immediate and uses
- * the current style.
+ * @classdesc
+ * Allows the user to zoom the map by pinching with two fingers
+ * on a touch screen.
  *
- * @param {ol.geom.Polygon|ol.render.Feature} geometry Polygon geometry.
- * @override
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.PinchZoomOptions=} opt_options Options.
+ * @api
  */
-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);
-  }
+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);
 
 
 /**
- * Render MultiPolygon geometry into the canvas.  Rendering is immediate and
- * uses the current style.
- * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
- * @override
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.PinchZoom}
+ * @private
  */
-ol.render.canvas.Immediate.prototype.drawMultiPolygon = function(geometry) {
-  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
+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;
   }
-  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();
-    }
+  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 (this.text_ !== '') {
-    var flatInteriorPoints = geometry.getFlatInteriorPoints();
-    this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2);
+
+  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_);
 };
 
 
 /**
- * @param {ol.CanvasFillState} fillState Fill state.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.PinchZoom}
  * @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;
+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;
   }
 };
 
 
 /**
- * @param {ol.CanvasStrokeState} strokeState Stroke state.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.PinchZoom}
  * @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);
+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);
     }
-    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
-    };
+    return true;
   } else {
-    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;
-    }
+    return false;
   }
 };
 
 
 /**
- * @param {ol.CanvasTextState} textState Text state.
- * @private
+ * @inheritDoc
  */
-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;
-    }
-  }
-};
+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');
 
 
 /**
- * Set the fill and stroke style for subsequent draw operations.  To clear
- * either fill or stroke styles, pass null for the appropriate parameter.
+ * 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 {ol.style.Fill} fillStyle Fill style.
- * @param {ol.style.Stroke} strokeStyle Stroke style.
- * @override
+ * @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.render.canvas.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
-  if (!fillStyle) {
-    this.fillState_ = null;
-  } else {
-    var fillStyleColor = fillStyle.getColor();
-    this.fillState_ = {
-      fillStyle: ol.colorlike.asColorLike(fillStyleColor ?
-          fillStyleColor : ol.render.canvas.defaultFillStyle)
-    };
+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());
   }
-  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)
-    };
+
+  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
+    }));
+  }
 
-/**
- * Set the image style for subsequent draw operations.  Pass null to remove
- * the image style.
- *
- * @param {ol.style.Image} imageStyle Image style.
- * @override
- */
-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];
+  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.ImageBase');
 
-/**
- * Set the text style for subsequent draw operations.  Pass null to
- * remove the text style.
- *
- * @param {ol.style.Text} textStyle Text style.
- * @override
- */
-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);
-  }
-};
-
-// FIXME offset panning
-
-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');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
 
 
 /**
  * @constructor
- * @extends {ol.renderer.Map}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
+ * @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.
  */
-ol.renderer.canvas.Map = function(container, map) {
+ol.ImageBase = function(extent, resolution, pixelRatio, state) {
 
-  ol.renderer.Map.call(this, container, map);
+  ol.events.EventTarget.call(this);
 
   /**
-   * @private
-   * @type {CanvasRenderingContext2D}
+   * @protected
+   * @type {ol.Extent}
    */
-  this.context_ = ol.dom.createCanvasContext2D();
+  this.extent = extent;
 
   /**
    * @private
-   * @type {HTMLCanvasElement}
+   * @type {number}
    */
-  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);
+  this.pixelRatio_ = pixelRatio;
 
   /**
-   * @private
-   * @type {boolean}
+   * @protected
+   * @type {number|undefined}
    */
-  this.renderedVisible_ = true;
+  this.resolution = resolution;
 
   /**
-   * @private
-   * @type {ol.Transform}
+   * @protected
+   * @type {ol.ImageState}
    */
-  this.transform_ = ol.transform.create();
+  this.state = state;
 
 };
-ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
+ol.inherits(ol.ImageBase, ol.events.EventTarget);
 
 
 /**
- * @param {ol.render.EventType} type Event type.
- * @param {olx.FrameState} frameState Frame state.
- * @private
+ * @protected
  */
-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;
+ol.ImageBase.prototype.changed = function() {
+  this.dispatchEvent(ol.events.EventType.CHANGE);
+};
 
-    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);
-  }
+/**
+ * @return {ol.Extent} Extent.
+ */
+ol.ImageBase.prototype.getExtent = function() {
+  return this.extent;
 };
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @protected
- * @return {!ol.Transform} Transform.
+ * @abstract
+ * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
  */
-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);
-};
+ol.ImageBase.prototype.getImage = function() {};
 
 
 /**
- * @inheritDoc
+ * @return {number} PixelRatio.
  */
-ol.renderer.canvas.Map.prototype.getType = function() {
-  return ol.renderer.Type.CANVAS;
+ol.ImageBase.prototype.getPixelRatio = function() {
+  return this.pixelRatio_;
 };
 
 
 /**
- * @inheritDoc
+ * @return {number} Resolution.
  */
-ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
-
-  if (!frameState) {
-    if (this.renderedVisible_) {
-      this.canvas_.style.display = 'none';
-      this.renderedVisible_ = false;
-    }
-    return;
-  }
+ol.ImageBase.prototype.getResolution = function() {
+  return /** @type {number} */ (this.resolution);
+};
 
-  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);
-  }
 
-  var rotation = frameState.viewState.rotation;
+/**
+ * @return {ol.ImageState} State.
+ */
+ol.ImageBase.prototype.getState = function() {
+  return this.state;
+};
 
-  this.calculateMatrices2D(frameState);
 
-  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+/**
+ * Load not yet loaded URI.
+ * @abstract
+ */
+ol.ImageBase.prototype.load = function() {};
 
-  var layerStatesArray = frameState.layerStatesArray;
-  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
+goog.provide('ol.ImageState');
 
-  if (rotation) {
-    context.save();
-    ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
-  }
+/**
+ * @enum {number}
+ */
+ol.ImageState = {
+  IDLE: 0,
+  LOADING: 1,
+  LOADED: 2,
+  ERROR: 3
+};
 
-  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);
-    }
-  }
+goog.provide('ol.ImageCanvas');
 
-  if (rotation) {
-    context.restore();
-  }
+goog.require('ol');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
 
-  this.dispatchComposeEvent_(
-      ol.render.EventType.POSTCOMPOSE, frameState);
 
-  if (!this.renderedVisible_) {
-    this.canvas_.style.display = '';
-    this.renderedVisible_ = true;
-  }
+/**
+ * @constructor
+ * @extends {ol.ImageBase}
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {HTMLCanvasElement} canvas Canvas.
+ * @param {ol.ImageCanvasLoader=} opt_loader Optional loader function to
+ *     support asynchronous canvas drawing.
+ */
+ol.ImageCanvas = function(extent, resolution, pixelRatio, canvas, opt_loader) {
 
-  this.scheduleRemoveUnusedLayerRenderers(frameState);
-  this.scheduleExpireIconCache(frameState);
-};
+  /**
+   * 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;
 
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
-        layerFilter, thisArg2) {
-  var result;
-  var viewState = frameState.viewState;
-  var viewResolution = viewState.resolution;
+  ol.ImageBase.call(this, extent, resolution, pixelRatio, state);
 
-  var layerStates = frameState.layerStatesArray;
-  var numLayers = layerStates.length;
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = canvas;
 
-  var coordinate = ol.transform.apply(
-      frameState.pixelToCoordinateTransform, pixel.slice());
+  /**
+   * @private
+   * @type {Error}
+   */
+  this.error_ = null;
 
-  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;
 };
-
-goog.provide('ol.render.ReplayType');
+ol.inherits(ol.ImageCanvas, ol.ImageBase);
 
 
 /**
- * @enum {string}
+ * Get any error associated with asynchronous rendering.
+ * @return {Error} Any error that occurred during rendering.
  */
-ol.render.ReplayType = {
-  CIRCLE: 'Circle',
-  IMAGE: 'Image',
-  LINE_STRING: 'LineString',
-  POLYGON: 'Polygon',
-  TEXT: 'Text'
+ol.ImageCanvas.prototype.getError = function() {
+  return this.error_;
 };
 
-goog.provide('ol.render.replay');
 
-goog.require('ol.render.ReplayType');
+/**
+ * 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();
+};
 
 
 /**
- * @const
- * @type {Array.<ol.render.ReplayType>}
+ * @inheritDoc
  */
-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');
+ol.ImageCanvas.prototype.load = function() {
+  if (this.state == ol.ImageState.IDLE) {
+    this.state = ol.ImageState.LOADING;
+    this.changed();
+    this.loader_(this.handleLoad_.bind(this));
+  }
+};
 
 
 /**
- * Base class for replay groups.
- * @constructor
- * @abstract
+ * @inheritDoc
  */
-ol.render.ReplayGroup = function() {};
+ol.ImageCanvas.prototype.getImage = function() {
+  return this.canvas_;
+};
 
+goog.provide('ol.LayerType');
 
 /**
- * @abstract
- * @param {number|undefined} zIndex Z index.
- * @param {ol.render.ReplayType} replayType Replay type.
- * @return {ol.render.VectorContext} Replay.
+ * A layer type used when creating layer renderers.
+ * @enum {string}
  */
-ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {};
+ol.LayerType = {
+  IMAGE: 'IMAGE',
+  TILE: 'TILE',
+  VECTOR_TILE: 'VECTOR_TILE',
+  VECTOR: 'VECTOR'
+};
 
+goog.provide('ol.layer.VectorRenderType');
 
 /**
- * @abstract
- * @return {boolean} Is empty.
+ * @enum {string}
+ * Render mode for vector layers:
+ *  * `'image'`: Vector layers are rendered as images. Great performance, but
+ *    point symbols and texts are always rotated with the view and pixels are
+ *    scaled during zoom animations.
+ *  * `'vector'`: Vector layers are rendered as vectors. Most accurate rendering
+ *    even during animations, but slower performance.
+ * @api
  */
-ol.render.ReplayGroup.prototype.isEmpty = function() {};
+ol.layer.VectorRenderType = {
+  IMAGE: 'image',
+  VECTOR: 'vector'
+};
 
-goog.provide('ol.webgl.Shader');
+goog.provide('ol.render.Event');
 
 goog.require('ol');
-goog.require('ol.functions');
-
-
-if (ol.ENABLE_WEBGL) {
-
-  /**
-   * @constructor
-   * @abstract
-   * @param {string} source Source.
-   * @struct
-   */
-  ol.webgl.Shader = function(source) {
-
-    /**
-     * @private
-     * @type {string}
-     */
-    this.source_ = source;
-
-  };
+goog.require('ol.events.Event');
 
 
-  /**
-   * @abstract
-   * @return {number} Type.
-   */
-  ol.webgl.Shader.prototype.getType = function() {};
+/**
+ * @constructor
+ * @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.render.Event = function(
+    type, opt_vectorContext, opt_frameState, opt_context,
+    opt_glContext) {
 
+  ol.events.Event.call(this, type);
 
   /**
-   * @return {string} Source.
+   * For canvas, this is an instance of {@link ol.render.canvas.Immediate}.
+   * @type {ol.render.VectorContext|undefined}
+   * @api
    */
-  ol.webgl.Shader.prototype.getSource = function() {
-    return this.source_;
-  };
-
+  this.vectorContext = opt_vectorContext;
 
   /**
-   * @return {boolean} Is animated?
+   * An object representing the current render frame state.
+   * @type {olx.FrameState|undefined}
+   * @api
    */
-  ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE;
-
-}
-
-goog.provide('ol.webgl.Fragment');
-
-goog.require('ol');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Shader');
-
-
-if (ol.ENABLE_WEBGL) {
+  this.frameState = opt_frameState;
 
   /**
-   * @constructor
-   * @extends {ol.webgl.Shader}
-   * @param {string} source Source.
-   * @struct
+   * Canvas context. Only available when a Canvas renderer is used, null
+   * otherwise.
+   * @type {CanvasRenderingContext2D|null|undefined}
+   * @api
    */
-  ol.webgl.Fragment = function(source) {
-    ol.webgl.Shader.call(this, source);
-  };
-  ol.inherits(ol.webgl.Fragment, ol.webgl.Shader);
-
+  this.context = opt_context;
 
   /**
-   * @inheritDoc
+   * WebGL context. Only available when a WebGL renderer is used, null
+   * otherwise.
+   * @type {ol.webgl.Context|null|undefined}
+   * @api
    */
-  ol.webgl.Fragment.prototype.getType = function() {
-    return ol.webgl.FRAGMENT_SHADER;
-  };
+  this.glContext = opt_glContext;
 
-}
+};
+ol.inherits(ol.render.Event, ol.events.Event);
 
-goog.provide('ol.webgl.Vertex');
+goog.provide('ol.structs.LRUCache');
 
 goog.require('ol');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Shader');
-
+goog.require('ol.asserts');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
 
-if (ol.ENABLE_WEBGL) {
 
-  /**
-   * @constructor
-   * @extends {ol.webgl.Shader}
-   * @param {string} source Source.
-   * @struct
-   */
-  ol.webgl.Vertex = function(source) {
-    ol.webgl.Shader.call(this, source);
-  };
-  ol.inherits(ol.webgl.Vertex, ol.webgl.Shader);
+/**
+ * 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
+ * @extends {ol.events.EventTarget}
+ * @fires ol.events.Event
+ * @struct
+ * @template T
+ * @param {number=} opt_highWaterMark High water mark.
+ */
+ol.structs.LRUCache = function(opt_highWaterMark) {
 
+  ol.events.EventTarget.call(this);
 
   /**
-   * @inheritDoc
+   * @type {number}
    */
-  ol.webgl.Vertex.prototype.getType = function() {
-    return ol.webgl.VERTEX_SHADER;
-  };
-
-}
-
-// 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');
-
-if (ol.ENABLE_WEBGL) {
+  this.highWaterMark = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048;
 
   /**
-   * @constructor
-   * @extends {ol.webgl.Fragment}
-   * @struct
+   * @private
+   * @type {number}
    */
-  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);
-
+  this.count_ = 0;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {!Object.<string, ol.LRUCacheEntry>}
    */
-  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';
-
+  this.entries_ = {};
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {?ol.LRUCacheEntry}
    */
-  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;}}';
-
+  this.oldest_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {?ol.LRUCacheEntry}
    */
-  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;
+  this.newest_ = null;
 
+};
 
-  ol.render.webgl.circlereplay.defaultshader.fragment = new ol.render.webgl.circlereplay.defaultshader.Fragment();
+ol.inherits(ol.structs.LRUCache, ol.events.EventTarget);
 
 
-  /**
-   * @constructor
-   * @extends {ol.webgl.Vertex}
-   * @struct
-   */
-  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);
+/**
+ * @return {boolean} Can expire cache.
+ */
+ol.structs.LRUCache.prototype.canExpireCache = function() {
+  return this.getCount() > this.highWaterMark;
+};
 
 
-  /**
-   * @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';
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.structs.LRUCache.prototype.clear = function() {
+  this.count_ = 0;
+  this.entries_ = {};
+  this.oldest_ = null;
+  this.newest_ = null;
+  this.dispatchEvent(ol.events.EventType.CLEAR);
+};
 
 
-  /**
-   * @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);}}';
+/**
+ * @param {string} key Key.
+ * @return {boolean} Contains key.
+ */
+ol.structs.LRUCache.prototype.containsKey = function(key) {
+  return this.entries_.hasOwnProperty(key);
+};
 
 
-  /**
-   * @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;
+/**
+ * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function
+ *     to call for every entry from the oldest to the newer. This function takes
+ *     3 arguments (the entry value, the entry key and the LRUCache object).
+ *     The return value is ignored.
+ * @param {S=} opt_this The object to use as `this` in `f`.
+ * @template S
+ */
+ol.structs.LRUCache.prototype.forEach = function(f, opt_this) {
+  var entry = this.oldest_;
+  while (entry) {
+    f.call(opt_this, entry.value_, entry.key_, this);
+    entry = entry.newer;
+  }
+};
 
 
-  ol.render.webgl.circlereplay.defaultshader.vertex = new ol.render.webgl.circlereplay.defaultshader.Vertex();
+/**
+ * @param {string} key Key.
+ * @return {T} Value.
+ */
+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 {
+    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_;
+};
 
 
-  /**
-   * @constructor
-   * @param {WebGLRenderingContext} gl GL.
-   * @param {WebGLProgram} program Program.
-   * @struct
-   */
-  ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) {
+/**
+ * Remove an entry from the cache.
+ * @param {string} key The entry key.
+ * @return {T} The removed entry.
+ */
+ol.structs.LRUCache.prototype.remove = 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_) {
+    this.newest_ = /** @type {ol.LRUCacheEntry} */ (entry.older);
+    if (this.newest_) {
+      this.newest_.newer = null;
+    }
+  } else if (entry === this.oldest_) {
+    this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
+    if (this.oldest_) {
+      this.oldest_.older = null;
+    }
+  } else {
+    entry.newer.older = entry.older;
+    entry.older.newer = entry.newer;
+  }
+  delete this.entries_[key];
+  --this.count_;
+  return entry.value_;
+};
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_fillColor = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_fillColor' : 'n');
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_lineWidth = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k');
+/**
+ * @return {number} Count.
+ */
+ol.structs.LRUCache.prototype.getCount = function() {
+  return this.count_;
+};
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_offsetRotateMatrix = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_offsetScaleMatrix = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i');
+/**
+ * @return {Array.<string>} Keys.
+ */
+ol.structs.LRUCache.prototype.getKeys = function() {
+  var keys = new Array(this.count_);
+  var i = 0;
+  var entry;
+  for (entry = this.newest_; entry; entry = entry.older) {
+    keys[i++] = entry.key_;
+  }
+  return keys;
+};
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_opacity = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm');
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_pixelRatio = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'l');
+/**
+ * @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_;
+  }
+  return values;
+};
 
-    /**
-     * @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' : 'p');
+/**
+ * @return {T} Last value.
+ */
+ol.structs.LRUCache.prototype.peekLast = function() {
+  return this.oldest_.value_;
+};
 
-    /**
-     * @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');
+/**
+ * @return {string} Last key.
+ */
+ol.structs.LRUCache.prototype.peekLastKey = function() {
+  return this.oldest_.key_;
+};
 
-    /**
-     * @type {number}
-     */
-    this.a_position = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_position' : 'e');
 
-    /**
-     * @type {number}
-     */
-    this.a_radius = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_radius' : 'g');
-  };
+/**
+ * Get the key of the newest item in the cache.  Throws if the cache is empty.
+ * @return {string} The newest key.
+ */
+ol.structs.LRUCache.prototype.peekFirstKey = function() {
+  return this.newest_.key_;
+};
 
-}
 
-goog.provide('ol.vec.Mat4');
+/**
+ * @return {T} value Value.
+ */
+ol.structs.LRUCache.prototype.pop = function() {
+  var entry = this.oldest_;
+  delete this.entries_[entry.key_];
+  if (entry.newer) {
+    entry.newer.older = null;
+  }
+  this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
+  if (!this.oldest_) {
+    this.newest_ = null;
+  }
+  --this.count_;
+  return entry.value_;
+};
 
 
 /**
- * @return {Array.<number>} 4x4 matrix representing a 3D identity transform.
+ * @param {string} key Key.
+ * @param {T} value Value.
  */
-ol.vec.Mat4.create = function() {
-  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
+ol.structs.LRUCache.prototype.replace = function(key, value) {
+  this.get(key);  // update `newest_`
+  this.entries_[key].value_ = value;
 };
 
 
 /**
- * @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.
+ * @param {string} key Key.
+ * @param {T} value Value.
  */
-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;
+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_;
 };
 
-goog.provide('ol.render.webgl.Replay');
 
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.render.VectorContext');
+/**
+ * Prune the cache.
+ */
+ol.structs.LRUCache.prototype.prune = function() {
+  while (this.canExpireCache()) {
+    this.pop();
+  }
+};
+
+goog.provide('ol.render.canvas');
+
+
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.obj');
+goog.require('ol.structs.LRUCache');
 goog.require('ol.transform');
-goog.require('ol.vec.Mat4');
-goog.require('ol.webgl');
 
 
-if (ol.ENABLE_WEBGL) {
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultFont = '10px sans-serif';
 
-  /**
-   * @constructor
-   * @abstract
-   * @extends {ol.render.VectorContext}
-   * @param {number} tolerance Tolerance.
-   * @param {ol.Extent} maxExtent Max extent.
-   * @struct
-   */
-  ol.render.webgl.Replay = function(tolerance, maxExtent) {
-    ol.render.VectorContext.call(this);
 
-    /**
-     * @protected
-     * @type {number}
-     */
-    this.tolerance = tolerance;
+/**
+ * @const
+ * @type {ol.Color}
+ */
+ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
 
-    /**
-     * @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);
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultLineCap = 'round';
 
-    /**
-     * @private
-     * @type {ol.Transform}
-     */
-    this.projectionMatrix_ = ol.transform.create();
 
-    /**
-     * @private
-     * @type {ol.Transform}
-     */
-    this.offsetRotateMatrix_ = ol.transform.create();
+/**
+ * @const
+ * @type {Array.<number>}
+ */
+ol.render.canvas.defaultLineDash = [];
 
-    /**
-     * @private
-     * @type {ol.Transform}
-     */
-    this.offsetScaleMatrix_ = ol.transform.create();
 
-    /**
-     * @private
-     * @type {Array.<number>}
-     */
-    this.tmpMat4_ = ol.vec.Mat4.create();
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.canvas.defaultLineDashOffset = 0;
 
-    /**
-     * @protected
-     * @type {Array.<number>}
-     */
-    this.indices = [];
 
-    /**
-     * @protected
-     * @type {?ol.webgl.Buffer}
-     */
-    this.indicesBuffer = null;
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultLineJoin = 'round';
 
-    /**
-     * 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 = [];
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.canvas.defaultMiterLimit = 10;
 
-    /**
-     * @protected
-     * @type {Array.<number>}
-     */
-    this.vertices = [];
 
-    /**
-     * @protected
-     * @type {?ol.webgl.Buffer}
-     */
-    this.verticesBuffer = null;
+/**
+ * @const
+ * @type {ol.Color}
+ */
+ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
 
-    /**
-     * Optional parameter for PolygonReplay instances.
-     * @protected
-     * @type {ol.render.webgl.LineStringReplay|undefined}
-     */
-    this.lineStringReplay = undefined;
 
-  };
-  ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext);
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultTextAlign = 'center';
 
 
-  /**
-   * @abstract
-   * @param {ol.webgl.Context} context WebGL context.
-   * @return {function()} Delete resources function.
-   */
-  ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = function(context) {};
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.canvas.defaultTextBaseline = 'middle';
 
 
-  /**
-   * @abstract
-   * @param {ol.webgl.Context} context Context.
-   */
-  ol.render.webgl.Replay.prototype.finish = function(context) {};
+/**
+ * @const
+ * @type {Array.<number>}
+ */
+ol.render.canvas.defaultPadding = [0, 0, 0, 0];
 
 
-  /**
-   * @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) {};
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.canvas.defaultLineWidth = 1;
 
 
-  /**
-   * @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) {};
+/**
+ * @type {ol.structs.LRUCache.<HTMLCanvasElement>}
+ */
+ol.render.canvas.labelCache = new ol.structs.LRUCache();
 
 
-  /**
-   * @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) {};
+/**
+ * @type {!Object.<string, number>}
+ */
+ol.render.canvas.checkedFonts_ = {};
 
 
-  /**
-   * @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) {};
+/**
+ * @type {CanvasRenderingContext2D}
+ */
+ol.render.canvas.measureContext_ = null;
 
 
-  /**
-   * @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);
+/**
+ * @type {!Object.<string, number>}
+ */
+ol.render.canvas.textHeights_ = {};
+
+
+/**
+ * Clears the label cache when a font becomes available.
+ * @param {string} fontSpec CSS font spec.
+ */
+ol.render.canvas.checkFont = (function() {
+  var retries = 60;
+  var checked = ol.render.canvas.checkedFonts_;
+  var labelCache = ol.render.canvas.labelCache;
+  var font = '32px monospace';
+  var text = 'wmytzilWMYTZIL@#/&?$%10';
+  var interval, referenceWidth;
+
+  function isAvailable(fontFamily) {
+    var context = ol.render.canvas.getMeasureContext();
+    context.font = font;
+    referenceWidth = context.measureText(text).width;
+    var available = true;
+    if (fontFamily != 'monospace') {
+      context.font = '32px ' + fontFamily + ',monospace';
+      var width = context.measureText(text).width;
+      // If width and referenceWidth are the same, then the 'monospace'
+      // fallback was used instead of the font we wanted, so the font is not
+      // available.
+      available = width != referenceWidth;
     }
-  };
+    return available;
+  }
 
+  function check() {
+    var done = true;
+    for (var font in checked) {
+      if (checked[font] < retries) {
+        if (isAvailable(font)) {
+          checked[font] = retries;
+          ol.obj.clear(ol.render.canvas.textHeights_);
+          // Make sure that loaded fonts are picked up by Safari
+          ol.render.canvas.measureContext_ = null;
+          labelCache.clear();
+        } else {
+          ++checked[font];
+          done = false;
+        }
+      }
+    }
+    if (done) {
+      window.clearInterval(interval);
+      interval = undefined;
+    }
+  }
 
-  /**
-   * @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;
+  return function(fontSpec) {
+    var fontFamilies = ol.css.getFontFamilies(fontSpec);
+    if (!fontFamilies) {
+      return;
+    }
+    for (var i = 0, ii = fontFamilies.length; i < ii; ++i) {
+      var fontFamily = fontFamilies[i];
+      if (!(fontFamily in checked)) {
+        checked[fontFamily] = retries;
+        if (!isAvailable(fontFamily)) {
+          checked[fontFamily] = 0;
+          if (interval === undefined) {
+            interval = window.setInterval(check, 32);
+          }
+        }
+      }
     }
   };
+})();
 
 
-  /**
-   * @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);
-
-      gl.stencilMask(0);
-      gl.stencilFunc(gl.NOTEQUAL, 1, 255);
-    }
-
-    context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer);
-
-    context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
-
-    var locations = this.setUpProgram(gl, context, size, pixelRatio);
-
-    // 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]);
-
-    var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_);
-    if (rotation !== 0) {
-      ol.transform.rotate(offsetRotateMatrix, -rotation);
-    }
-
-    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);
-
-    // 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);
-    }
+/**
+ * @return {CanvasRenderingContext2D} Measure context.
+ */
+ol.render.canvas.getMeasureContext = function() {
+  var context = ol.render.canvas.measureContext_;
+  if (!context) {
+    context = ol.render.canvas.measureContext_ = ol.dom.createCanvasContext2D(1, 1);
+  }
+  return context;
+};
 
-    // disable the vertex attrib arrays
-    this.shutDownProgram(gl, locations);
 
-    if (this.lineStringReplay) {
-      if (!tmpStencil) {
-        gl.disable(gl.STENCIL_TEST);
+/**
+ * @param {string} font Font to use for measuring.
+ * @return {ol.Size} Measurement.
+ */
+ol.render.canvas.measureTextHeight = (function() {
+  var span;
+  var heights = ol.render.canvas.textHeights_;
+  return function(font) {
+    var height = heights[font];
+    if (height == undefined) {
+      if (!span) {
+        span = document.createElement('span');
+        span.textContent = 'M';
+        span.style.margin = span.style.padding = '0 !important';
+        span.style.position = 'absolute !important';
+        span.style.left = '-99999px !important';
       }
-      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));
+      span.style.font = font;
+      document.body.appendChild(span);
+      height = heights[font] = span.offsetHeight;
+      document.body.removeChild(span);
     }
-
-    return result;
-  };
-
-  /**
-   * @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);
+    return height;
   };
+})();
 
-}
-
-goog.provide('ol.render.webgl');
 
-goog.require('ol');
+/**
+ * @param {string} font Font.
+ * @param {string} text Text.
+ * @return {number} Width.
+ */
+ol.render.canvas.measureTextWidth = function(font, text) {
+  var measureContext = ol.render.canvas.getMeasureContext();
+  if (font != measureContext.font) {
+    measureContext.font = font;
+  }
+  return measureContext.measureText(text).width;
+};
 
 
-if (ol.ENABLE_WEBGL) {
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} rotation Rotation.
+ * @param {number} offsetX X offset.
+ * @param {number} offsetY Y offset.
+ */
+ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) {
+  if (rotation !== 0) {
+    context.translate(offsetX, offsetY);
+    context.rotate(rotation);
+    context.translate(-offsetX, -offsetY);
+  }
+};
 
-  /**
-   * @const
-   * @type {ol.Color}
-   */
-  ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0];
 
-  /**
-   * @const
-   * @type {string}
-   */
-  ol.render.webgl.defaultLineCap = 'round';
+ol.render.canvas.resetTransform_ = ol.transform.create();
 
 
-  /**
-   * @const
-   * @type {Array.<number>}
-   */
-  ol.render.webgl.defaultLineDash = [];
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {ol.Transform|null} transform Transform.
+ * @param {number} opacity Opacity.
+ * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
+ * @param {number} originX Origin X.
+ * @param {number} originY Origin Y.
+ * @param {number} w Width.
+ * @param {number} h Height.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {number} scale Scale.
+ */
+ol.render.canvas.drawImage = function(context,
+    transform, opacity, image, originX, originY, w, h, x, y, scale) {
+  var alpha;
+  if (opacity != 1) {
+    alpha = context.globalAlpha;
+    context.globalAlpha = alpha * opacity;
+  }
+  if (transform) {
+    context.setTransform.apply(context, transform);
+  }
 
+  context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.render.webgl.defaultLineDashOffset = 0;
+  if (alpha) {
+    context.globalAlpha = alpha;
+  }
+  if (transform) {
+    context.setTransform.apply(context, ol.render.canvas.resetTransform_);
+  }
+};
 
+goog.provide('ol.color');
 
-  /**
-   * @const
-   * @type {string}
-   */
-  ol.render.webgl.defaultLineJoin = 'round';
+goog.require('ol.asserts');
+goog.require('ol.math');
 
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.render.webgl.defaultMiterLimit = 10;
+/**
+ * This RegExp matches # followed by 3, 4, 6, or 8 hex digits.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.color.HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3,4}){1,2}$/i;
 
-  /**
-   * @const
-   * @type {ol.Color}
-   */
-  ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0];
 
-  /**
-   * @const
-   * @type {number}
-   */
-  ol.render.webgl.defaultLineWidth = 1;
+/**
+ * Regular expression for matching potential named color style strings.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ol.color.NAMED_COLOR_RE_ = /^([a-z]*)$/i;
 
-  /**
-   * 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;
+/**
+ * Return the color as an array. This function maintains a cache of calculated
+ * arrays which means the result should not be modified.
+ * @param {ol.Color|string} color Color.
+ * @return {ol.Color} Color.
+ * @api
+ */
+ol.color.asArray = function(color) {
+  if (Array.isArray(color)) {
+    return color;
+  } else {
+    return ol.color.fromString(/** @type {string} */ (color));
+  }
+};
 
-}
 
-goog.provide('ol.webgl.Buffer');
+/**
+ * Return the color as an rgba string.
+ * @param {ol.Color|string} color Color.
+ * @return {string} Rgba string.
+ * @api
+ */
+ol.color.asString = function(color) {
+  if (typeof color === 'string') {
+    return color;
+  } else {
+    return ol.color.toString(color);
+  }
+};
 
-goog.require('ol');
-goog.require('ol.webgl');
+/**
+ * Return named color as an rgba string.
+ * @param {string} color Named color.
+ * @return {string} Rgb string.
+ */
+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;
+};
 
 
-if (ol.ENABLE_WEBGL) {
+/**
+ * @param {string} s String.
+ * @return {ol.Color} Color.
+ */
+ol.color.fromString = (
+  function() {
 
-  /**
-   * @constructor
-   * @param {Array.<number>=} opt_arr Array.
-   * @param {number=} opt_usage Usage.
-   * @struct
-   */
-  ol.webgl.Buffer = function(opt_arr, opt_usage) {
+    // 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.
 
     /**
-     * @private
-     * @type {Array.<number>}
+     * @const
+     * @type {number}
      */
-    this.arr_ = opt_arr !== undefined ? opt_arr : [];
+    var MAX_CACHE_SIZE = 1024;
+
+    /**
+     * @type {Object.<string, ol.Color>}
+     */
+    var cache = {};
 
     /**
-     * @private
      * @type {number}
      */
-    this.usage_ = opt_usage !== undefined ?
-        opt_usage : ol.webgl.Buffer.Usage_.STATIC_DRAW;
+    var cacheSize = 0;
 
-  };
+    return (
+      /**
+       * @param {string} s String.
+       * @return {ol.Color} Color.
+       */
+      function(s) {
+        var color;
+        if (cache.hasOwnProperty(s)) {
+          color = cache[s];
+        } else {
+          if (cacheSize >= MAX_CACHE_SIZE) {
+            var i = 0;
+            var key;
+            for (key in cache) {
+              if ((i++ & 3) === 0) {
+                delete cache[key];
+                --cacheSize;
+              }
+            }
+          }
+          color = ol.color.fromStringInternal_(s);
+          cache[s] = color;
+          ++cacheSize;
+        }
+        return color;
+      });
 
+  })();
 
-  /**
-   * @return {Array.<number>} Array.
-   */
-  ol.webgl.Buffer.prototype.getArray = function() {
-    return this.arr_;
-  };
 
+/**
+ * @param {string} s String.
+ * @private
+ * @return {ol.Color} Color.
+ */
+ol.color.fromStringInternal_ = function(s) {
+  var r, g, b, a, color, parts;
 
-  /**
-   * @return {number} Usage.
-   */
-  ol.webgl.Buffer.prototype.getUsage = function() {
-    return this.usage_;
-  };
+  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
+    var d; // number of digits per channel
+    if (n <= 4) {
+      d = 1;
+    } else {
+      d = 2;
+    }
+    var hasAlpha = n === 4 || n === 8;
+    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 (hasAlpha) {
+      a = parseInt(s.substr(1 + 3 * d, d), 16);
+    } else {
+      a = 255;
+    }
+    if (d == 1) {
+      r = (r << 4) + r;
+      g = (g << 4) + g;
+      b = (b << 4) + b;
+      if (hasAlpha) {
+        a = (a << 4) + a;
+      }
+    }
+    color = [r, g, b, a / 255];
+  } 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);
+};
 
-  /**
-   * @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
-  };
 
-}
+/**
+ * @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;
+};
 
-goog.provide('ol.render.webgl.CircleReplay');
 
-goog.require('ol');
-goog.require('ol.array');
+/**
+ * @param {ol.Color} color Color.
+ * @return {string} String.
+ */
+ol.color.toString = function(color) {
+  var r = color[0];
+  if (r != (r | 0)) {
+    r = (r + 0.5) | 0;
+  }
+  var g = color[1];
+  if (g != (g | 0)) {
+    g = (g + 0.5) | 0;
+  }
+  var b = color[2];
+  if (b != (b | 0)) {
+    b = (b + 0.5) | 0;
+  }
+  var a = color[3] === undefined ? 1 : color[3];
+  return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
+};
+
+goog.provide('ol.colorlike');
+
 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');
 
 
-if (ol.ENABLE_WEBGL) {
+/**
+ * @param {ol.Color|ol.ColorLike} color Color.
+ * @return {ol.ColorLike} The color as an ol.ColorLike
+ * @api
+ */
+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));
+  }
+};
 
-  /**
-   * @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;
+/**
+ * @param {?} color The value that is potentially an ol.ColorLike
+ * @return {boolean} Whether the color is an ol.ColorLike
+ */
+ol.colorlike.isColorLike = function(color) {
+  return (
+    typeof color === 'string' ||
+    color instanceof CanvasPattern ||
+    color instanceof CanvasGradient
+  );
+};
 
-    /**
-     * @private
-     * @type {Array.<Array.<Array.<number>|number>>}
-     */
-    this.styles_ = [];
+goog.provide('ol.render.VectorContext');
 
-    /**
-     * @private
-     * @type {Array.<number>}
-     */
-    this.styleIndices_ = [];
 
-    /**
-     * @private
-     * @type {number}
-     */
-    this.radius_ = 0;
+/**
+ * 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.render.VectorContext = function() {
+};
 
-    /**
-     * @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);
+/**
+ * Render a geometry with a custom renderer.
+ *
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {Function} renderer Renderer.
+ */
+ol.render.VectorContext.prototype.drawCustom = function(geometry, feature, renderer) {};
 
 
-  /**
-   * @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_;
-
-      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_;
-
-      this.vertices[numVertices++] = flatCoordinates[i];
-      this.vertices[numVertices++] = flatCoordinates[i + 1];
-      this.vertices[numVertices++] = 3;
-      this.vertices[numVertices++] = this.radius_;
+/**
+ * Render a geometry.
+ *
+ * @param {ol.geom.Geometry} geometry The geometry to render.
+ */
+ol.render.VectorContext.prototype.drawGeometry = function(geometry) {};
 
-      this.indices[numIndices++] = n;
-      this.indices[numIndices++] = n + 1;
-      this.indices[numIndices++] = n + 2;
 
-      this.indices[numIndices++] = n + 2;
-      this.indices[numIndices++] = n + 3;
-      this.indices[numIndices++] = n;
+/**
+ * Set the rendering style.
+ *
+ * @param {ol.style.Style} style The rendering style.
+ */
+ol.render.VectorContext.prototype.setStyle = function(style) {};
 
-      n += 4;
-    }
-  };
 
+/**
+ * @param {ol.geom.Circle} circleGeometry Circle geometry.
+ * @param {ol.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {};
 
-  /**
-   * @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;
-      }
 
-      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;
-        }
-      }
-    }
-  };
+/**
+ * @param {ol.Feature} feature Feature.
+ * @param {ol.style.Style} style Style.
+ */
+ol.render.VectorContext.prototype.drawFeature = function(feature, style) {};
 
 
-  /**
-   * @inheritDoc
-   **/
-  ol.render.webgl.CircleReplay.prototype.finish = function(context) {
-    // create, bind, and populate the vertices buffer
-    this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
+/**
+ * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
+ *     collection.
+ * @param {ol.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {};
 
-    // create, bind, and populate the indices buffer
-    this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
-    this.startIndices.push(this.indices.length);
+/**
+ * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line
+ *     string geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {};
 
-    //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;
-  };
+/**
+ * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry
+ *     MultiLineString geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {};
 
 
-  /**
-   * @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);
-    };
-  };
+/**
+ * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint
+ *     geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {};
 
 
-  /**
-   * @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_;
-    }
+/**
+ * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {};
 
-    context.useProgram(program);
 
-    // enable the vertex attrib arrays
-    gl.enableVertexAttribArray(locations.a_position);
-    gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
-        false, 16, 0);
+/**
+ * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {};
 
-    gl.enableVertexAttribArray(locations.a_instruction);
-    gl.vertexAttribPointer(locations.a_instruction, 1, ol.webgl.FLOAT,
-        false, 16, 8);
 
-    gl.enableVertexAttribArray(locations.a_radius);
-    gl.vertexAttribPointer(locations.a_radius, 1, ol.webgl.FLOAT,
-        false, 16, 12);
+/**
+ * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon
+ *     geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {};
 
-    // Enable renderer specific uniforms.
-    gl.uniform2fv(locations.u_size, size);
-    gl.uniform1f(locations.u_pixelRatio, pixelRatio);
 
-    return locations;
-  };
+/**
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.VectorContext.prototype.drawText = function(geometry, feature) {};
 
 
-  /**
-   * @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);
-  };
+/**
+ * @param {ol.style.Fill} fillStyle Fill style.
+ * @param {ol.style.Stroke} strokeStyle Stroke style.
+ */
+ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {};
 
 
-  /**
-   * @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;
-      }
-    }
-  };
+/**
+ * @param {ol.style.Image} imageStyle Image style.
+ * @param {ol.DeclutterGroup=} opt_declutterGroup Declutter.
+ */
+ol.render.VectorContext.prototype.setImageStyle = function(imageStyle, opt_declutterGroup) {};
 
 
-  /**
-   * @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);
+/**
+ * @param {ol.style.Text} textStyle Text style.
+ * @param {ol.DeclutterGroup=} opt_declutterGroup Declutter.
+ */
+ol.render.VectorContext.prototype.setTextStyle = function(textStyle, opt_declutterGroup) {};
 
-          var result = featureCallback(feature);
+// 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 (result) {
-            return result;
-          }
+goog.provide('ol.render.canvas.Immediate');
+
+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');
 
-        }
-        featureIndex--;
-        end = start;
-      }
-    }
-    return undefined;
-  };
 
+/**
+ * @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
+ */
+ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform, viewRotation) {
+  ol.render.VectorContext.call(this);
 
   /**
    * @private
-   * @param {WebGLRenderingContext} gl gl.
-   * @param {ol.webgl.Context} context Context.
-   * @param {Object} skippedFeaturesHash Ids of features to skip.
+   * @type {CanvasRenderingContext2D}
    */
-  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];
-
-      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;
-    }
-  };
-
+  this.context_ = context;
 
   /**
    * @private
-   * @param {WebGLRenderingContext} gl gl.
-   * @param {Array.<number>} color Color.
+   * @type {number}
    */
-  ol.render.webgl.CircleReplay.prototype.setFillStyle_ = function(gl, color) {
-    gl.uniform4fv(this.defaultLocations_.u_fillColor, color);
-  };
-
+  this.pixelRatio_ = pixelRatio;
 
   /**
    * @private
-   * @param {WebGLRenderingContext} gl gl.
-   * @param {Array.<number>} color Color.
-   * @param {number} lineWidth Line width.
+   * @type {ol.Extent}
    */
-  ol.render.webgl.CircleReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth) {
-    gl.uniform4fv(this.defaultLocations_.u_strokeColor, color);
-    gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth);
-  };
-
+  this.extent_ = extent;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {ol.Transform}
    */
-  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]);
-    }
-  };
-
-}
-
-// This file is automatically generated, do not edit
-/* eslint openlayers-internal/no-missing-requires: 0 */
-goog.provide('ol.render.webgl.imagereplay.defaultshader');
-
-goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
-
-if (ol.ENABLE_WEBGL) {
+  this.transform_ = transform;
 
   /**
-   * @constructor
-   * @extends {ol.webgl.Fragment}
-   * @struct
+   * @private
+   * @type {number}
    */
-  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);
-
+  this.viewRotation_ = viewRotation;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {?ol.CanvasFillState}
    */
-  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';
-
+  this.contextFillState_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {?ol.CanvasStrokeState}
    */
-  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;}';
-
+  this.contextStrokeState_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {?ol.CanvasTextState}
    */
-  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;
-
-
-  ol.render.webgl.imagereplay.defaultshader.fragment = new ol.render.webgl.imagereplay.defaultshader.Fragment();
-
+  this.contextTextState_ = null;
 
   /**
-   * @constructor
-   * @extends {ol.webgl.Vertex}
-   * @struct
+   * @private
+   * @type {?ol.CanvasFillState}
    */
-  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);
-
+  this.fillState_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {?ol.CanvasStrokeState}
    */
-  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';
-
+  this.strokeState_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
    */
-  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.image_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {number}
    */
-  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;
-
-
-  ol.render.webgl.imagereplay.defaultshader.vertex = new ol.render.webgl.imagereplay.defaultshader.Vertex();
+  this.imageAnchorX_ = 0;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageAnchorY_ = 0;
 
   /**
-   * @constructor
-   * @param {WebGLRenderingContext} gl GL.
-   * @param {WebGLProgram} program Program.
-   * @struct
+   * @private
+   * @type {number}
    */
-  ol.render.webgl.imagereplay.defaultshader.Locations = function(gl, program) {
+  this.imageHeight_ = 0;
 
-    /**
-     * @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');
-
-    /**
-     * @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');
-
-    /**
-     * @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');
-
-    /**
-     * @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');
-
-    /**
-     * @type {number}
-     */
-    this.a_texCoord = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'd');
-  };
-
-}
-
-goog.provide('ol.webgl.ContextEventType');
-
-
-/**
- * @enum {string}
- */
-ol.webgl.ContextEventType = {
-  LOST: 'webglcontextlost',
-  RESTORED: 'webglcontextrestored'
-};
-
-goog.provide('ol.webgl.Context');
-
-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) {
+  /**
+   * @private
+   * @type {number}
+   */
+  this.imageOpacity_ = 0;
 
   /**
-   * @classdesc
-   * A WebGL context for accessing low-level WebGL capabilities.
-   *
-   * @constructor
-   * @extends {ol.Disposable}
-   * @param {HTMLCanvasElement} canvas Canvas.
-   * @param {WebGLRenderingContext} gl GL.
+   * @private
+   * @type {number}
    */
-  ol.webgl.Context = function(canvas, gl) {
-
-    /**
-     * @private
-     * @type {HTMLCanvasElement}
-     */
-    this.canvas_ = canvas;
-
-    /**
-     * @private
-     * @type {WebGLRenderingContext}
-     */
-    this.gl_ = gl;
-
-    /**
-     * @private
-     * @type {Object.<string, ol.WebglBufferCacheEntry>}
-     */
-    this.bufferCache_ = {};
-
-    /**
-     * @private
-     * @type {Object.<string, 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) {
-      gl.getExtension('OES_element_index_uint');
-    }
-
-    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);
-
+  this.imageOriginX_ = 0;
 
   /**
-   * 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.
+   * @private
+   * @type {number}
    */
-  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
-      };
-    }
-  };
-
+  this.imageOriginY_ = 0;
 
   /**
-   * @param {ol.webgl.Buffer} buf Buffer.
+   * @private
+   * @type {boolean}
    */
-  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];
-  };
-
+  this.imageRotateWithView_ = false;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {number}
    */
-  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_);
-    }
-  };
-
+  this.imageRotation_ = 0;
 
   /**
-   * @return {HTMLCanvasElement} Canvas.
+   * @private
+   * @type {number}
    */
-  ol.webgl.Context.prototype.getCanvas = function() {
-    return this.canvas_;
-  };
-
+  this.imageScale_ = 0;
 
   /**
-   * Get the WebGL rendering context
-   * @return {WebGLRenderingContext} The rendering context.
-   * @api
+   * @private
+   * @type {boolean}
    */
-  ol.webgl.Context.prototype.getGL = function() {
-    return this.gl_;
-  };
-
+  this.imageSnapToPixel_ = false;
 
   /**
-   * Get the frame buffer for hit detection.
-   * @return {WebGLFramebuffer} The hit detection frame buffer.
+   * @private
+   * @type {number}
    */
-  ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() {
-    if (!this.hitDetectionFramebuffer_) {
-      this.initHitDetectionFramebuffer_();
-    }
-    return this.hitDetectionFramebuffer_;
-  };
-
+  this.imageWidth_ = 0;
 
   /**
-   * 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.
+   * @private
+   * @type {string}
    */
-  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;
-    }
-  };
-
+  this.text_ = '';
 
   /**
-   * 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.
+   * @private
+   * @type {number}
    */
-  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;
-    }
-  };
-
+  this.textOffsetX_ = 0;
 
   /**
-   * FIXME empy description for jsdoc
+   * @private
+   * @type {number}
    */
-  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;
-  };
-
+  this.textOffsetY_ = 0;
 
   /**
-   * FIXME empy description for jsdoc
+   * @private
+   * @type {boolean}
    */
-  ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
-  };
-
+  this.textRotateWithView_ = false;
 
   /**
-   * Creates a 1x1 pixel framebuffer for the hit-detection.
    * @private
+   * @type {number}
    */
-  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;
-  };
-
+  this.textRotation_ = 0;
 
   /**
-   * Use a program.  If the program is already in use, this will return `false`.
-   * @param {WebGLProgram} program Program.
-   * @return {boolean} Changed.
-   * @api
+   * @private
+   * @type {number}
    */
-  ol.webgl.Context.prototype.useProgram = function(program) {
-    if (program == this.currentProgram_) {
-      return false;
-    } else {
-      var gl = this.getGL();
-      gl.useProgram(program);
-      this.currentProgram_ = program;
-      return true;
-    }
-  };
-
+  this.textScale_ = 0;
 
   /**
-   * @param {WebGLRenderingContext} gl WebGL rendering context.
-   * @param {number=} opt_wrapS wrapS.
-   * @param {number=} opt_wrapT wrapT.
-   * @return {WebGLTexture} The texture.
    * @private
+   * @type {?ol.CanvasFillState}
    */
-  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(
-          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;
-  };
-
+  this.textFillState_ = null;
 
   /**
-   * @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.
+   * @private
+   * @type {?ol.CanvasStrokeState}
    */
-  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;
-  };
-
+  this.textStrokeState_ = null;
 
   /**
-   * @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.
+   * @private
+   * @type {?ol.CanvasTextState}
    */
-  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.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');
-
-
-if (ol.ENABLE_WEBGL) {
+  this.textState_ = null;
 
   /**
-   * @constructor
-   * @extends {ol.render.webgl.Replay}
-   * @param {number} tolerance Tolerance.
-   * @param {ol.Extent} maxExtent Max extent.
-   * @struct
+   * @private
+   * @type {Array.<number>}
    */
-  ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
-    ol.render.webgl.Replay.call(this, tolerance, maxExtent);
-
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.anchorX_ = undefined;
-
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.anchorY_ = undefined;
-
-    /**
-     * @type {Array.<number>}
-     * @private
-     */
-    this.groupIndices_ = [];
-
-    /**
-     * @type {Array.<number>}
-     * @private
-     */
-    this.hitDetectionGroupIndices_ = [];
-
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.height_ = undefined;
+  this.pixelCoordinates_ = [];
 
-    /**
-     * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
-     * @private
-     */
-    this.images_ = [];
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.tmpLocalTransform_ = ol.transform.create();
 
-    /**
-     * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
-     * @private
-     */
-    this.hitDetectionImages_ = [];
+};
+ol.inherits(ol.render.canvas.Immediate, ol.render.VectorContext);
 
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.imageHeight_ = undefined;
 
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.imageWidth_ = undefined;
+/**
+ * @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;
+  }
+  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;
+  }
+};
 
-    /**
-     * @private
-     * @type {ol.render.webgl.imagereplay.defaultshader.Locations}
-     */
-    this.defaultLocations_ = null;
 
-    /**
-     * @private
-     * @type {number|undefined}
-     */
-    this.opacity_ = undefined;
+/**
+ * @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_);
+  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);
+  }
+};
 
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.originX_ = undefined;
 
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.originY_ = undefined;
+/**
+ * @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 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;
+};
 
-    /**
-     * @private
-     * @type {boolean|undefined}
-     */
-    this.rotateWithView_ = undefined;
 
-    /**
-     * @private
-     * @type {number|undefined}
-     */
-    this.rotation_ = undefined;
+/**
+ * @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 i, ii;
+  for (i = 0, ii = ends.length; i < ii; ++i) {
+    offset = this.moveToLineTo_(
+        flatCoordinates, offset, ends[i], stride, true);
+  }
+  return offset;
+};
 
-    /**
-     * @private
-     * @type {number|undefined}
-     */
-    this.scale_ = undefined;
 
-    /**
-     * @type {Array.<WebGLTexture>}
-     * @private
-     */
-    this.textures_ = [];
+/**
+ * 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
+ */
+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);
+  }
+};
 
-    /**
-     * @type {Array.<WebGLTexture>}
-     * @private
-     */
-    this.hitDetectionTextures_ = [];
 
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.width_ = undefined;
-  };
-  ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay);
+/**
+ * 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.canvas.Immediate.prototype.setStyle = function(style) {
+  this.setFillStrokeStyle(style.getFill(), style.getStroke());
+  this.setImageStyle(style.getImage());
+  this.setTextStyle(style.getText());
+};
 
 
-  /**
-   * @inheritDoc
-   */
-  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);
-    };
-  };
-
-
-  /**
-   * @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];
+/**
+ * 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
+ */
+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:
+  }
+};
 
-      // 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;
-    }
+/**
+ * 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
+ */
+ol.render.canvas.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);
+};
 
-    return numVertices;
-  };
 
+/**
+ * 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
+ */
+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]);
+  }
+};
 
-  /**
-   * @inheritDoc
-   */
-  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);
-  };
 
+/**
+ * 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
+ */
+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);
+  }
+};
 
-  /**
-   * @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);
-  };
 
+/**
+ * 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
+ */
+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);
+  }
+};
 
-  /**
-   * @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;
-  };
 
+/**
+ * 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
+ */
+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);
+  }
+};
 
-  /**
-   * @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];
 
-      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;
+/**
+ * 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
+ */
+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);
+  }
+};
 
 
-  /**
-   * @inheritDoc
-   */
-  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_;
+/**
+ * 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
+ */
+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_);
     }
-
-    // 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, ol.webgl.FLOAT,
-        false, 32, 0);
-
-    gl.enableVertexAttribArray(locations.a_offsets);
-    gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT,
-        false, 32, 8);
-
-    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);
-
-    gl.enableVertexAttribArray(locations.a_rotateWithView);
-    gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT,
-        false, 32, 28);
-
-    return locations;
-  };
-
-
-  /**
-   * @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);
-  };
-
-
-  /**
-   * @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_;
-
-    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;
-      }
+    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);
+  }
+};
 
-  /**
-   * 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;
 
+/**
+ * Render MultiPolygon geometry into the canvas.  Rendering is immediate and
+ * uses the current style.
+ * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
+ * @override
+ */
+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;
-    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++;
-      }
-
-      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);
-      }
+    context.beginPath();
+    for (i = 0, ii = endss.length; i < ii; ++i) {
+      var ends = endss[i];
+      offset = this.drawRings_(flatCoordinates, offset, ends, stride);
     }
-  };
-
-
-  /**
-   * @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);
+    if (this.fillState_) {
+      context.fill();
+    }
+    if (this.strokeState_) {
+      context.stroke();
+    }
+  }
+  if (this.text_ !== '') {
+    var flatInteriorPoints = geometry.getFlatInteriorPoints();
+    this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2);
+  }
+};
 
-          var result = featureCallback(feature);
-          if (result) {
-            return result;
-          }
-        }
 
-        end = start;
-        featureIndex--;
-      }
+/**
+ * @param {ol.CanvasFillState} 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;
     }
-    return undefined;
-  };
+  }
+};
 
 
-  /**
-   * @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 {
-      currentImage = this.images_[this.images_.length - 1];
-      if (ol.getUid(currentImage) != ol.getUid(image)) {
-        this.groupIndices_.push(this.indices.length);
-        this.images_.push(image);
-      }
+/**
+ * @param {ol.CanvasStrokeState} 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.lineDashOffset = strokeState.lineDashOffset;
     }
-
-    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);
+    context.lineJoin = strokeState.lineJoin;
+    context.lineWidth = strokeState.lineWidth;
+    context.miterLimit = strokeState.miterLimit;
+    context.strokeStyle = strokeState.strokeStyle;
+    this.contextStrokeState_ = {
+      lineCap: strokeState.lineCap,
+      lineDash: strokeState.lineDash,
+      lineDashOffset: strokeState.lineDashOffset,
+      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 (!ol.array.equals(
+          contextStrokeState.lineDash, strokeState.lineDash)) {
+        context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash);
+      }
+      if (contextStrokeState.lineDashOffset != strokeState.lineDashOffset) {
+        contextStrokeState.lineDashOffset = context.lineDashOffset =
+            strokeState.lineDashOffset;
       }
     }
+    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;
+    }
+  }
+};
 
-    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];
-  };
-
-}
-
-goog.provide('ol.geom.flat.topology');
-
-goog.require('ol.geom.flat.area');
 
 /**
- * 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.
+ * @param {ol.CanvasTextState} textState Text state.
+ * @private
  */
-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);
+ol.render.canvas.Immediate.prototype.setContextTextState_ = function(textState) {
+  var context = this.context_;
+  var contextTextState = this.contextTextState_;
+  var textAlign = textState.textAlign ?
+    textState.textAlign : ol.render.canvas.defaultTextAlign;
+  if (!contextTextState) {
+    context.font = textState.font;
+    context.textAlign = textAlign;
+    context.textBaseline = textState.textBaseline;
+    this.contextTextState_ = {
+      font: textState.font,
+      textAlign: textAlign,
+      textBaseline: textState.textBaseline
+    };
+  } else {
+    if (contextTextState.font != textState.font) {
+      contextTextState.font = context.font = textState.font;
+    }
+    if (contextTextState.textAlign != textAlign) {
+      contextTextState.textAlign = textAlign;
+    }
+    if (contextTextState.textBaseline != textState.textBaseline) {
+      contextTextState.textBaseline = context.textBaseline =
+          textState.textBaseline;
+    }
   }
-  return false;
 };
 
-// This file is automatically generated, do not edit
-/* eslint openlayers-internal/no-missing-requires: 0 */
-goog.provide('ol.render.webgl.linestringreplay.defaultshader');
-
-goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
-
-if (ol.ENABLE_WEBGL) {
-
-  /**
-   * @constructor
-   * @extends {ol.webgl.Fragment}
-   * @struct
-   */
-  ol.render.webgl.linestringreplay.defaultshader.Fragment = function() {
-    ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE);
-  };
-  ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment);
-
 
-  /**
-   * @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';
+/**
+ * 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.render.canvas.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+  if (!fillStyle) {
+    this.fillState_ = null;
+  } else {
+    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)
+    };
+  }
+};
 
 
-  /**
-   * @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;}';
-
-
-  /**
-   * @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;
+/**
+ * Set the image style for subsequent draw operations.  Pass null to remove
+ * the image style.
+ *
+ * @param {ol.style.Image} imageStyle Image style.
+ * @override
+ */
+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.pixelRatio_;
+    this.imageSnapToPixel_ = imageStyle.getSnapToPixel();
+    this.imageWidth_ = imageSize[0];
+  }
+};
 
 
-  ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment();
+/**
+ * Set the text style for subsequent draw operations.  Pass null to
+ * remove the text style.
+ *
+ * @param {ol.style.Text} textStyle Text style.
+ * @override
+ */
+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);
+  }
+};
 
+goog.provide('ol.renderer.Layer');
 
-  /**
-   * @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);
+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');
 
 
-  /**
-   * @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';
+/**
+ * @constructor
+ * @extends {ol.Observable}
+ * @param {ol.layer.Layer} layer Layer.
+ * @struct
+ */
+ol.renderer.Layer = function(layer) {
 
+  ol.Observable.call(this);
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {ol.layer.Layer}
    */
-  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);}}';
+  this.layer_ = layer;
 
 
-  /**
-   * @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;
+};
+ol.inherits(ol.renderer.Layer, ol.Observable);
 
 
-  ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex();
+/**
+ * @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.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
 
 
-  /**
-   * @constructor
-   * @param {WebGLRenderingContext} gl GL.
-   * @param {WebGLProgram} program Program.
-   * @struct
-   */
-  ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) {
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState Frame state.
+ * @return {boolean} Is there a feature at the given coordinate?
+ */
+ol.renderer.Layer.prototype.hasFeatureAtCoordinate = ol.functions.FALSE;
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_color = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_color' : 'n');
 
+/**
+ * Create a function that adds loaded tiles to the tile lookup.
+ * @param {ol.source.Tile} source Tile source.
+ * @param {ol.proj.Projection} projection Projection of the tiles.
+ * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded
+ *     tiles by zoom level.
+ * @return {function(number, ol.TileRange):boolean} A function that can be
+ *     called with a zoom level and a tile range to add loaded tiles to the
+ *     lookup.
+ * @protected
+ */
+ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
+  return (
     /**
-     * @type {WebGLUniformLocation}
+     * @param {number} zoom Zoom level.
+     * @param {ol.TileRange} tileRange Tile range.
+     * @return {boolean} The tile range is fully loaded.
      */
-    this.u_lineWidth = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k');
+    function(zoom, tileRange) {
+      function callback(tile) {
+        if (!tiles[zoom]) {
+          tiles[zoom] = {};
+        }
+        tiles[zoom][tile.tileCoord.toString()] = tile;
+      }
+      return source.forEachLoadedTile(projection, zoom, tileRange, callback);
+    });
+};
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_miterLimit = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_miterLimit' : 'l');
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_offsetRotateMatrix = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
+/**
+ * @return {ol.layer.Layer} Layer.
+ */
+ol.renderer.Layer.prototype.getLayer = function() {
+  return this.layer_;
+};
 
-    /**
-     * @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');
+/**
+ * Handle changes in image state.
+ * @param {ol.events.Event} event Image change event.
+ * @private
+ */
+ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
+  var image = /** @type {ol.Image} */ (event.target);
+  if (image.getState() === ol.ImageState.LOADED) {
+    this.renderIfReadyAndVisible();
+  }
+};
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_pixelRatio = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'p');
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_projectionMatrix = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h');
+/**
+ * Load the image if not already loaded, and register the image change
+ * listener if needed.
+ * @param {ol.ImageBase} image Image.
+ * @return {boolean} `true` if the image is already loaded, `false`
+ *     otherwise.
+ * @protected
+ */
+ol.renderer.Layer.prototype.loadImage = function(image) {
+  var imageState = image.getState();
+  if (imageState != ol.ImageState.LOADED &&
+      imageState != ol.ImageState.ERROR) {
+    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;
+};
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_size = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_size' : 'o');
 
-    /**
-     * @type {number}
-     */
-    this.a_direction = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_direction' : 'g');
+/**
+ * @protected
+ */
+ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
+  var layer = this.getLayer();
+  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
+    this.changed();
+  }
+};
 
-    /**
-     * @type {number}
-     */
-    this.a_lastPos = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_lastPos' : 'd');
 
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Tile} tileSource Tile source.
+ * @protected
+ */
+ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) {
+  if (tileSource.canExpireCache()) {
     /**
-     * @type {number}
+     * @param {ol.source.Tile} tileSource Tile source.
+     * @param {ol.PluggableMap} map Map.
+     * @param {olx.FrameState} frameState Frame state.
      */
-    this.a_nextPos = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_nextPos' : 'f');
+    var postRenderFunction = function(tileSource, map, frameState) {
+      var tileSourceKey = ol.getUid(tileSource).toString();
+      if (tileSourceKey in frameState.usedTiles) {
+        tileSource.expireCache(frameState.viewState.projection,
+            frameState.usedTiles[tileSourceKey]);
+      }
+    }.bind(null, tileSource);
 
-    /**
-     * @type {number}
-     */
-    this.a_position = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_position' : 'e');
-  };
+    frameState.postRenderFunctions.push(
+        /** @type {ol.PostRenderFunction} */ (postRenderFunction)
+    );
+  }
+};
 
-}
 
-goog.provide('ol.render.webgl.LineStringReplay');
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.source.Source} source Source.
+ * @protected
+ */
+ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
+  var logo = source.getLogo();
+  if (logo !== undefined) {
+    if (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;
+    }
+  }
+};
 
-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');
 
+/**
+ * @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 = 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;
+  }
+};
 
-if (ol.ENABLE_WEBGL) {
 
-  /**
-   * @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);
+/**
+ * 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 = 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 = minZoom; z <= currentZ; ++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);
+        }
+      }
+    }
+  }
+};
 
-    /**
-     * @private
-     * @type {ol.render.webgl.linestringreplay.defaultshader.Locations}
-     */
-    this.defaultLocations_ = null;
+goog.provide('ol.renderer.canvas.Layer');
 
-    /**
-     * @private
-     * @type {Array.<Array.<?>>}
-     */
-    this.styles_ = [];
+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');
 
-    /**
-     * @private
-     * @type {Array.<number>}
-     */
-    this.styleIndices_ = [];
 
-    /**
-     * @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
-    };
+/**
+ * @constructor
+ * @abstract
+ * @extends {ol.renderer.Layer}
+ * @param {ol.layer.Layer} layer Layer.
+ */
+ol.renderer.canvas.Layer = function(layer) {
 
-  };
-  ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay);
+  ol.renderer.Layer.call(this, layer);
 
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.renderedResolution;
 
   /**
-   * Draw one segment.
    * @private
-   * @param {Array.<number>} flatCoordinates Flat coordinates.
-   * @param {number} offset Offset.
-   * @param {number} end End.
-   * @param {number} stride Stride.
+   * @type {ol.Transform}
    */
-  ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
-
-    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.
-
-          if (lineCap) {
-            numVertices = this.addVertices_([0, 0], p1, p2,
-                lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices);
+  this.transform_ = ol.transform.create();
 
-            numVertices = this.addVertices_([0, 0], p1, p2,
-                -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices);
+};
+ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);
 
-            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;
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.Extent} extent Clip extent.
+ * @protected
+ */
+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);
 
-          numVertices = this.addVertices_([0, 0], p1, p2,
-              lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices);
+  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);
+};
 
-          numVertices = this.addVertices_([0, 0], p1, p2,
-              -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices);
 
-          lastIndex = numVertices / 7 - 1;
+/**
+ * @param {ol.render.EventType} type Event type.
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.Transform=} 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 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);
+  }
+};
 
-          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);
+/**
+ * @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.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) {
+  var hasFeature = this.forEachFeatureAtCoordinate(
+      coordinate, frameState, 0, ol.functions.TRUE, this);
 
-          numVertices = this.addVertices_(p0, p1, [0, 0],
-              -lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE * (lineCap || 1), numVertices);
+  if (hasFeature) {
+    return callback.call(thisArg, this.getLayer(), null);
+  } else {
+    return undefined;
+  }
+};
 
-          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);
-
-            numVertices = this.addVertices_(p0, p1, [0, 0],
-                -lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE_CAP * lineCap, numVertices);
-
-            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;
-
-          }
-
-          break;
-        }
-      } else {
-        p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]];
-      }
-
-      // 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;
+/**
+ * @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);
+};
 
-      numVertices = this.addVertices_(p0, p1, p2,
-          sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices);
 
-      numVertices = this.addVertices_(p0, p1, p2,
-          sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_SECOND * (lineJoin || 1), numVertices);
+/**
+ * @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);
+};
 
-      numVertices = this.addVertices_(p0, p1, p2,
-          -sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_BOTTOM * (lineJoin || 1), numVertices);
 
-      if (i > offset) {
-        this.indices[numIndices++] = n;
-        this.indices[numIndices++] = lastIndex - 1;
-        this.indices[numIndices++] = lastIndex;
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.Transform=} 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);
+};
 
-        this.indices[numIndices++] = n + 2;
-        this.indices[numIndices++] = n;
-        this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1;
-      }
 
-      this.indices[numIndices++] = n;
-      this.indices[numIndices++] = n + 2;
-      this.indices[numIndices++] = n + 1;
+/**
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {number} offsetX Offset on the x-axis in view coordinates.
+ * @protected
+ * @return {!ol.Transform} Transform.
+ */
+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);
+};
 
-      lastIndex = n + 2;
-      lastSign = sign;
 
-      //Add miter
-      if (lineJoin) {
-        numVertices = this.addVertices_(p0, p1, p2,
-            sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_TOP * lineJoin, numVertices);
+/**
+ * @abstract
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @param {CanvasRenderingContext2D} context Context.
+ */
+ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {};
 
-        this.indices[numIndices++] = n + 1;
-        this.indices[numIndices++] = n + 3;
-        this.indices[numIndices++] = n;
-      }
-    }
+/**
+ * @abstract
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @return {boolean} whether composeFrame should be called.
+ */
+ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {};
 
-    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;
+goog.provide('ol.renderer.canvas.IntermediateCanvas');
 
-      numVertices = this.addVertices_(p0, p1, p2,
-          sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices);
+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');
 
-      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;
+/**
+ * @constructor
+ * @abstract
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.Layer} layer Layer.
+ */
+ol.renderer.canvas.IntermediateCanvas = function(layer) {
 
-      this.indices[numIndices++] = n + 1;
-      this.indices[numIndices++] = n;
-      this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1;
-    }
-  };
+  ol.renderer.canvas.Layer.call(this, layer);
 
   /**
-   * @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
+   * @protected
+   * @type {ol.Transform}
    */
-  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;
-
-    return numVertices;
-  };
+  this.coordinateToCanvasPixelTransform = ol.transform.create();
 
   /**
-   * 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
+   * @type {CanvasRenderingContext2D}
    */
-  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);
-    }
-
-    return true;
-  };
+  this.hitCanvasContext_ = null;
 
+};
+ol.inherits(ol.renderer.canvas.IntermediateCanvas, ol.renderer.canvas.Layer);
 
-  /**
-   * @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);
-    }
-  };
 
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.IntermediateCanvas.prototype.composeFrame = function(frameState, layerState, context) {
 
-  /**
-   * @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);
-        }
-      }
-    }
-    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;
-      }
-    }
-  };
+  this.preCompose(context, frameState);
 
+  var image = this.getImage();
+  if (image) {
 
-  /**
-   * @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);
-      }
+    // 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));
     }
-  };
 
+    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;
 
-  /**
-   * @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;
-    }
-  };
+    // 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();
+    }
+  }
 
-  /**
-   * @return {number} Current index.
-   */
-  ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() {
-    return this.indices.length;
-  };
+  this.postCompose(context, frameState, layerState);
+};
 
 
-  /**
-   * @inheritDoc
-   **/
-  ol.render.webgl.LineStringReplay.prototype.finish = function(context) {
-    // create, bind, and populate the vertices buffer
-    this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
+/**
+ * @abstract
+ * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
+ */
+ol.renderer.canvas.IntermediateCanvas.prototype.getImage = function() {};
 
-    // create, bind, and populate the indices buffer
-    this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
-    this.startIndices.push(this.indices.length);
+/**
+ * @abstract
+ * @return {!ol.Transform} Image transform.
+ */
+ol.renderer.canvas.IntermediateCanvas.prototype.getImageTransform = function() {};
 
-    //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;
-  };
+/**
+ * @inheritDoc
+ */
+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);
+      });
+};
 
 
-  /**
-   * @inheritDoc
-   */
-  ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) {
-    var verticesBuffer = this.verticesBuffer;
-    var indicesBuffer = this.indicesBuffer;
-    return function() {
-      context.deleteBuffer(verticesBuffer);
-      context.deleteBuffer(indicesBuffer);
-    };
-  };
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.IntermediateCanvas.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) {
+  if (!this.getImage()) {
+    return undefined;
+  }
 
+  if (this.getLayer().getSource().forEachFeatureAtCoordinate !== ol.nullFunction) {
+    // for ImageCanvas 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);
 
-  /**
-   * @inheritDoc
-   */
-  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_;
+    if (!this.hitCanvasContext_) {
+      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
     }
 
-    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);
+    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
+    this.hitCanvasContext_.drawImage(this.getImage(), pixel[0], pixel[1], 1, 1, 0, 0, 1, 1);
 
-    gl.enableVertexAttribArray(locations.a_position);
-    gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
-        false, 28, 8);
+    var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
+    if (imageData[3] > 0) {
+      return callback.call(thisArg, this.getLayer(),  imageData);
+    } else {
+      return undefined;
+    }
+  }
+};
 
-    gl.enableVertexAttribArray(locations.a_nextPos);
-    gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT,
-        false, 28, 16);
+goog.provide('ol.renderer.canvas.ImageLayer');
 
-    gl.enableVertexAttribArray(locations.a_direction);
-    gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT,
-        false, 28, 24);
+goog.require('ol');
+goog.require('ol.ImageCanvas');
+goog.require('ol.LayerType');
+goog.require('ol.ViewHint');
+goog.require('ol.array');
+goog.require('ol.extent');
+goog.require('ol.layer.VectorRenderType');
+goog.require('ol.obj');
+goog.require('ol.plugins');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.IntermediateCanvas');
+goog.require('ol.transform');
 
-    // Enable renderer specific uniforms.
-    gl.uniform2fv(locations.u_size, size);
-    gl.uniform1f(locations.u_pixelRatio, pixelRatio);
 
-    return locations;
-  };
+/**
+ * @constructor
+ * @extends {ol.renderer.canvas.IntermediateCanvas}
+ * @param {ol.layer.Image} imageLayer Single image layer.
+ * @api
+ */
+ol.renderer.canvas.ImageLayer = function(imageLayer) {
 
+  ol.renderer.canvas.IntermediateCanvas.call(this, imageLayer);
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {?ol.ImageBase}
    */
-  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);
-  };
-
+  this.image_ = null;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {ol.Transform}
    */
-  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);
-    }
-
-    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);
-    }
-  };
+  this.imageTransform_ = ol.transform.create();
 
+  /**
+   * @type {!Array.<string>}
+   */
+  this.skippedFeatures_ = [];
 
   /**
    * @private
-   * @param {WebGLRenderingContext} gl gl.
-   * @param {ol.webgl.Context} context Context.
-   * @param {Object} skippedFeaturesHash Ids of features to skip.
+   * @type {ol.renderer.canvas.VectorLayer}
    */
-  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();
-
-        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;
-    }
-  };
+  this.vectorRenderer_ = null;
 
+};
+ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.IntermediateCanvas);
 
-  /**
-   * @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);
+/**
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @param {ol.layer.Layer} layer The candidate layer.
+ * @return {boolean} The renderer can render the layer.
+ */
+ol.renderer.canvas.ImageLayer['handles'] = function(type, layer) {
+  return type === ol.renderer.Type.CANVAS && (layer.getType() === ol.LayerType.IMAGE ||
+      layer.getType() === ol.LayerType.VECTOR &&
+      /** @type {ol.layer.Vector} */ (layer).getRenderMode() === ol.layer.VectorRenderType.IMAGE);
+};
 
-          if (result) {
-            return result;
-          }
 
-        }
-        featureIndex--;
-        end = start;
+/**
+ * Create a layer renderer.
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @param {ol.layer.Layer} layer The layer to be rendererd.
+ * @return {ol.renderer.canvas.ImageLayer} The layer renderer.
+ */
+ol.renderer.canvas.ImageLayer['create'] = function(mapRenderer, layer) {
+  var renderer = new ol.renderer.canvas.ImageLayer(/** @type {ol.layer.Image} */ (layer));
+  if (layer.getType() === ol.LayerType.VECTOR) {
+    var candidates = ol.plugins.getLayerRendererPlugins();
+    for (var i = 0, ii = candidates.length; i < ii; ++i) {
+      var candidate = /** @type {Object.<string, Function>} */ (candidates[i]);
+      if (candidate !== ol.renderer.canvas.ImageLayer && candidate['handles'](ol.renderer.Type.CANVAS, layer)) {
+        renderer.setVectorRenderer(candidate['create'](mapRenderer, layer));
       }
     }
-    return undefined;
-  };
+  }
+  return renderer;
+};
 
 
-  /**
-   * @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);
-  };
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
+  return !this.image_ ? null : this.image_.getImage();
+};
 
 
-  /**
-   * @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]);
-    }
-  };
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() {
+  return this.imageTransform_;
+};
 
-  /**
-   * @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
-  };
 
-}
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) {
 
-// This file is automatically generated, do not edit
-/* eslint openlayers-internal/no-missing-requires: 0 */
-goog.provide('ol.render.webgl.polygonreplay.defaultshader');
+  var pixelRatio = frameState.pixelRatio;
+  var size = frameState.size;
+  var viewState = frameState.viewState;
+  var viewCenter = viewState.center;
+  var viewResolution = viewState.resolution;
 
-goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
+  var image;
+  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
+  var imageSource = imageLayer.getSource();
 
-if (ol.ENABLE_WEBGL) {
+  var hints = frameState.viewHints;
 
-  /**
-   * @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);
+  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;
+      }
+    }
+    var vectorRenderer = this.vectorRenderer_;
+    if (vectorRenderer) {
+      var context = vectorRenderer.context;
+      var imageFrameState = /** @type {olx.FrameState} */ (ol.obj.assign({}, frameState, {
+        size: [
+          ol.extent.getWidth(renderedExtent) / viewResolution,
+          ol.extent.getHeight(renderedExtent) / viewResolution
+        ],
+        viewState: /** @type {olx.ViewState} */ (ol.obj.assign({}, frameState.viewState, {
+          rotation: 0
+        }))
+      }));
+      var skippedFeatures = Object.keys(imageFrameState.skippedFeatureUids).sort();
+      if (vectorRenderer.prepareFrame(imageFrameState, layerState) &&
+          (vectorRenderer.replayGroupChanged ||
+          !ol.array.equals(skippedFeatures, this.skippedFeatures_))) {
+        context.canvas.width = imageFrameState.size[0] * pixelRatio;
+        context.canvas.height = imageFrameState.size[1] * pixelRatio;
+        vectorRenderer.composeFrame(imageFrameState, layerState, context);
+        this.image_ = new ol.ImageCanvas(renderedExtent, viewResolution, pixelRatio, context.canvas);
+        this.skippedFeatures_ = skippedFeatures;
+      }
+    } else {
+      image = imageSource.getImage(
+          renderedExtent, viewResolution, pixelRatio, projection);
+      if (image) {
+        var loaded = this.loadImage(image);
+        if (loaded) {
+          this.image_ = image;
+        }
+      }
+    }
+  }
 
-  /**
-   * @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';
+  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.updateLogos(frameState, imageSource);
+    this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio;
+  }
 
-  /**
-   * @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;}';
+  return !!this.image_;
+};
 
 
-  /**
-   * @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;
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
+  if (this.vectorRenderer_) {
+    return this.vectorRenderer_.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg);
+  } else {
+    return ol.renderer.canvas.IntermediateCanvas.prototype.forEachFeatureAtCoordinate.call(this, coordinate, frameState, hitTolerance, callback, thisArg);
+  }
+};
+
 
+/**
+ * @param {ol.renderer.canvas.VectorLayer} renderer Vector renderer.
+ */
+ol.renderer.canvas.ImageLayer.prototype.setVectorRenderer = function(renderer) {
+  this.vectorRenderer_ = renderer;
+};
 
-  ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment();
+goog.provide('ol.style.IconImageCache');
 
+goog.require('ol.color');
 
-  /**
-   * @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);
 
+/**
+ * Singleton class. Available through {@link ol.style.iconImageCache}.
+ * @constructor
+ */
+ol.style.IconImageCache = function() {
 
   /**
-   * @const
-   * @type {string}
+   * @type {Object.<string, ol.style.IconImage>}
+   * @private
    */
-  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';
-
+  this.cache_ = {};
 
   /**
-   * @const
-   * @type {string}
+   * @type {number}
+   * @private
    */
-  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);}';
-
+  this.cacheSize_ = 0;
 
   /**
-   * @const
-   * @type {string}
+   * @type {number}
+   * @private
    */
-  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;
+  this.maxCacheSize_ = 32;
+};
 
 
-  ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex();
+/**
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.Color} color Color.
+ * @return {string} Cache key.
+ */
+ol.style.IconImageCache.getKey = function(src, crossOrigin, color) {
+  var colorString = color ? ol.color.asString(color) : 'null';
+  return crossOrigin + ':' + src + ':' + colorString;
+};
 
 
-  /**
-   * @constructor
-   * @param {WebGLRenderingContext} gl GL.
-   * @param {WebGLProgram} program Program.
-   * @struct
-   */
-  ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) {
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.style.IconImageCache.prototype.clear = function() {
+  this.cache_ = {};
+  this.cacheSize_ = 0;
+};
 
-    /**
-     * @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');
+/**
+ * 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 && !iconImage.hasListener()) {
+        delete this.cache_[key];
+        --this.cacheSize_;
+      }
+    }
+  }
+};
 
-    /**
-     * @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');
+/**
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.Color} color Color.
+ * @return {ol.style.IconImage} Icon image.
+ */
+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;
+};
 
-    /**
-     * @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');
-  };
+/**
+ * @param {string} src Src.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.Color} color Color.
+ * @param {ol.style.IconImage} iconImage Icon image.
+ */
+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.Stroke');
+/**
+ * Set the cache size of the icon cache. Default is `32`. Change this value when
+ * your map uses more than 32 different icon images and you are not caching icon
+ * styles on the application level.
+ * @param {number} maxCacheSize Cache max size.
+ * @api
+ */
+ol.style.IconImageCache.prototype.setSize = function(maxCacheSize) {
+  this.maxCacheSize_ = maxCacheSize;
+  this.expire();
+};
 
-goog.require('ol');
+goog.provide('ol.style');
 
+goog.require('ol.style.IconImageCache');
 
 /**
- * @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.
+ * The {@link ol.style.IconImageCache} for {@link ol.style.Icon} images.
  * @api
  */
-ol.style.Stroke = function(opt_options) {
+ol.style.iconImageCache = new ol.style.IconImageCache();
 
-  var options = opt_options || {};
+goog.provide('ol.renderer.Map');
 
-  /**
-   * @private
-   * @type {ol.Color|ol.ColorLike}
-   */
-  this.color_ = options.color !== undefined ? options.color : null;
+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.plugins');
+goog.require('ol.style');
+goog.require('ol.transform');
 
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.lineCap_ = options.lineCap;
 
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;
+/**
+ * @constructor
+ * @abstract
+ * @extends {ol.Disposable}
+ * @param {Element} container Container.
+ * @param {ol.PluggableMap} map Map.
+ * @struct
+ */
+ol.renderer.Map = function(container, map) {
 
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lineDashOffset_ = options.lineDashOffset;
+  ol.Disposable.call(this);
 
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.lineJoin_ = options.lineJoin;
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {ol.PluggableMap}
    */
-  this.miterLimit_ = options.miterLimit;
+  this.map_ = map;
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {Object.<string, ol.renderer.Layer>}
    */
-  this.width_ = options.width;
+  this.layerRenderers_ = {};
 
   /**
    * @private
-   * @type {string|undefined}
+   * @type {Object.<string, ol.EventsKey>}
    */
-  this.checksum_ = undefined;
+  this.layerRendererListeners_ = {};
+
 };
+ol.inherits(ol.renderer.Map, ol.Disposable);
 
 
 /**
- * Clones the style.
- * @return {ol.style.Stroke} The cloned style.
- * @api
+ * @param {olx.FrameState} frameState FrameState.
+ * @protected
  */
-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()
-  });
+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));
 };
 
 
 /**
- * Get the stroke color.
- * @return {ol.Color|ol.ColorLike} Color.
- * @api
+ * Removes all layer renderers.
  */
-ol.style.Stroke.prototype.getColor = function() {
-  return this.color_;
+ol.renderer.Map.prototype.removeLayerRenderers = function() {
+  for (var key in this.layerRenderers_) {
+    this.removeLayerRendererByKey_(key).dispose();
+  }
 };
 
 
 /**
- * Get the line cap type for the stroke.
- * @return {string|undefined} Line cap.
- * @api
+ * @param {ol.PluggableMap} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-ol.style.Stroke.prototype.getLineCap = function() {
-  return this.lineCap_;
+ol.renderer.Map.expireIconCache_ = function(map, frameState) {
+  var cache = ol.style.iconImageCache;
+  cache.expire();
 };
 
 
 /**
- * Get the line dash style for the stroke.
- * @return {Array.<number>} Line dash.
- * @api
+ * @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.style.Stroke.prototype.getLineDash = function() {
-  return this.lineDash_;
+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;
 };
 
 
 /**
- * Get the line dash offset for the stroke.
- * @return {number|undefined} Line dash offset.
- * @api
+ * @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.style.Stroke.prototype.getLineDashOffset = function() {
-  return this.lineDashOffset_;
-};
+ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
+    layerFilter, thisArg2) {};
 
 
 /**
- * Get the line join type for the stroke.
- * @return {string|undefined} Line join.
- * @api
+ * @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.style.Stroke.prototype.getLineJoin = function() {
-  return this.lineJoin_;
+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;
 };
 
 
 /**
- * Get the miter limit for the stroke.
- * @return {number|undefined} Miter limit.
- * @api
+ * @param {ol.layer.Layer} layer Layer.
+ * @protected
+ * @return {ol.renderer.Layer} Layer renderer.
  */
-ol.style.Stroke.prototype.getMiterLimit = function() {
-  return this.miterLimit_;
+ol.renderer.Map.prototype.getLayerRenderer = function(layer) {
+  var layerKey = ol.getUid(layer).toString();
+  if (layerKey in this.layerRenderers_) {
+    return this.layerRenderers_[layerKey];
+  } else {
+    var layerRendererPlugins = ol.plugins.getLayerRendererPlugins();
+    var renderer;
+    var type = this.getType();
+    for (var i = 0, ii = layerRendererPlugins.length; i < ii; ++i) {
+      var plugin = layerRendererPlugins[i];
+      if (plugin['handles'](type, layer)) {
+        renderer = plugin['create'](this, layer);
+        break;
+      }
+    }
+    if (renderer) {
+      this.layerRenderers_[layerKey] = renderer;
+      this.layerRendererListeners_[layerKey] = ol.events.listen(renderer,
+          ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this);
+    } else {
+      throw new Error('Unable to create renderer for layer: ' + layer.getType());
+    }
+    return renderer;
+  }
 };
 
 
 /**
- * Get the stroke width.
- * @return {number|undefined} Width.
- * @api
+ * @param {string} layerKey Layer key.
+ * @protected
+ * @return {ol.renderer.Layer} Layer renderer.
  */
-ol.style.Stroke.prototype.getWidth = function() {
-  return this.width_;
+ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) {
+  return this.layerRenderers_[layerKey];
 };
 
 
 /**
- * Set the color.
- *
- * @param {ol.Color|ol.ColorLike} color Color.
- * @api
+ * @protected
+ * @return {Object.<string, ol.renderer.Layer>} Layer renderers.
  */
-ol.style.Stroke.prototype.setColor = function(color) {
-  this.color_ = color;
-  this.checksum_ = undefined;
+ol.renderer.Map.prototype.getLayerRenderers = function() {
+  return this.layerRenderers_;
 };
 
 
 /**
- * Set the line cap.
- *
- * @param {string|undefined} lineCap Line cap.
- * @api
+ * @return {ol.PluggableMap} Map.
  */
-ol.style.Stroke.prototype.setLineCap = function(lineCap) {
-  this.lineCap_ = lineCap;
-  this.checksum_ = undefined;
+ol.renderer.Map.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
- * Set the line dash.
- *
- * 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.
- *
- * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility
- *
- * @param {Array.<number>} lineDash Line dash.
- * @api
+ * @abstract
+ * @return {ol.renderer.Type} Type
  */
-ol.style.Stroke.prototype.setLineDash = function(lineDash) {
-  this.lineDash_ = lineDash;
-  this.checksum_ = undefined;
-};
+ol.renderer.Map.prototype.getType = function() {};
 
 
 /**
- * Set the line dash offset.
- *
- * @param {number|undefined} lineDashOffset Line dash offset.
- * @api
+ * Handle changes in a layer renderer.
+ * @private
  */
-ol.style.Stroke.prototype.setLineDashOffset = function(lineDashOffset) {
-  this.lineDashOffset_ = lineDashOffset;
-  this.checksum_ = undefined;
+ol.renderer.Map.prototype.handleLayerRendererChange_ = function() {
+  this.map_.render();
 };
 
 
 /**
- * Set the line join.
- *
- * @param {string|undefined} lineJoin Line join.
- * @api
+ * @param {string} layerKey Layer key.
+ * @return {ol.renderer.Layer} Layer renderer.
+ * @private
  */
-ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
-  this.lineJoin_ = lineJoin;
-  this.checksum_ = undefined;
+ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) {
+  var layerRenderer = this.layerRenderers_[layerKey];
+  delete this.layerRenderers_[layerKey];
+
+  ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]);
+  delete this.layerRendererListeners_[layerKey];
+
+  return layerRenderer;
 };
 
 
 /**
- * Set the miter limit.
- *
- * @param {number|undefined} miterLimit Miter limit.
- * @api
+ * Render.
+ * @param {?olx.FrameState} frameState Frame state.
  */
-ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
-  this.miterLimit_ = miterLimit;
-  this.checksum_ = undefined;
+ol.renderer.Map.prototype.renderFrame = ol.nullFunction;
+
+
+/**
+ * @param {ol.PluggableMap} 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)) {
+      this.removeLayerRendererByKey_(layerKey).dispose();
+    }
+  }
 };
 
 
 /**
- * Set the width.
- *
- * @param {number|undefined} width Width.
- * @api
+ * @param {olx.FrameState} frameState Frame state.
+ * @protected
  */
-ol.style.Stroke.prototype.setWidth = function(width) {
-  this.width_ = width;
-  this.checksum_ = undefined;
+ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
+  frameState.postRenderFunctions.push(
+      /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_)
+  );
 };
 
 
 /**
- * @return {string} The checksum.
+ * @param {!olx.FrameState} frameState Frame state.
+ * @protected
  */
-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 {
-      this.checksum_ += '-';
+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;
     }
-    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() : '-');
   }
+};
 
-  return this.checksum_;
+
+/**
+ * @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.structs.LinkedList');
+// FIXME offset panning
+
+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');
+
 
 /**
- * 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.
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.PluggableMap} map Map.
+ * @api
  */
-ol.structs.LinkedList = function(opt_circular) {
+ol.renderer.canvas.Map = function(container, map) {
+
+  ol.renderer.Map.call(this, container, map);
 
   /**
    * @private
-   * @type {ol.LinkedListItem|undefined}
+   * @type {CanvasRenderingContext2D}
    */
-  this.first_ = undefined;
+  this.context_ = ol.dom.createCanvasContext2D();
 
   /**
    * @private
-   * @type {ol.LinkedListItem|undefined}
+   * @type {HTMLCanvasElement}
    */
-  this.last_ = undefined;
+  this.canvas_ = this.context_.canvas;
 
-  /**
-   * @private
-   * @type {ol.LinkedListItem|undefined}
-   */
-  this.head_ = undefined;
+  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);
 
   /**
    * @private
    * @type {boolean}
    */
-  this.circular_ = opt_circular === undefined ? true : opt_circular;
+  this.renderedVisible_ = true;
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.Transform}
    */
-  this.length_ = 0;
+  this.transform_ = ol.transform.create();
+
 };
+ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
+
 
 /**
- * Inserts an item into the linked list right after the current one.
- *
- * @param {?} data Item data.
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @return {boolean} The renderer can render the layer.
  */
-ol.structs.LinkedList.prototype.insertItem = function(data) {
-
-  /** @type {ol.LinkedListItem} */
-  var item = {
-    prev: undefined,
-    next: undefined,
-    data: data
-  };
-
-  var head = this.head_;
+ol.renderer.canvas.Map['handles'] = function(type) {
+  return type === ol.renderer.Type.CANVAS;
+};
 
-  //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;
-    }
 
-    if (head === this.last_) {
-      this.last_ = item;
-    }
-  }
-  this.head_ = item;
-  this.length_++;
+/**
+ * Create the map renderer.
+ * @param {Element} container Container.
+ * @param {ol.PluggableMap} map Map.
+ * @return {ol.renderer.canvas.Map} The map renderer.
+ */
+ol.renderer.canvas.Map['create'] = function(container, map) {
+  return new ol.renderer.canvas.Map(container, map);
 };
 
+
 /**
- * Removes the current item from the list. Sets the cursor to the next item,
- * if possible.
+ * @param {ol.render.EventType} type Event type.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-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;
+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;
 
-    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_--;
+    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);
   }
 };
 
+
 /**
- * Sets the cursor to the first item, and returns the associated data.
- *
- * @return {?} Item data.
+ * @param {olx.FrameState} frameState Frame state.
+ * @protected
+ * @return {!ol.Transform} Transform.
  */
-ol.structs.LinkedList.prototype.firstItem = function() {
-  this.head_ = this.first_;
-  if (this.head_) {
-    return this.head_.data;
-  }
-  return undefined;
+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);
 };
 
-/**
-* 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;
-};
 
 /**
- * Sets the cursor to the next item, and returns the associated data.
- *
- * @return {?} Item data.
+ * @inheritDoc
  */
-ol.structs.LinkedList.prototype.nextItem = function() {
-  if (this.head_ && this.head_.next) {
-    this.head_ = this.head_.next;
-    return this.head_.data;
-  }
-  return undefined;
+ol.renderer.canvas.Map.prototype.getType = function() {
+  return ol.renderer.Type.CANVAS;
 };
 
+
 /**
- * Returns the next item's data without moving the cursor.
- *
- * @return {?} Item data.
+ * @inheritDoc
  */
-ol.structs.LinkedList.prototype.getNextItem = function() {
-  if (this.head_ && this.head_.next) {
-    return this.head_.next.data;
+ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
+
+  if (!frameState) {
+    if (this.renderedVisible_) {
+      this.canvas_.style.display = 'none';
+      this.renderedVisible_ = false;
+    }
+    return;
   }
-  return undefined;
-};
 
-/**
- * Sets the cursor to the previous item, and returns the associated data.
- *
- * @return {?} Item data.
- */
-ol.structs.LinkedList.prototype.prevItem = function() {
-  if (this.head_ && this.head_.prev) {
-    this.head_ = this.head_.prev;
-    return this.head_.data;
+  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);
   }
-  return undefined;
+
+  var rotation = frameState.viewState.rotation;
+
+  this.calculateMatrices2D(frameState);
+
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+
+  var layerStatesArray = frameState.layerStatesArray;
+  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
+
+  if (rotation) {
+    context.save();
+    ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
+  }
+
+  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);
+    }
+  }
+
+  if (rotation) {
+    context.restore();
+  }
+
+  this.dispatchComposeEvent_(
+      ol.render.EventType.POSTCOMPOSE, frameState);
+
+  if (!this.renderedVisible_) {
+    this.canvas_.style.display = '';
+    this.renderedVisible_ = true;
+  }
+
+  this.scheduleRemoveUnusedLayerRenderers(frameState);
+  this.scheduleExpireIconCache(frameState);
 };
 
+
 /**
- * Returns the previous item's data without moving the cursor.
- *
- * @return {?} Item data.
+ * @inheritDoc
  */
-ol.structs.LinkedList.prototype.getPrevItem = function() {
-  if (this.head_ && this.head_.prev) {
-    return this.head_.prev.data;
+ol.renderer.canvas.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 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;
 };
 
+goog.provide('ol.renderer.canvas.TileLayer');
+
+goog.require('ol');
+goog.require('ol.LayerType');
+goog.require('ol.TileRange');
+goog.require('ol.TileState');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.IntermediateCanvas');
+goog.require('ol.transform');
+
+
 /**
- * Returns the current item's data.
- *
- * @return {?} Item data.
+ * @constructor
+ * @extends {ol.renderer.canvas.IntermediateCanvas}
+ * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer.
+ * @api
  */
-ol.structs.LinkedList.prototype.getCurrItem = function() {
-  if (this.head_) {
-    return this.head_.data;
-  }
-  return undefined;
+ol.renderer.canvas.TileLayer = function(tileLayer) {
+
+  ol.renderer.canvas.IntermediateCanvas.call(this, tileLayer);
+
+  /**
+   * @protected
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context = this.context === null ? null :  ol.dom.createCanvasContext2D();
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.oversampling_;
+
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.renderedExtent_ = null;
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.renderedRevision;
+
+  /**
+   * @protected
+   * @type {!Array.<ol.Tile>}
+   */
+  this.renderedTiles = [];
+
+  /**
+   * @protected
+   * @type {ol.Extent}
+   */
+  this.tmpExtent = ol.extent.createEmpty();
+
+  /**
+   * @private
+   * @type {ol.TileRange}
+   */
+  this.tmpTileRange_ = new ol.TileRange(0, 0, 0, 0);
+
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.imageTransform_ = ol.transform.create();
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.zDirection = 0;
+
 };
+ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.IntermediateCanvas);
+
 
 /**
- * Sets the first item of the list. This only works for circular lists, and sets
- * the last item accordingly.
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @param {ol.layer.Layer} layer The candidate layer.
+ * @return {boolean} The renderer can render the layer.
  */
-ol.structs.LinkedList.prototype.setFirstItem = function() {
-  if (this.circular_ && this.head_) {
-    this.first_ = this.head_;
-    this.last_ = this.head_.prev;
-  }
+ol.renderer.canvas.TileLayer['handles'] = function(type, layer) {
+  return type === ol.renderer.Type.CANVAS && layer.getType() === ol.LayerType.TILE;
 };
 
+
 /**
- * Concatenates two lists.
- * @param {ol.structs.LinkedList} list List to merge into the current list.
+ * Create a layer renderer.
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @param {ol.layer.Layer} layer The layer to be rendererd.
+ * @return {ol.renderer.canvas.TileLayer} The layer renderer.
  */
-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 {
-      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;
-  }
+ol.renderer.canvas.TileLayer['create'] = function(mapRenderer, layer) {
+  return new ol.renderer.canvas.TileLayer(/** @type {ol.layer.Tile} */ (layer));
 };
 
+
 /**
- * Returns the current length of the list.
- *
- * @return {number} Length.
+ * @private
+ * @param {ol.Tile} tile Tile.
+ * @return {boolean} Tile is drawable.
  */
-ol.structs.LinkedList.prototype.getLength = function() {
-  return this.length_;
+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;
 };
 
-
 /**
- * @fileoverview
- * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, unusedLocalVariables, uselessCode, visibility}
+ * @inheritDoc
  */
-goog.provide('ol.ext.rbush');
+ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(frameState, layerState) {
 
-/** @typedef {function(*)} */
-ol.ext.rbush = function() {};
+  var pixelRatio = frameState.pixelRatio;
+  var size = frameState.size;
+  var viewState = frameState.viewState;
+  var projection = viewState.projection;
+  var viewResolution = viewState.resolution;
+  var viewCenter = viewState.center;
 
-(function() {(function (exports) {
-'use strict';
+  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;
 
-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);
+  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.getTileRangeForExtentAndZ(extent, z);
+  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);
+      if (tile.getState() == ol.TileState.ERROR) {
+        if (!tileLayer.getUseInterimTilesOnError()) {
+          // When useInterimTilesOnError is false, we consider the error tile as loaded.
+          tile.setState(ol.TileState.LOADED);
+        } else if (tileLayer.getPreload() > 0) {
+          // Preloaded tiles for lower resolutions might have finished loading.
+          newTiles = true;
         }
-        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 (!this.isDrawableTile_(tile)) {
+        tile = tile.getInterimTile();
+      }
+      if (this.isDrawableTile_(tile)) {
+        var uid = ol.getUid(this);
+        if (tile.getState() == ol.TileState.LOADED) {
+          tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
+          var inTransition = tile.inTransition(uid);
+          if (!newTiles && (inTransition || this.renderedTiles.indexOf(tile) === -1)) {
+            newTiles = true;
+          }
         }
-        if (compare(arr[left], t) === 0) swap(arr, left, j);
-        else {
-            j++;
-            swap(arr, j, right);
+        if (tile.getAlpha(uid, frameState.time) === 1) {
+          // don't look for alt tiles if alpha is 1
+          continue;
         }
-        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;
-}
-function defaultCompare(a, b) {
-    return a < b ? -1 : a > b ? 1 : 0;
-}
+      }
+
+      var childTileRange = tileGrid.getTileCoordChildTileRange(
+          tile.tileCoord, tmpTileRange, tmpExtent);
+      var covered = false;
+      if (childTileRange) {
+        covered = findLoadedTiles(z + 1, childTileRange);
+      }
+      if (!covered) {
+        tileGrid.forEachTileCoordParentTileRange(
+            tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
+      }
 
-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);
     }
-    this.clear();
-}
-rbush.prototype = {
-    all: function () {
-        return this._all(this.data, []);
-    },
-    search: function (bbox) {
-        var node = this.data,
-            result = [],
-            toBBox = this.toBBox;
+  }
+
+  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 {
+        if (this.renderedExtent_ && !ol.extent.equals(imageExtent, this.renderedExtent_)) {
+          context.clearRect(0, 0, width, height);
+        }
+        oversampling = this.oversampling_;
+      }
+    }
+
+    this.renderedTiles.length = 0;
+    /** @type {Array.<number>} */
+    var zs = Object.keys(tilesToDrawByZ).map(Number);
+    zs.sort(function(a, b) {
+      if (a === z) {
+        return 1;
+      } else if (b === z) {
+        return -1;
+      } else {
+        return a > b ? 1 : a < b ? -1 : 0;
+      }
+    });
+    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, z === currentZ);
+        this.renderedTiles.push(tile);
+      }
+    }
+
+    this.renderedRevision = sourceRevision;
+    this.renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling;
+    this.renderedExtent_ = imageExtent;
+  }
+
+  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;
+};
+
+
+/**
+ * @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.
+ * @param {boolean} transition Apply an alpha transition.
+ */
+ol.renderer.canvas.TileLayer.prototype.drawTileImage = function(tile, frameState, layerState, x, y, w, h, gutter, transition) {
+  var image = tile.getImage(this.getLayer());
+  if (!image) {
+    return;
+  }
+  var uid = ol.getUid(this);
+  var alpha = transition ? tile.getAlpha(uid, frameState.time) : 1;
+  if (alpha === 1 && !this.getLayer().getSource().getOpaque(frameState.viewState.projection)) {
+    this.context.clearRect(x, y, w, h);
+  }
+  var alphaChanged = alpha !== this.context.globalAlpha;
+  if (alphaChanged) {
+    this.context.save();
+    this.context.globalAlpha = alpha;
+  }
+  this.context.drawImage(image, gutter, gutter,
+      image.width - 2 * gutter, image.height - 2 * gutter, x, y, w, h);
+
+  if (alphaChanged) {
+    this.context.restore();
+  }
+  if (alpha !== 1) {
+    frameState.animate = true;
+  } else if (transition) {
+    tile.endTransition(uid);
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.getImage = function() {
+  var context = this.context;
+  return context ? context.canvas : null;
+};
+
+
+/**
+ * @function
+ * @return {ol.layer.Tile|ol.layer.VectorTile}
+ */
+ol.renderer.canvas.TileLayer.prototype.getLayer;
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() {
+  return this.imageTransform_;
+};
+
+
+/**
+ * @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.provide('ol.ext.rbush');
+
+/** @typedef {function(*)} */
+ol.ext.rbush = function() {};
+
+(function() {(function (exports) {
+'use strict';
+
+var quickselect_1 = quickselect;
+var default_1 = quickselect;
+function quickselect(arr, k, left, right, compare) {
+    quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
+}
+function quickselectStep(arr, k, left, right, compare) {
+    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));
+            quickselectStep(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;
+    }
+}
+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;
+}
+quickselect_1.default = default_1;
+
+var rbush_1 = 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);
+    }
+    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;
@@ -27893,4880 +27324,4932 @@ function multiSelect(arr, left, right, n, compare) {
         left = stack.pop();
         if (right - left <= n) continue;
         mid = left + Math.ceil((right - left) / n / 2) * n;
-        index$2(arr, mid, left, right, compare);
+        quickselect_1(arr, mid, left, right, compare);
         stack.push(left, mid, mid, right);
     }
 }
 
-exports['default'] = index;
+exports['default'] = rbush_1;
 
 }((this.rbush = this.rbush || {})));}).call(ol.ext);
 ol.ext.rbush = ol.ext.rbush.default;
 
-goog.provide('ol.structs.RBush');
-
-goog.require('ol');
-goog.require('ol.ext.rbush');
-goog.require('ol.extent');
-goog.require('ol.obj');
+goog.provide('ol.render.ReplayGroup');
 
 
 /**
- * Wrapper around the RBush by Vladimir Agafonkin.
- *
+ * Base class for replay groups.
  * @constructor
- * @param {number=} opt_maxEntries Max entries.
- * @see https://github.com/mourner/rbush
- * @struct
- * @template T
+ * @abstract
  */
-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, ol.RBushEntry>}
-   */
-  this.items_ = {};
-
-};
+ol.render.ReplayGroup = function() {};
 
 
 /**
- * Insert a value into the RBush.
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
+ * @abstract
+ * @param {number|undefined} zIndex Z index.
+ * @param {ol.render.ReplayType} replayType Replay type.
+ * @return {ol.render.VectorContext} Replay.
  */
-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;
-};
+ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {};
 
 
 /**
- * Bulk-insert values into the RBush.
- * @param {Array.<ol.Extent>} extents Extents.
- * @param {Array.<T>} values Values.
+ * @abstract
+ * @return {boolean} Is empty.
  */
-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];
+ol.render.ReplayGroup.prototype.isEmpty = function() {};
 
-    /** @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);
-};
+goog.provide('ol.render.ReplayType');
 
 
 /**
- * Remove a value from the RBush.
- * @param {T} value Value.
- * @return {boolean} Removed.
+ * @enum {string}
  */
-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;
+ol.render.ReplayType = {
+  CIRCLE: 'Circle',
+  DEFAULT: 'Default',
+  IMAGE: 'Image',
+  LINE_STRING: 'LineString',
+  POLYGON: 'Polygon',
+  TEXT: 'Text'
 };
 
+goog.provide('ol.geom.flat.length');
+
 
 /**
- * Update the extent of a value in the RBush.
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Length.
  */
-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);
+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;
 };
 
 
 /**
- * Return all values in the RBush.
- * @return {Array.<T>} All.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} Perimeter.
  */
-ol.structs.RBush.prototype.getAll = function() {
-  var items = this.rbush_.all();
-  return items.map(function(item) {
-    return item.value;
-  });
+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.flat.textpath');
 
-/**
- * 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) {
-  /** @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;
-  });
-};
+goog.require('ol.math');
 
 
 /**
- * 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
+ * @param {Array.<number>} flatCoordinates Path to put text on.
+ * @param {number} offset Start offset of the `flatCoordinates`.
+ * @param {number} end End offset of the `flatCoordinates`.
+ * @param {number} stride Stride.
+ * @param {string} text Text to place on the path.
+ * @param {function(string):number} measure Measure function returning the
+ * width of the character passed as 1st argument.
+ * @param {number} startM m along the path where the text starts.
+ * @param {number} maxAngle Max angle between adjacent chars in radians.
+ * @return {Array.<Array.<*>>} The result array of null if `maxAngle` was
+ * exceeded. Entries of the array are x, y, anchorX, angle, chunk.
  */
-ol.structs.RBush.prototype.forEach = function(callback, opt_this) {
-  return this.forEach_(this.getAll(), callback, opt_this);
-};
+ol.geom.flat.textpath.lineString = function(
+    flatCoordinates, offset, end, stride, text, measure, startM, maxAngle) {
+  var result = [];
 
+  // Keep text upright
+  var reverse = flatCoordinates[offset] > flatCoordinates[end - stride];
 
-/**
- * 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) {
-  return this.forEach_(this.getInExtent(extent), callback, opt_this);
-};
-
+  var numChars = text.length;
 
-/**
- * @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;
+  var x1 = flatCoordinates[offset];
+  var y1 = flatCoordinates[offset + 1];
+  offset += stride;
+  var x2 = flatCoordinates[offset];
+  var y2 = flatCoordinates[offset + 1];
+  var segmentM = 0;
+  var segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
+
+  var chunk = '';
+  var chunkLength = 0;
+  var data, index, previousAngle;
+  for (var i = 0; i < numChars; ++i) {
+    index = reverse ? numChars - i - 1 : i;
+    var char = text.charAt(index);
+    chunk = reverse ? char + chunk : chunk + char;
+    var charLength = measure(chunk) - chunkLength;
+    chunkLength += charLength;
+    var charM = startM + charLength / 2;
+    while (offset < end - stride && segmentM + segmentLength < charM) {
+      x1 = x2;
+      y1 = y2;
+      offset += stride;
+      x2 = flatCoordinates[offset];
+      y2 = flatCoordinates[offset + 1];
+      segmentM += segmentLength;
+      segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
+    }
+    var segmentPos = charM - segmentM;
+    var angle = Math.atan2(y2 - y1, x2 - x1);
+    if (reverse) {
+      angle += angle > 0 ? -Math.PI : Math.PI;
+    }
+    if (previousAngle !== undefined) {
+      var delta = angle - previousAngle;
+      delta += (delta > Math.PI) ? -2 * Math.PI : (delta < -Math.PI) ? 2 * Math.PI : 0;
+      if (Math.abs(delta) > maxAngle) {
+        return null;
+      }
+    }
+    var interpolate = segmentPos / segmentLength;
+    var x = ol.math.lerp(x1, x2, interpolate);
+    var y = ol.math.lerp(y1, y2, interpolate);
+    if (previousAngle == angle) {
+      if (reverse) {
+        data[0] = x;
+        data[1] = y;
+        data[2] = charLength / 2;
+      }
+      data[4] = chunk;
+    } else {
+      chunk = char;
+      chunkLength = charLength;
+      data = [x, y, charLength / 2, angle, chunk];
+      if (reverse) {
+        result.unshift(data);
+      } else {
+        result.push(data);
+      }
+      previousAngle = angle;
     }
+    startM += charLength;
   }
   return result;
 };
 
+goog.provide('ol.render.canvas.Instruction');
 
 /**
- * @return {boolean} Is empty.
+ * @enum {number}
  */
-ol.structs.RBush.prototype.isEmpty = function() {
-  return ol.obj.isEmpty(this.items_);
+ol.render.canvas.Instruction = {
+  BEGIN_GEOMETRY: 0,
+  BEGIN_PATH: 1,
+  CIRCLE: 2,
+  CLOSE_PATH: 3,
+  CUSTOM: 4,
+  DRAW_CHARS: 5,
+  DRAW_IMAGE: 6,
+  END_GEOMETRY: 7,
+  FILL: 8,
+  MOVE_TO_LINE_TO: 9,
+  SET_FILL_STYLE: 10,
+  SET_STROKE_STYLE: 11,
+  STROKE: 12
 };
 
+goog.provide('ol.render.replay');
 
-/**
- * Remove all values from the RBush.
- */
-ol.structs.RBush.prototype.clear = function() {
-  this.rbush_.clear();
-  this.items_ = {};
-};
+goog.require('ol.render.ReplayType');
 
 
 /**
- * @param {ol.Extent=} opt_extent Extent.
- * @return {!ol.Extent} Extent.
+ * @const
+ * @type {Array.<ol.render.ReplayType>}
  */
-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);
-};
-
+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,
+  ol.render.ReplayType.DEFAULT
+];
 
 /**
- * @param {ol.structs.RBush} rbush R-Tree.
+ * @const
+ * @enum {number}
  */
-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];
-  }
-};
+ol.render.replay.TEXT_ALIGN = {};
+ol.render.replay.TEXT_ALIGN['left'] = 0;
+ol.render.replay.TEXT_ALIGN['end'] = 0;
+ol.render.replay.TEXT_ALIGN['center'] = 0.5;
+ol.render.replay.TEXT_ALIGN['right'] = 1;
+ol.render.replay.TEXT_ALIGN['start'] = 1;
+ol.render.replay.TEXT_ALIGN['top'] = 0;
+ol.render.replay.TEXT_ALIGN['middle'] = 0.5;
+ol.render.replay.TEXT_ALIGN['hanging'] = 0.2;
+ol.render.replay.TEXT_ALIGN['alphabetic'] = 0.8;
+ol.render.replay.TEXT_ALIGN['ideographic'] = 0.8;
+ol.render.replay.TEXT_ALIGN['bottom'] = 1;
 
-goog.provide('ol.render.webgl.PolygonReplay');
+goog.provide('ol.render.canvas.Replay');
 
 goog.require('ol');
 goog.require('ol.array');
-goog.require('ol.color');
+goog.require('ol.colorlike');
 goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.geom.flat.contains');
-goog.require('ol.geom.flat.orient');
+goog.require('ol.extent.Relationship');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.flat.inflate');
+goog.require('ol.geom.flat.length');
+goog.require('ol.geom.flat.textpath');
 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');
+goog.require('ol.has');
+goog.require('ol.obj');
+goog.require('ol.render.VectorContext');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.replay');
+goog.require('ol.transform');
 
 
-if (ol.ENABLE_WEBGL) {
+/**
+ * @constructor
+ * @extends {ol.render.VectorContext}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @param {?} declutterTree Declutter tree.
+ * @struct
+ */
+ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
+  ol.render.VectorContext.call(this);
 
   /**
-   * @constructor
-   * @extends {ol.render.webgl.Replay}
-   * @param {number} tolerance Tolerance.
-   * @param {ol.Extent} maxExtent Max extent.
-   * @struct
+   * @type {?}
    */
-  ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) {
-    ol.render.webgl.Replay.call(this, tolerance, maxExtent);
+  this.declutterTree = declutterTree;
 
-    this.lineStringReplay = new ol.render.webgl.LineStringReplay(
-        tolerance, maxExtent);
-
-    /**
-     * @private
-     * @type {ol.render.webgl.polygonreplay.defaultshader.Locations}
-     */
-    this.defaultLocations_ = null;
-
-    /**
-     * @private
-     * @type {Array.<Array.<number>>}
-     */
-    this.styles_ = [];
-
-    /**
-     * @private
-     * @type {Array.<number>}
-     */
-    this.styleIndices_ = [];
-
-    /**
-     * @private
-     * @type {{fillColor: (Array.<number>|null),
-     *         changed: boolean}|null}
-     */
-    this.state_ = {
-      fillColor: null,
-      changed: false
-    };
-
-  };
-  ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay);
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.tmpExtent_ = ol.extent.createEmpty();
 
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.tolerance = tolerance;
 
   /**
-   * Draw one polygon.
-   * @param {Array.<number>} flatCoordinates Flat coordinates.
-   * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates.
-   * @param {number} stride Stride.
-   * @private
+   * @protected
+   * @const
+   * @type {ol.Extent}
    */
-  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);
-
-    // 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);
-  };
-
+  this.maxExtent = maxExtent;
 
   /**
-   * 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 {
-      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);
-
-    return [maxXX, maxXY];
-  };
-
+   * @protected
+   * @type {boolean}
+   */
+  this.overlaps = overlaps;
 
   /**
-   * 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.
+   * @protected
+   * @type {number}
    */
-  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;
-  };
-
+  this.pixelRatio = pixelRatio;
 
   /**
-   * @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 (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;
-          }
-        }
-      }
-    }
-
-    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};
-
-    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;
-  };
-
+   * @protected
+   * @type {number}
+   */
+  this.maxLineWidth = 0;
 
   /**
-   * @private
-   * @param {ol.structs.LinkedList} list Linked list of the polygon.
-   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @protected
+   * @const
+   * @type {number}
    */
-  ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) {
-    var ccw = false;
-    var simple = this.isSimple_(list, rtree);
-
-    // 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);
-              }
-            }
-          }
-        }
-      }
-    }
-    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;
-    }
-  };
-
+  this.resolution = resolution;
 
   /**
    * @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.
+   * @type {ol.Coordinate}
    */
-  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;
-  };
-
+  this.fillOrigin_;
 
   /**
    * @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;
-
-    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;
-
-        resolvedIntersections = true;
-        if (breakCond) {
-          break;
-        }
-      }
-
-      s0 = list.getPrevItem();
-      s1 = list.nextItem();
-    } while (s0 !== start);
-    return resolvedIntersections;
-  };
-
+   * @type {Array.<*>}
+   */
+  this.beginGeometryInstruction1_ = null;
 
   /**
    * @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.
+   * @type {Array.<*>}
    */
-  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;
-  };
-
+  this.beginGeometryInstruction2_ = null;
 
   /**
    * @private
-   * @param {ol.structs.LinkedList} list Linked list of the polygon.
-   * @return {boolean} Orientation is clockwise.
+   * @type {ol.Extent}
    */
-  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);
-  };
-
+  this.bufferedMaxExtent_ = null;
 
   /**
-   * @private
-   * @param {ol.structs.LinkedList} list Linked list of the polygon.
-   * @param {ol.structs.RBush} rtree R-Tree of the polygon.
+   * @protected
+   * @type {Array.<*>}
    */
-  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);
-  };
-
+  this.instructions = [];
 
   /**
-   * @private
-   * @param {number} x X coordinate.
-   * @param {number} y Y coordinate.
-   * @param {number} i Index.
-   * @return {ol.WebglPolygonVertex} List item.
+   * @protected
+   * @type {Array.<number>}
    */
-  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;
-  };
-
+  this.coordinates = [];
 
   /**
    * @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.
+   * @type {Object.<number,ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>>}
    */
-  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;
-  };
-
-
-   /**
-    * @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);
-    }
-  };
-
+  this.coordinateCache_ = {};
 
   /**
    * @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.
+   * @type {!ol.Transform}
    */
-  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;
-  };
-
+  this.renderedTransform_ = ol.transform.create();
 
   /**
-   * @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.
+   * @protected
+   * @type {Array.<*>}
    */
-  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;
-  };
-
+  this.hitDetectionInstructions = [];
 
   /**
-   * 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;
-  };
+   * @type {Array.<number>}
+   */
+  this.pixelCoordinates_ = null;
 
+  /**
+   * @protected
+   * @type {ol.CanvasFillStrokeState}
+   */
+  this.state = /** @type {ol.CanvasFillStrokeState} */ ({});
 
   /**
    * @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.
+   * @type {number}
    */
-  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;
-  };
-
+  this.viewRotation_ = 0;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {!ol.Transform}
    */
-  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);
-    }
-  };
-
+  this.tmpLocalTransform_ = ol.transform.create();
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {!ol.Transform}
    */
-  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);
-
-        this.lineStringReplay.drawPolygonCoordinates(outerRing, holes, stride);
-        this.drawCoordinates_(outerRing, holes, stride);
-      }
-    }
-  };
+  this.resetTransform_ = ol.transform.create();
+};
+ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);
 
 
-  /**
-   * @inheritDoc
-   **/
-  ol.render.webgl.PolygonReplay.prototype.finish = function(context) {
-    // create, bind, and populate the vertices buffer
-    this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {ol.Coordinate} p1 1st point of the background box.
+ * @param {ol.Coordinate} p2 2nd point of the background box.
+ * @param {ol.Coordinate} p3 3rd point of the background box.
+ * @param {ol.Coordinate} p4 4th point of the background box.
+ * @param {Array.<*>} fillInstruction Fill instruction.
+ * @param {Array.<*>} strokeInstruction Stroke instruction.
+ */
+ol.render.canvas.Replay.prototype.replayTextBackground_ = function(context, p1, p2, p3, p4,
+    fillInstruction, strokeInstruction) {
+  context.beginPath();
+  context.moveTo.apply(context, p1);
+  context.lineTo.apply(context, p2);
+  context.lineTo.apply(context, p3);
+  context.lineTo.apply(context, p4);
+  context.lineTo.apply(context, p1);
+  if (fillInstruction) {
+    this.fillOrigin_ = /** @type {Array.<number>} */ (fillInstruction[2]);
+    this.fill_(context);
+  }
+  if (strokeInstruction) {
+    this.setStrokeStyle_(context, /** @type {Array.<*>} */ (strokeInstruction));
+    context.stroke();
+  }
+};
 
-    // create, bind, and populate the indices buffer
-    this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
-    this.startIndices.push(this.indices.length);
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} x X.
+ * @param {number} y Y.
+ * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
+ * @param {number} anchorX Anchor X.
+ * @param {number} anchorY Anchor Y.
+ * @param {ol.DeclutterGroup} declutterGroup Declutter group.
+ * @param {number} height Height.
+ * @param {number} opacity Opacity.
+ * @param {number} originX Origin X.
+ * @param {number} originY Origin Y.
+ * @param {number} rotation Rotation.
+ * @param {number} scale Scale.
+ * @param {boolean} snapToPixel Snap to pixel.
+ * @param {number} width Width.
+ * @param {Array.<number>} padding Padding.
+ * @param {Array.<*>} fillInstruction Fill instruction.
+ * @param {Array.<*>} strokeInstruction Stroke instruction.
+ */
+ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image,
+    anchorX, anchorY, declutterGroup, height, opacity, originX, originY,
+    rotation, scale, snapToPixel, width, padding, fillInstruction, strokeInstruction) {
+  var fillStroke = fillInstruction || strokeInstruction;
+  var localTransform = this.tmpLocalTransform_;
+  anchorX *= scale;
+  anchorY *= scale;
+  x -= anchorX;
+  y -= anchorY;
+  if (snapToPixel) {
+    x = Math.round(x);
+    y = Math.round(y);
+  }
+
+  var w = (width + originX > image.width) ? image.width - originX : width;
+  var h = (height + originY > image.height) ? image.height - originY : height;
+  var box = this.tmpExtent_;
+  var boxW = padding[3] + w * scale + padding[1];
+  var boxH = padding[0] + h * scale + padding[2];
+  var boxX = x - padding[3];
+  var boxY = y - padding[0];
 
-    this.lineStringReplay.finish(context);
+  /** @type {ol.Coordinate} */
+  var p1;
+  /** @type {ol.Coordinate} */
+  var p2;
+  /** @type {ol.Coordinate} */
+  var p3;
+  /** @type {ol.Coordinate} */
+  var p4;
+  if (fillStroke || rotation !== 0) {
+    p1 = [boxX, boxY];
+    p2 = [boxX + boxW, boxY];
+    p3 = [boxX + boxW, boxY + boxH];
+    p4 = [boxX, boxY + boxH];
+  }
 
-    //Clean up, if there is nothing to draw
-    if (this.styleIndices_.length === 0 && this.styles_.length > 0) {
-      this.styles_ = [];
+  var transform = null;
+  if (rotation !== 0) {
+    var centerX = x + anchorX;
+    var centerY = y + anchorY;
+    transform = ol.transform.compose(localTransform,
+        centerX, centerY, 1, 1, rotation, -centerX, -centerY);
+
+    ol.extent.createOrUpdateEmpty(box);
+    ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p1));
+    ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p2));
+    ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p3));
+    ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p4));
+  } else {
+    ol.extent.createOrUpdate(boxX, boxY, boxX + boxW, boxY + boxH, box);
+  }
+  var canvas = context.canvas;
+  var intersects = box[0] <= canvas.width && box[2] >= 0 && box[1] <= canvas.height && box[3] >= 0;
+  if (declutterGroup) {
+    if (!intersects && declutterGroup[4] == 1) {
+      return;
     }
+    ol.extent.extend(declutterGroup, box);
+    var declutterArgs = intersects ?
+      [context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] :
+      null;
+    if (declutterArgs && fillStroke) {
+      declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4);
+    }
+    declutterGroup.push(declutterArgs);
+  } else if (intersects) {
+    if (fillStroke) {
+      this.replayTextBackground_(context, p1, p2, p3, p4,
+          /** @type {Array.<*>} */ (fillInstruction),
+          /** @type {Array.<*>} */ (strokeInstruction));
+    }
+    ol.render.canvas.drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale);
+  }
+};
 
-    this.vertices = null;
-    this.indices = null;
-  };
+
+/**
+ * @protected
+ * @param {Array.<number>} dashArray Dash array.
+ * @return {Array.<number>} Dash array with pixel ratio applied
+ */
+ol.render.canvas.Replay.prototype.applyPixelRatio = function(dashArray) {
+  var pixelRatio = this.pixelRatio;
+  return pixelRatio == 1 ? dashArray : dashArray.map(function(dash) {
+    return dash * pixelRatio;
+  });
+};
 
 
-  /**
-   * @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();
-    };
-  };
+/**
+ * @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.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;
 
-  /**
-   * @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;
+  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 {
-      locations = this.defaultLocations_;
+      skipped = true;
     }
+    lastCoord[0] = nextCoord[0];
+    lastCoord[1] = nextCoord[1];
+    lastRel = nextRel;
+  }
 
-    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);
-
-    return locations;
-  };
-
+  // 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;
+};
 
-  /**
-   * @inheritDoc
-   */
-  ol.render.webgl.PolygonReplay.prototype.shutDownProgram = function(gl, locations) {
-    gl.disableVertexAttribArray(locations.a_position);
-  };
 
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {Array.<number>} ends Ends.
+ * @param {number} stride Stride.
+ * @param {Array.<number>} replayEnds Replay ends.
+ * @return {number} Offset.
+ */
+ol.render.canvas.Replay.prototype.drawCustomCoordinates_ = function(flatCoordinates, offset, ends, stride, replayEnds) {
+  for (var i = 0, ii = ends.length; i < ii; ++i) {
+    var end = ends[i];
+    var replayEnd = this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false);
+    replayEnds.push(replayEnd);
+    offset = end;
+  }
+  return offset;
+};
 
-  /**
-   * @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);
-    }
+/**
+ * @inheritDoc.
+ */
+ol.render.canvas.Replay.prototype.drawCustom = function(geometry, feature, renderer) {
+  this.beginGeometry(geometry, feature);
+  var type = geometry.getType();
+  var stride = geometry.getStride();
+  var replayBegin = this.coordinates.length;
+  var flatCoordinates, replayEnd, replayEnds, replayEndss;
+  var offset;
+  if (type == ol.geom.GeometryType.MULTI_POLYGON) {
+    geometry = /** @type {ol.geom.MultiPolygon} */ (geometry);
+    flatCoordinates = geometry.getOrientedFlatCoordinates();
+    replayEndss = [];
+    var endss = geometry.getEndss();
+    offset = 0;
+    for (var i = 0, ii = endss.length; i < ii; ++i) {
+      var myEnds = [];
+      offset = this.drawCustomCoordinates_(flatCoordinates, offset, endss[i], stride, myEnds);
+      replayEndss.push(myEnds);
+    }
+    this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
+      replayBegin, replayEndss, geometry, renderer, ol.geom.flat.inflate.coordinatesss]);
+  } else if (type == ol.geom.GeometryType.POLYGON || type == ol.geom.GeometryType.MULTI_LINE_STRING) {
+    replayEnds = [];
+    flatCoordinates = (type == ol.geom.GeometryType.POLYGON) ?
+      /** @type {ol.geom.Polygon} */ (geometry).getOrientedFlatCoordinates() :
+      geometry.getFlatCoordinates();
+    offset = this.drawCustomCoordinates_(flatCoordinates, 0,
+        /** @type {ol.geom.Polygon|ol.geom.MultiLineString} */ (geometry).getEnds(),
+        stride, replayEnds);
+    this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
+      replayBegin, replayEnds, geometry, renderer, ol.geom.flat.inflate.coordinatess]);
+  } else if (type == ol.geom.GeometryType.LINE_STRING || type == ol.geom.GeometryType.MULTI_POINT) {
+    flatCoordinates = geometry.getFlatCoordinates();
+    replayEnd = this.appendFlatCoordinates(
+        flatCoordinates, 0, flatCoordinates.length, stride, false, false);
+    this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
+      replayBegin, replayEnd, geometry, renderer, ol.geom.flat.inflate.coordinates]);
+  } else if (type == ol.geom.GeometryType.POINT) {
+    flatCoordinates = geometry.getFlatCoordinates();
+    this.coordinates.push(flatCoordinates[0], flatCoordinates[1]);
+    replayEnd = this.coordinates.length;
+    this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
+      replayBegin, replayEnd, geometry, renderer]);
+  }
+  this.endGeometry(geometry, feature);
+};
 
-    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);
-    }
-  };
 
+/**
+ * @protected
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) {
+  this.beginGeometryInstruction1_ =
+      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
+  this.instructions.push(this.beginGeometryInstruction1_);
+  this.beginGeometryInstruction2_ =
+      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
+  this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
+};
 
-  /**
-   * @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);
+/**
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ */
+ol.render.canvas.Replay.prototype.fill_ = function(context) {
+  if (this.fillOrigin_) {
+    var origin = ol.transform.apply(this.renderedTransform_, this.fillOrigin_.slice());
+    context.translate(origin[0], origin[1]);
+    context.rotate(this.viewRotation_);
+  }
+  context.fill();
+  if (this.fillOrigin_) {
+    context.setTransform.apply(context, ol.render.canvas.resetTransform_);
+  }
+};
 
-          if (result) {
-            return result;
-          }
 
-        }
-        featureIndex--;
-        end = start;
-      }
-    }
-    return undefined;
-  };
+/**
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {Array.<*>} instruction Instruction.
+ */
+ol.render.canvas.Replay.prototype.setStrokeStyle_ = function(context, instruction) {
+  context.strokeStyle = /** @type {ol.ColorLike} */ (instruction[1]);
+  context.lineWidth = /** @type {number} */ (instruction[2]);
+  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.lineDashOffset = /** @type {number} */ (instruction[7]);
+    context.setLineDash(/** @type {Array.<number>} */ (instruction[6]));
+  }
+};
 
 
-  /**
-   * @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];
-
-      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);
+/**
+ * @param {ol.DeclutterGroup} declutterGroup Declutter group.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.canvas.Replay.prototype.renderDeclutter_ = function(declutterGroup, feature) {
+  if (declutterGroup && declutterGroup.length > 5) {
+    var groupCount = declutterGroup[4];
+    if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
+      /** @type {ol.RBushEntry} */
+      var box = {
+        minX: /** @type {number} */ (declutterGroup[0]),
+        minY: /** @type {number} */ (declutterGroup[1]),
+        maxX: /** @type {number} */ (declutterGroup[2]),
+        maxY: /** @type {number} */ (declutterGroup[3]),
+        value: feature
+      };
+      if (!this.declutterTree.collides(box)) {
+        this.declutterTree.insert(box);
+        var drawImage = ol.render.canvas.drawImage;
+        for (var j = 5, jj = declutterGroup.length; j < jj; ++j) {
+          var declutterData = /** @type {Array} */ (declutterGroup[j]);
+          if (declutterData) {
+            if (declutterData.length > 11) {
+              this.replayTextBackground_(declutterData[0],
+                  declutterData[13], declutterData[14], declutterData[15], declutterData[16],
+                  declutterData[11], declutterData[12]);
+            }
+            drawImage.apply(undefined, declutterData);
           }
-          end = featureStart;
         }
-        featureIndex--;
-        start = featureStart;
       }
-      if (start !== end) {
-        this.drawElements(gl, context, start, end);
-        gl.clear(gl.DEPTH_BUFFER_BIT);
-      }
-      start = end = groupStart;
+      declutterGroup.length = 5;
+      ol.extent.createOrUpdateEmpty(declutterGroup);
     }
-  };
-
-
-  /**
-   * @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);
-  };
+  }
+};
 
 
-  /**
-   * @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);
+/**
+ * @private
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {ol.Transform} transform Transform.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ *     to skip.
+ * @param {Array.<*>} instructions Instructions array.
+ * @param {function((ol.Feature|ol.render.Feature)): T|undefined}
+ *     featureCallback Feature callback.
+ * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
+ *     extent.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.canvas.Replay.prototype.replay_ = function(
+    context, transform, 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 anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image;
+  var pendingFill = 0;
+  var pendingStroke = 0;
+  var lastFillInstruction = null;
+  var lastStrokeInstruction = null;
+  var coordinateCache = this.coordinateCache_;
+  var viewRotation = this.viewRotation_;
+
+  var state = /** @type {olx.render.State} */ ({
+    context: context,
+    pixelRatio: this.pixelRatio,
+    resolution: this.resolution,
+    rotation: viewRotation
+  });
 
-goog.provide('ol.render.webgl.TextReplay');
+  // 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 /** @type {ol.Feature|ol.render.Feature} */ feature, 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);
+          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.CUSTOM:
+        d = /** @type {number} */ (instruction[1]);
+        dd = instruction[2];
+        var geometry = /** @type {ol.geom.SimpleGeometry} */ (instruction[3]);
+        var renderer = instruction[4];
+        var fn = instruction.length == 6 ? instruction[5] : undefined;
+        state.geometry = geometry;
+        state.feature = feature;
+        if (!(i in coordinateCache)) {
+          coordinateCache[i] = [];
+        }
+        var coords = coordinateCache[i];
+        if (fn) {
+          fn(pixelCoordinates, d, dd, 2, coords);
+        } else {
+          coords[0] = pixelCoordinates[d];
+          coords[1] = pixelCoordinates[d + 1];
+          coords.length = 2;
+        }
+        renderer(coords, state);
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.DRAW_IMAGE:
+        d = /** @type {number} */ (instruction[1]);
+        dd = /** @type {number} */ (instruction[2]);
+        image =  /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */
+            (instruction[3]);
+        // Remaining arguments in DRAW_IMAGE are in alphabetical order
+        anchorX = /** @type {number} */ (instruction[4]);
+        anchorY = /** @type {number} */ (instruction[5]);
+        declutterGroup = featureCallback ? null : /** @type {ol.DeclutterGroup} */ (instruction[6]);
+        var height = /** @type {number} */ (instruction[7]);
+        var opacity = /** @type {number} */ (instruction[8]);
+        var originX = /** @type {number} */ (instruction[9]);
+        var originY = /** @type {number} */ (instruction[10]);
+        var rotateWithView = /** @type {boolean} */ (instruction[11]);
+        var rotation = /** @type {number} */ (instruction[12]);
+        var scale = /** @type {number} */ (instruction[13]);
+        var snapToPixel = /** @type {boolean} */ (instruction[14]);
+        var width = /** @type {number} */ (instruction[15]);
+
+        var padding, backgroundFill, backgroundStroke;
+        if (instruction.length > 16) {
+          padding = /** @type {Array.<number>} */ (instruction[16]);
+          backgroundFill = /** @type {boolean} */ (instruction[17]);
+          backgroundStroke = /** @type {boolean} */ (instruction[18]);
+        } else {
+          padding = ol.render.canvas.defaultPadding;
+          backgroundFill = backgroundStroke = false;
+        }
 
-goog.require('ol');
+        if (rotateWithView) {
+          rotation += viewRotation;
+        }
+        for (; d < dd; d += 2) {
+          this.replayImage_(context,
+              pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
+              declutterGroup, height, opacity, originX, originY, rotation, scale,
+              snapToPixel, width, padding,
+              backgroundFill ? /** @type {Array.<*>} */ (lastFillInstruction) : null,
+              backgroundStroke ? /** @type {Array.<*>} */ (lastStrokeInstruction) : null);
+        }
+        this.renderDeclutter_(declutterGroup, feature);
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.DRAW_CHARS:
+        var begin = /** @type {number} */ (instruction[1]);
+        var end = /** @type {number} */ (instruction[2]);
+        var baseline = /** @type {number} */ (instruction[3]);
+        declutterGroup = featureCallback ? null : /** @type {ol.DeclutterGroup} */ (instruction[4]);
+        var overflow = /** @type {number} */ (instruction[5]);
+        var fillKey = /** @type {string} */ (instruction[6]);
+        var maxAngle = /** @type {number} */ (instruction[7]);
+        var measure = /** @type {function(string):number} */ (instruction[8]);
+        var offsetY = /** @type {number} */ (instruction[9]);
+        var strokeKey = /** @type {string} */ (instruction[10]);
+        var strokeWidth =  /** @type {number} */ (instruction[11]);
+        var text = /** @type {string} */ (instruction[12]);
+        var textKey = /** @type {string} */ (instruction[13]);
+        var textScale = /** @type {number} */ (instruction[14]);
+
+        var pathLength = ol.geom.flat.length.lineString(pixelCoordinates, begin, end, 2);
+        var textLength = measure(text);
+        if (overflow || textLength <= pathLength) {
+          var textAlign = /** @type {ol.render.canvas.TextReplay} */ (this).textStates[textKey].textAlign;
+          var startM = (pathLength - textLength) * ol.render.replay.TEXT_ALIGN[textAlign];
+          var parts = ol.geom.flat.textpath.lineString(
+              pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle);
+          if (parts) {
+            var c, cc, chars, label, part;
+            if (strokeKey) {
+              for (c = 0, cc = parts.length; c < cc; ++c) {
+                part = parts[c]; // x, y, anchorX, rotation, chunk
+                chars = /** @type {string} */ (part[4]);
+                label = /** @type {ol.render.canvas.TextReplay} */ (this).getImage(chars, textKey, '', strokeKey);
+                anchorX = /** @type {number} */ (part[2]) + strokeWidth;
+                anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY;
+                this.replayImage_(context,
+                    /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
+                    anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
+                    /** @type {number} */ (part[3]), textScale, false, label.width,
+                    ol.render.canvas.defaultPadding, null, null);
+              }
+            }
+            if (fillKey) {
+              for (c = 0, cc = parts.length; c < cc; ++c) {
+                part = parts[c]; // x, y, anchorX, rotation, chunk
+                chars = /** @type {string} */ (part[4]);
+                label = /** @type {ol.render.canvas.TextReplay} */ (this).getImage(chars, textKey, fillKey, '');
+                anchorX = /** @type {number} */ (part[2]);
+                anchorY = baseline * label.height - offsetY;
+                this.replayImage_(context,
+                    /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
+                    anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
+                    /** @type {number} */ (part[3]), textScale, false, label.width,
+                    ol.render.canvas.defaultPadding, null, null);
+              }
+            }
+          }
+        }
+        this.renderDeclutter_(declutterGroup, feature);
+        ++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);
+        }
+        ++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:
+        lastFillInstruction = instruction;
+        this.fillOrigin_ = instruction[2];
 
+        if (pendingFill) {
+          this.fill_(context);
+          pendingFill = 0;
+          if (pendingStroke) {
+            context.stroke();
+            pendingStroke = 0;
+          }
+        }
 
-if (ol.ENABLE_WEBGL) {
+        context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]);
+        ++i;
+        break;
+      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
+        lastStrokeInstruction = instruction;
+        if (pendingStroke) {
+          context.stroke();
+          pendingStroke = 0;
+        }
+        this.setStrokeStyle_(context, /** @type {Array.<*>} */ (instruction));
+        ++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);
+  }
+  if (pendingStroke) {
+    context.stroke();
+  }
+  return undefined;
+};
 
-  /**
-   * @constructor
-   * @abstract
-   * @param {number} tolerance Tolerance.
-   * @param {ol.Extent} maxExtent Max extent.
-   * @struct
-   */
-  ol.render.webgl.TextReplay = function(tolerance, maxExtent) {};
 
-  /**
-   * @param {ol.style.Text} textStyle Text style.
-   */
-  ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {};
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {ol.Transform} 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, transform, viewRotation, skippedFeaturesHash) {
+  this.viewRotation_ = viewRotation;
+  this.replay_(context, transform,
+      skippedFeaturesHash, this.instructions, undefined, undefined);
+};
 
-  /**
-   * @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;
-  };
 
-  /**
-   * @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) {};
+/**
+ * @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.render.canvas.Replay.prototype.replayHitDetection = function(
+    context, transform, viewRotation, skippedFeaturesHash,
+    opt_featureCallback, opt_hitExtent) {
+  this.viewRotation_ = viewRotation;
+  return this.replay_(context, transform, skippedFeaturesHash,
+      this.hitDetectionInstructions, opt_featureCallback, opt_hitExtent);
+};
 
-  /**
-   * @abstract
-   * @param {ol.webgl.Context} context Context.
-   */
-  ol.render.webgl.TextReplay.prototype.finish = function(context) {};
 
-  /**
-   * @param {ol.webgl.Context} context WebGL context.
-   * @return {function()} Delete resources function.
-   */
-  ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) {
-    return ol.nullFunction;
-  };
+/**
+ * Reverse the hit detection instructions.
+ */
+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;
+    }
+  }
+};
 
-}
 
-goog.provide('ol.render.webgl.ReplayGroup');
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.Replay.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;
 
-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');
+    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;
+  }
+};
 
 
-if (ol.ENABLE_WEBGL) {
+/**
+ * @param {ol.CanvasFillStrokeState} state State.
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ */
+ol.render.canvas.Replay.prototype.applyFill = function(state, geometry) {
+  var fillStyle = state.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);
+};
 
-  /**
-   * @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;
+/**
+ * @param {ol.CanvasFillStrokeState} state State.
+ */
+ol.render.canvas.Replay.prototype.applyStroke = function(state) {
+  this.instructions.push([
+    ol.render.canvas.Instruction.SET_STROKE_STYLE,
+    state.strokeStyle, state.lineWidth * this.pixelRatio, state.lineCap,
+    state.lineJoin, state.miterLimit,
+    this.applyPixelRatio(state.lineDash), state.lineDashOffset * this.pixelRatio
+  ]);
+};
 
-    /**
-     * @type {number}
-     * @private
-     */
-    this.tolerance_ = tolerance;
 
-    /**
-     * @type {number|undefined}
-     * @private
-     */
-    this.renderBuffer_ = opt_renderBuffer;
+/**
+ * @param {ol.CanvasFillStrokeState} state State.
+ * @param {function(this:ol.render.canvas.Replay, ol.CanvasFillStrokeState, (ol.geom.Geometry|ol.render.Feature))} applyFill Apply fill.
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ */
+ol.render.canvas.Replay.prototype.updateFillStyle = function(state, applyFill, geometry) {
+  var fillStyle = state.fillStyle;
+  if (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle) {
+    applyFill.call(this, state, geometry);
+    state.currentFillStyle = fillStyle;
+  }
+};
 
-    /**
-     * @private
-     * @type {!Object.<string,
-     *        Object.<ol.render.ReplayType, ol.render.webgl.Replay>>}
-     */
-    this.replaysByZIndex_ = {};
 
-  };
-  ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);
+/**
+ * @param {ol.CanvasFillStrokeState} state State.
+ * @param {function(this:ol.render.canvas.Replay, ol.CanvasFillStrokeState)} applyStroke Apply stroke.
+ */
+ol.render.canvas.Replay.prototype.updateStrokeStyle = function(state, applyStroke) {
+  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 ||
+      (lineDash != state.currentLineDash && !ol.array.equals(state.currentLineDash, lineDash)) ||
+      state.currentLineDashOffset != lineDashOffset ||
+      state.currentLineJoin != lineJoin ||
+      state.currentLineWidth != lineWidth ||
+      state.currentMiterLimit != miterLimit) {
+    applyStroke.call(this, state);
+    state.currentStrokeStyle = strokeStyle;
+    state.currentLineCap = lineCap;
+    state.currentLineDash = lineDash;
+    state.currentLineDashOffset = lineDashOffset;
+    state.currentLineJoin = lineJoin;
+    state.currentLineWidth = lineWidth;
+    state.currentMiterLimit = miterLimit;
+  }
+};
 
 
-  /**
-   * @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;
-    };
-  };
+/**
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ */
+ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) {
+  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 {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);
-      }
-    }
-  };
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.render.canvas.Replay.prototype.finish = ol.nullFunction;
 
 
-  /**
-   * @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;
+/**
+ * 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() {
+  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 replay;
-  };
+  }
+  return this.bufferedMaxExtent_;
+};
 
+goog.provide('ol.render.canvas.ImageReplay');
+
+goog.require('ol');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
 
-  /**
-   * @inheritDoc
-   */
-  ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
-    return ol.obj.isEmpty(this.replaysByZIndex_);
-  };
 
+/**
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @param {?} declutterTree Declutter tree.
+ * @struct
+ */
+ol.render.canvas.ImageReplay = function(
+    tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
+  ol.render.canvas.Replay.call(this,
+      tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
 
   /**
-   * @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.
+   * @private
+   * @type {ol.DeclutterGroup}
    */
-  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);
-
-    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);
-        }
-      }
-    }
-  };
-
+  this.declutterGroup_ = null;
 
   /**
    * @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
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
    */
-  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;
-  };
-
+  this.hitDetectionImage_ = null;
 
   /**
-   * @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
+   * @private
+   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
    */
-  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());
+  this.image_ = null;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.anchorX_ = undefined;
 
-    /**
-     * @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);
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.anchorY_ = undefined;
 
-          if (imageData[3] > 0) {
-            var result = callback(feature);
-            if (result) {
-              return result;
-            }
-          }
-        }, true, hitExtent);
-  };
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.height_ = undefined;
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.opacity_ = 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.
-   * @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);
+   * @private
+   * @type {number|undefined}
+   */
+  this.originX_ = undefined;
 
-    return hasFeature !== undefined;
-  };
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.originY_ = undefined;
 
   /**
-   * @const
    * @private
-   * @type {Array.<number>}
+   * @type {boolean|undefined}
    */
-  ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1];
+  this.rotateWithView_ = undefined;
 
   /**
-   * @const
    * @private
-   * @type {Object.<ol.render.ReplayType,
-   *                function(new: ol.render.webgl.Replay, number,
-   *                ol.Extent)>}
+   * @type {number|undefined}
    */
-  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
-  };
+  this.rotation_ = undefined;
 
-}
-
-goog.provide('ol.render.webgl.Immediate');
-
-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');
-
-
-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);
-
-    /**
-     * @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 {ol.style.Fill}
-     */
-    this.fillStyle_ = null;
-
-    /**
-     * @private
-     * @type {ol.style.Stroke}
-     */
-    this.strokeStyle_ = null;
-
-  };
-  ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext);
-
-
-  /**
-   * 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());
-  };
-
-
-  /**
-   * 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
-    }
-  };
-
-
-  /**
-   * @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);
-  };
-
-
-  /**
-   * @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]);
-    }
-  };
-
-
-  /**
-   * @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)();
-  };
-
-
-  /**
-   * @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)();
-  };
-
-
-  /**
-   * @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)();
-  };
-
-
-  /**
-   * @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)();
-  };
-
-
-  /**
-   * @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)();
-  };
-
-
-  /**
-   * @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)();
-  };
-
-
-  /**
-   * @inheritDoc
-   */
-  ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
-    this.imageStyle_ = imageStyle;
-  };
-
-
-  /**
-   * @inheritDoc
-   */
-  ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
-    this.fillStyle_ = fillStyle;
-    this.strokeStyle_ = strokeStyle;
-  };
-
-}
-
-goog.provide('ol.structs.LRUCache');
-
-goog.require('ol.asserts');
-
-
-/**
- * Implements a Least-Recently-Used cache where the keys do not conflict with
- * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring
- * items from the cache is the responsibility of the user.
- * @constructor
- * @struct
- * @template T
- */
-ol.structs.LRUCache = function() {
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.count_ = 0;
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.scale_ = undefined;
 
   /**
    * @private
-   * @type {!Object.<string, ol.LRUCacheEntry>}
-   */
-  this.entries_ = {};
-
-  /**
-   * @private
-   * @type {?ol.LRUCacheEntry}
+   * @type {boolean|undefined}
    */
-  this.oldest_ = null;
+  this.snapToPixel_ = undefined;
 
   /**
    * @private
-   * @type {?ol.LRUCacheEntry}
+   * @type {number|undefined}
    */
-  this.newest_ = null;
+  this.width_ = undefined;
 
 };
+ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay);
 
 
 /**
- * FIXME empty description for jsdoc
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @private
+ * @return {number} My end.
  */
-ol.structs.LRUCache.prototype.clear = function() {
-  this.count_ = 0;
-  this.entries_ = {};
-  this.oldest_ = null;
-  this.newest_ = null;
+ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
+  return this.appendFlatCoordinates(
+      flatCoordinates, offset, end, stride, false, false);
 };
 
 
 /**
- * @param {string} key Key.
- * @return {boolean} Contains key.
+ * @inheritDoc
  */
-ol.structs.LRUCache.prototype.containsKey = function(key) {
-  return this.entries_.hasOwnProperty(key);
+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.declutterGroup_, this.height_, this.opacity_,
+    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
+    this.scale_ * this.pixelRatio, 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.declutterGroup_, this.height_, this.opacity_,
+    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
+    this.scale_, this.snapToPixel_, this.width_
+  ]);
+  this.endGeometry(pointGeometry, feature);
 };
 
 
 /**
- * @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
+ * @inheritDoc
  */
-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.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.declutterGroup_, this.height_, this.opacity_,
+    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
+    this.scale_ * this.pixelRatio, 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.declutterGroup_, this.height_, this.opacity_,
+    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
+    this.scale_, this.snapToPixel_, this.width_
+  ]);
+  this.endGeometry(multiPointGeometry, feature);
 };
 
 
 /**
- * @param {string} key Key.
- * @return {T} Value.
+ * @inheritDoc
  */
-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 {
-    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.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;
 };
 
 
 /**
- * @return {number} Count.
+ * @inheritDoc
  */
-ol.structs.LRUCache.prototype.getCount = function() {
-  return this.count_;
+ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle, declutterGroup) {
+  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.declutterGroup_ = /** @type {ol.DeclutterGroup} */ (declutterGroup);
+  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.provide('ol.render.canvas.LineStringReplay');
 
-/**
- * @return {Array.<string>} Keys.
- */
-ol.structs.LRUCache.prototype.getKeys = function() {
-  var keys = new Array(this.count_);
-  var i = 0;
-  var entry;
-  for (entry = this.newest_; entry; entry = entry.older) {
-    keys[i++] = entry.key_;
-  }
-  return keys;
-};
+goog.require('ol');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
 
 
 /**
- * @return {Array.<T>} Values.
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @param {?} declutterTree Declutter tree.
+ * @struct
  */
-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;
+ol.render.canvas.LineStringReplay = function(
+    tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
+  ol.render.canvas.Replay.call(this,
+      tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
 };
+ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay);
 
 
 /**
- * @return {T} Last value.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @private
+ * @return {number} end.
  */
-ol.structs.LRUCache.prototype.peekLast = function() {
-  return this.oldest_.value_;
+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;
 };
 
 
 /**
- * @return {string} Last key.
+ * @inheritDoc
  */
-ol.structs.LRUCache.prototype.peekLastKey = function() {
-  return this.oldest_.key_;
+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.updateStrokeStyle(state, this.applyStroke);
+  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
+  ], [
+    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);
 };
 
 
 /**
- * @return {T} value Value.
+ * @inheritDoc
  */
-ol.structs.LRUCache.prototype.pop = function() {
-  var entry = this.oldest_;
-  delete this.entries_[entry.key_];
-  if (entry.newer) {
-    entry.newer.older = null;
+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.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
-  if (!this.oldest_) {
-    this.newest_ = null;
+  this.updateStrokeStyle(state, this.applyStroke);
+  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
+  ], [
+    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.count_;
-  return entry.value_;
+  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
+  this.endGeometry(multiLineStringGeometry, feature);
 };
 
 
 /**
- * @param {string} key Key.
- * @param {T} value Value.
+ * @inheritDoc
  */
-ol.structs.LRUCache.prototype.replace = function(key, value) {
-  this.get(key);  // update `newest_`
-  this.entries_[key].value_ = value;
+ol.render.canvas.LineStringReplay.prototype.finish = function() {
+  var state = this.state;
+  if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) {
+    this.instructions.push([ol.render.canvas.Instruction.STROKE]);
+  }
+  this.reverseHitDetectionInstructions();
+  this.state = null;
 };
 
 
 /**
- * @param {string} key Key.
- * @param {T} value Value.
+ * @inheritDoc.
  */
-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;
+ol.render.canvas.LineStringReplay.prototype.applyStroke = function(state) {
+  if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) {
+    this.instructions.push([ol.render.canvas.Instruction.STROKE]);
+    state.lastStroke = this.coordinates.length;
   }
-  this.newest_ = entry;
-  this.entries_[key] = entry;
-  ++this.count_;
+  state.lastStroke = 0;
+  ol.render.canvas.Replay.prototype.applyStroke.call(this, state);
+  this.instructions.push([ol.render.canvas.Instruction.BEGIN_PATH]);
 };
 
-// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)
-
-goog.provide('ol.renderer.webgl.Map');
+goog.provide('ol.render.canvas.PolygonReplay');
 
 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');
-
-
-if (ol.ENABLE_WEBGL) {
-
-  /**
-   * @constructor
-   * @extends {ol.renderer.Map}
-   * @param {Element} container Container.
-   * @param {ol.Map} map Map.
-   */
-  ol.renderer.webgl.Map = function(container, map) {
-    ol.renderer.Map.call(this, container, map);
+goog.require('ol.color');
+goog.require('ol.geom.flat.simplify');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
 
-    /**
-     * @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);
 
-    /**
-     * @private
-     * @type {number}
-     */
-    this.clipTileCanvasWidth_ = 0;
-
-    /**
-     * @private
-     * @type {number}
-     */
-    this.clipTileCanvasHeight_ = 0;
+/**
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @param {?} declutterTree Declutter tree.
+ * @struct
+ */
+ol.render.canvas.PolygonReplay = function(
+    tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
+  ol.render.canvas.Replay.call(this,
+      tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
+};
+ol.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);
 
-    /**
-     * @private
-     * @type {CanvasRenderingContext2D}
-     */
-    this.clipTileContext_ = ol.dom.createCanvasContext2D();
 
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this.renderedVisible_ = true;
+/**
+ * @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 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;
+};
 
-    /**
-     * @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_);
+/**
+ * @inheritDoc
+ */
+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
+    ]);
+  }
+  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);
+};
 
-    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();
+/**
+ * @inheritDoc
+ */
+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
+    ]);
+  }
+  var ends = polygonGeometry.getEnds();
+  var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
+  var stride = polygonGeometry.getStride();
+  this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
+  this.endGeometry(polygonGeometry, feature);
+};
 
-    /**
-     * @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();
-        });
+/**
+ * @inheritDoc
+ */
+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
+    ]);
+  }
+  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 {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);
+/**
+ * @inheritDoc
+ */
+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);
+    }
+  }
+};
 
 
-    /**
-     * @private
-     * @type {number}
-     */
-    this.textureCacheFrameMarkerCount_ = 0;
+/**
+ * @private
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ */
+ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function(geometry) {
+  var state = this.state;
+  var fillStyle = state.fillStyle;
+  if (fillStyle !== undefined) {
+    this.updateFillStyle(state, this.applyFill, geometry);
+  }
+  if (state.strokeStyle !== undefined) {
+    this.updateStrokeStyle(state, this.applyStroke);
+  }
+};
 
-    this.initializeGL_();
-  };
-  ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
+goog.provide('ol.geom.flat.straightchunk');
 
 
-  /**
-   * @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]);
+/**
+ * @param {number} maxAngle Maximum acceptable angle delta between segments.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {Array.<number>} Start and end of the first suitable chunk of the
+ * given `flatCoordinates`.
+ */
+ol.geom.flat.straightchunk.lineString = function(maxAngle, flatCoordinates, offset, end, stride) {
+  var chunkStart = offset;
+  var chunkEnd = offset;
+  var chunkM = 0;
+  var m = 0;
+  var start = offset;
+  var acos, i, m12, m23, x1, y1, x12, y12, x23, y23;
+  for (i = offset; i < end; i += stride) {
+    var x2 = flatCoordinates[i];
+    var y2 = flatCoordinates[i + 1];
+    if (x1 !== undefined) {
+      x23 = x2 - x1;
+      y23 = y2 - y1;
+      m23 = Math.sqrt(x23 * x23 + y23 * y23);
+      if (x12 !== undefined) {
+        m += m12;
+        acos = Math.acos((x12 * x23 + y12 * y23) / (m12 * m23));
+        if (acos > maxAngle) {
+          if (m > chunkM) {
+            chunkM = m;
+            chunkStart = start;
+            chunkEnd = i;
+          }
+          m = 0;
+          start = i - stride;
         }
-        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
-      });
+      m12 = m23;
+      x12 = x23;
+      y12 = y23;
     }
-  };
+    x1 = x2;
+    y1 = y2;
+  }
+  m += m23;
+  return m > chunkM ? [start, i] : [chunkStart, chunkEnd];
+};
 
+goog.provide('ol.style.TextPlacement');
 
-  /**
-   * @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;
+/**
+ * Text placement. One of `'point'`, `'line'`. Default is `'point'`. Note that
+ * `'line'` requires the underlying geometry to be a {@link ol.geom.LineString},
+ * {@link ol.geom.Polygon}, {@link ol.geom.MultiLineString} or
+ * {@link ol.geom.MultiPolygon}.
+ * @enum {string}
+ */
+ol.style.TextPlacement = {
+  POINT: 'point',
+  LINE: 'line'
+};
 
-      var resolution = viewState.resolution;
-      var center = viewState.center;
-      var rotation = viewState.rotation;
+goog.provide('ol.render.canvas.TextReplay');
+
+goog.require('ol');
+goog.require('ol.colorlike');
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.geom.flat.straightchunk');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.has');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.Instruction');
+goog.require('ol.render.canvas.Replay');
+goog.require('ol.render.replay');
+goog.require('ol.style.TextPlacement');
 
-      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);
-    }
-  };
 
+/**
+ * @constructor
+ * @extends {ol.render.canvas.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Maximum extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {boolean} overlaps The replay can have overlapping geometries.
+ * @param {?} declutterTree Declutter tree.
+ * @struct
+ */
+ol.render.canvas.TextReplay = function(
+    tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
+  ol.render.canvas.Replay.call(this,
+      tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {ol.DeclutterGroup}
    */
-  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);
-  };
-
+  this.declutterGroup_;
 
   /**
-   * @param {ol.Map} map Map.
-   * @param {olx.FrameState} frameState Frame state.
    * @private
+   * @type {Array.<HTMLCanvasElement>}
    */
-  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();
-    }
-  };
-
+  this.labels_ = null;
 
   /**
-   * @return {ol.webgl.Context} The context.
+   * @private
+   * @type {string}
    */
-  ol.renderer.webgl.Map.prototype.getContext = function() {
-    return this.context_;
-  };
-
+  this.text_ = '';
 
   /**
-   * @return {WebGLRenderingContext} GL.
+   * @private
+   * @type {number}
    */
-  ol.renderer.webgl.Map.prototype.getGL = function() {
-    return this.gl_;
-  };
-
+  this.textOffsetX_ = 0;
 
   /**
-   * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
+   * @private
+   * @type {number}
    */
-  ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
-    return this.tileTextureQueue_;
-  };
+  this.textOffsetY_ = 0;
 
+  /**
+   * @private
+   * @type {boolean|undefined}
+   */
+  this.textRotateWithView_ = undefined;
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {number}
    */
-  ol.renderer.webgl.Map.prototype.getType = function() {
-    return ol.renderer.Type.WEBGL;
-  };
+  this.textRotation_ = 0;
 
+  /**
+   * @private
+   * @type {?ol.CanvasFillState}
+   */
+  this.textFillState_ = null;
 
   /**
-   * @param {ol.events.Event} event Event.
-   * @protected
+   * @type {Object.<string, ol.CanvasFillState>}
    */
-  ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) {
-    event.preventDefault();
-    this.textureCache_.clear();
-    this.textureCacheFrameMarkerCount_ = 0;
+  this.fillStates = {};
 
-    var renderers = this.getLayerRenderers();
-    for (var id in renderers) {
-      var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]);
-      renderer.handleWebGLContextLost();
-    }
-  };
+  /**
+   * @private
+   * @type {?ol.CanvasStrokeState}
+   */
+  this.textStrokeState_ = null;
 
+  /**
+   * @type {Object.<string, ol.CanvasStrokeState>}
+   */
+  this.strokeStates = {};
 
   /**
-   * @protected
+   * @private
+   * @type {ol.CanvasTextState}
    */
-  ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
-    this.initializeGL_();
-    this.getMap().render();
-  };
+  this.textState_ = /** @type {ol.CanvasTextState} */ ({});
 
+  /**
+   * @type {Object.<string, ol.CanvasTextState>}
+   */
+  this.textStates = {};
 
   /**
    * @private
+   * @type {string}
    */
-  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);
-  };
-
+  this.textKey_ = '';
 
   /**
-   * @param {ol.Tile} tile Tile.
-   * @return {boolean} Is tile texture loaded.
+   * @private
+   * @type {string}
    */
-  ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) {
-    return this.textureCache_.containsKey(tile.getKey());
-  };
+  this.fillKey_ = '';
 
+  /**
+   * @private
+   * @type {string}
+   */
+  this.strokeKey_ = '';
 
   /**
-   * @inheritDoc
+   * @private
+   * @type {Object.<string, Object.<string, number>>}
    */
-  ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
+  this.widths_ = {};
 
-    var context = this.getContext();
-    var gl = this.getGL();
+  var labelCache = ol.render.canvas.labelCache;
+  labelCache.prune();
 
-    if (gl.isContextLost()) {
-      return false;
-    }
+};
+ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
 
-    if (!frameState) {
-      if (this.renderedVisible_) {
-        this.canvas_.style.display = 'none';
-        this.renderedVisible_ = false;
-      }
-      return false;
-    }
 
-    this.focus_ = frameState.focus;
+/**
+ * @param {string} font Font to use for measuring.
+ * @param {Array.<string>} lines Lines to measure.
+ * @param {Array.<number>} widths Array will be populated with the widths of
+ * each line.
+ * @return {number} Width of the whole text.
+ */
+ol.render.canvas.TextReplay.measureTextWidths = function(font, lines, widths) {
+  var numLines = lines.length;
+  var width = 0;
+  var currentWidth, i;
+  for (i = 0; i < numLines; ++i) {
+    currentWidth = ol.render.canvas.measureTextWidth(font, lines[i]);
+    width = Math.max(width, currentWidth);
+    widths.push(currentWidth);
+  }
+  return width;
+};
 
-    this.textureCache_.set((-frameState.index).toString(), null);
-    ++this.textureCacheFrameMarkerCount_;
 
-    this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
+  var fillState = this.textFillState_;
+  var strokeState = this.textStrokeState_;
+  var textState = this.textState_;
+  if (this.text_ === '' || !textState || (!fillState && !strokeState)) {
+    return;
+  }
 
-    /** @type {Array.<ol.LayerState>} */
-    var layerStatesToDraw = [];
-    var layerStatesArray = frameState.layerStatesArray;
-    ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
+  var begin = this.coordinates.length;
 
-    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);
-        }
+  var geometryType = geometry.getType();
+  var flatCoordinates = null;
+  var end = 2;
+  var stride = 2;
+  var i, ii;
+
+  if (textState.placement === ol.style.TextPlacement.LINE) {
+    if (!ol.extent.intersects(this.getBufferedMaxExtent(), geometry.getExtent())) {
+      return;
+    }
+    var ends;
+    flatCoordinates = geometry.getFlatCoordinates();
+    stride = geometry.getStride();
+    if (geometryType == ol.geom.GeometryType.LINE_STRING) {
+      ends = [flatCoordinates.length];
+    } else if (geometryType == ol.geom.GeometryType.MULTI_LINE_STRING) {
+      ends = geometry.getEnds();
+    } else if (geometryType == ol.geom.GeometryType.POLYGON) {
+      ends = geometry.getEnds().slice(0, 1);
+    } else if (geometryType == ol.geom.GeometryType.MULTI_POLYGON) {
+      var endss = geometry.getEndss();
+      ends = [];
+      for (i = 0, ii = endss.length; i < ii; ++i) {
+        ends.push(endss[i][0]);
       }
     }
-
-    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;
+    this.beginGeometry(geometry, feature);
+    var textAlign = textState.textAlign;
+    var flatOffset = 0;
+    var flatEnd;
+    for (var o = 0, oo = ends.length; o < oo; ++o) {
+      if (textAlign == undefined) {
+        var range = ol.geom.flat.straightchunk.lineString(
+            textState.maxAngle, flatCoordinates, flatOffset, ends[o], stride);
+        flatOffset = range[0];
+        flatEnd = range[1];
+      } else {
+        flatEnd = ends[o];
+      }
+      for (i = flatOffset; i < flatEnd; i += stride) {
+        this.coordinates.push(flatCoordinates[i], flatCoordinates[i + 1]);
+      }
+      end = this.coordinates.length;
+      flatOffset = ends[o];
+      this.drawChars_(begin, end, this.declutterGroup_);
+      begin = end;
     }
+    this.endGeometry(geometry, feature);
 
-    gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null);
-
-    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);
+  } else {
+    var label = this.getImage(this.text_, this.textKey_, this.fillKey_, this.strokeKey_);
+    var width = label.width / this.pixelRatio;
+    switch (geometryType) {
+      case ol.geom.GeometryType.POINT:
+      case ol.geom.GeometryType.MULTI_POINT:
+        flatCoordinates = geometry.getFlatCoordinates();
+        end = flatCoordinates.length;
+        break;
+      case ol.geom.GeometryType.LINE_STRING:
+        flatCoordinates = /** @type {ol.geom.LineString} */ (geometry).getFlatMidpoint();
+        break;
+      case ol.geom.GeometryType.CIRCLE:
+        flatCoordinates = /** @type {ol.geom.Circle} */ (geometry).getCenter();
+        break;
+      case ol.geom.GeometryType.MULTI_LINE_STRING:
+        flatCoordinates = /** @type {ol.geom.MultiLineString} */ (geometry).getFlatMidpoints();
+        end = flatCoordinates.length;
+        break;
+      case ol.geom.GeometryType.POLYGON:
+        flatCoordinates = /** @type {ol.geom.Polygon} */ (geometry).getFlatInteriorPoint();
+        if (!textState.overflow && flatCoordinates[2] / this.resolution < width) {
+          return;
+        }
+        stride = 3;
+        break;
+      case ol.geom.GeometryType.MULTI_POLYGON:
+        var interiorPoints = /** @type {ol.geom.MultiPolygon} */ (geometry).getFlatInteriorPoints();
+        flatCoordinates = [];
+        for (i = 0, ii = interiorPoints.length; i < ii; i += 3) {
+          if (textState.overflow || interiorPoints[i + 2] / this.resolution >= width) {
+            flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]);
+          }
+        }
+        end = flatCoordinates.length;
+        if (end == 0) {
+          return;
+        }
+        break;
+      default:
     }
-
-    if (!this.renderedVisible_) {
-      this.canvas_.style.display = '';
-      this.renderedVisible_ = true;
+    end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false);
+    this.beginGeometry(geometry, feature);
+    if (textState.backgroundFill || textState.backgroundStroke) {
+      this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke);
+      this.updateFillStyle(this.state, this.applyFill, geometry);
+      this.updateStrokeStyle(this.state, this.applyStroke);
     }
+    this.drawTextImage_(label, begin, end);
+    this.endGeometry(geometry, feature);
+  }
+};
 
-    this.calculateMatrices2D(frameState);
 
-    if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
-        ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
-      frameState.postRenderFunctions.push(
-        /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this))
-      );
+/**
+ * @param {string} text Text.
+ * @param {string} textKey Text style key.
+ * @param {string} fillKey Fill style key.
+ * @param {string} strokeKey Stroke style key.
+ * @return {HTMLCanvasElement} Image.
+ */
+ol.render.canvas.TextReplay.prototype.getImage = function(text, textKey, fillKey, strokeKey) {
+  var label;
+  var key = strokeKey + textKey + text + fillKey + this.pixelRatio;
+
+  var labelCache = ol.render.canvas.labelCache;
+  if (!labelCache.containsKey(key)) {
+    var strokeState = strokeKey ? this.strokeStates[strokeKey] || this.textStrokeState_ : null;
+    var fillState = fillKey ? this.fillStates[fillKey] || this.textFillState_ : null;
+    var textState = this.textStates[textKey] || this.textState_;
+    var pixelRatio = this.pixelRatio;
+    var scale = textState.scale * pixelRatio;
+    var align =  ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
+    var strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
+
+    var lines = text.split('\n');
+    var numLines = lines.length;
+    var widths = [];
+    var width = ol.render.canvas.TextReplay.measureTextWidths(textState.font, lines, widths);
+    var lineHeight = ol.render.canvas.measureTextHeight(textState.font);
+    var height = lineHeight * numLines;
+    var renderWidth = (width + strokeWidth);
+    var context = ol.dom.createCanvasContext2D(
+        Math.ceil(renderWidth * scale),
+        Math.ceil((height + strokeWidth) * scale));
+    label = context.canvas;
+    labelCache.set(key, label);
+    if (scale != 1) {
+      context.scale(scale, scale);
     }
-
-    if (!this.tileTextureQueue_.isEmpty()) {
-      frameState.postRenderFunctions.push(this.loadNextTileTexture_);
-      frameState.animate = true;
+    context.font = textState.font;
+    if (strokeKey) {
+      context.strokeStyle = strokeState.strokeStyle;
+      context.lineWidth = strokeWidth * (ol.has.SAFARI ? scale : 1);
+      context.lineCap = strokeState.lineCap;
+      context.lineJoin = strokeState.lineJoin;
+      context.miterLimit = strokeState.miterLimit;
+      if (ol.has.CANVAS_LINE_DASH && strokeState.lineDash.length) {
+        context.setLineDash(strokeState.lineDash);
+        context.lineDashOffset = strokeState.lineDashOffset;
+      }
     }
+    if (fillKey) {
+      context.fillStyle = fillState.fillStyle;
+    }
+    context.textBaseline = 'middle';
+    context.textAlign = 'center';
+    var leftRight = (0.5 - align);
+    var x = align * label.width / scale + leftRight * strokeWidth;
+    var i;
+    if (strokeKey) {
+      for (i = 0; i < numLines; ++i) {
+        context.strokeText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
+      }
+    }
+    if (fillKey) {
+      for (i = 0; i < numLines; ++i) {
+        context.fillText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
+      }
+    }
+  }
+  return labelCache.get(key);
+};
 
-    this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
-
-    this.scheduleRemoveUnusedLayerRenderers(frameState);
-    this.scheduleExpireIconCache(frameState);
-
-  };
 
+/**
+ * @private
+ * @param {HTMLCanvasElement} label Label.
+ * @param {number} begin Begin.
+ * @param {number} end End.
+ */
+ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(label, begin, end) {
+  var textState = this.textState_;
+  var strokeState = this.textStrokeState_;
+  var pixelRatio = this.pixelRatio;
+  var align = ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
+  var baseline = ol.render.replay.TEXT_ALIGN[textState.textBaseline];
+  var strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
+
+  var anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth;
+  var anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
+  this.instructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
+    label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
+    this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
+    1, true, label.width,
+    textState.padding == ol.render.canvas.defaultPadding ?
+      ol.render.canvas.defaultPadding : textState.padding.map(function(p) {
+        return p * pixelRatio;
+      }),
+    !!textState.backgroundFill, !!textState.backgroundStroke
+  ]);
+  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
+    label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
+    this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
+    1 / pixelRatio, true, label.width, textState.padding,
+    !!textState.backgroundFill, !!textState.backgroundStroke
+  ]);
+};
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg,
-          layerFilter, thisArg2) {
-    var result;
 
-    if (this.getGL().isContextLost()) {
-      return false;
+/**
+ * @private
+ * @param {number} begin Begin.
+ * @param {number} end End.
+ * @param {ol.DeclutterGroup} declutterGroup Declutter group.
+ */
+ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end, declutterGroup) {
+  var strokeState = this.textStrokeState_;
+  var textState = this.textState_;
+  var fillState = this.textFillState_;
+
+  var strokeKey = this.strokeKey_;
+  if (strokeState) {
+    if (!(strokeKey in this.strokeStates)) {
+      this.strokeStates[strokeKey] = /** @type {ol.CanvasStrokeState} */ ({
+        strokeStyle: strokeState.strokeStyle,
+        lineCap: strokeState.lineCap,
+        lineDashOffset: strokeState.lineDashOffset,
+        lineWidth: strokeState.lineWidth,
+        lineJoin: strokeState.lineJoin,
+        miterLimit: strokeState.miterLimit,
+        lineDash: strokeState.lineDash
+      });
     }
+  }
+  var textKey = this.textKey_;
+  if (!(this.textKey_ in this.textStates)) {
+    this.textStates[this.textKey_] = /** @type {ol.CanvasTextState} */ ({
+      font: textState.font,
+      textAlign: textState.textAlign || ol.render.canvas.defaultTextAlign,
+      scale: textState.scale
+    });
+  }
+  var fillKey = this.fillKey_;
+  if (fillState) {
+    if (!(fillKey in this.fillStates)) {
+      this.fillStates[fillKey] = /** @type {ol.CanvasFillState} */ ({
+        fillStyle: fillState.fillStyle
+      });
+    }
+  }
 
-    var viewState = frameState.viewState;
+  var pixelRatio = this.pixelRatio;
+  var baseline = ol.render.replay.TEXT_ALIGN[textState.textBaseline];
 
-    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;
-        }
+  var offsetY = this.textOffsetY_ * pixelRatio;
+  var text = this.text_;
+  var font = textState.font;
+  var textScale = textState.scale;
+  var strokeWidth = strokeState ? strokeState.lineWidth * textScale / 2 : 0;
+  var widths = this.widths_[font];
+  if (!widths) {
+    this.widths_[font] = widths = {};
+  }
+  this.instructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
+    begin, end, baseline, declutterGroup,
+    textState.overflow, fillKey, textState.maxAngle,
+    function(text) {
+      var width = widths[text];
+      if (!width) {
+        width = widths[text] = ol.render.canvas.measureTextWidth(font, text);
       }
-    }
-    return undefined;
-  };
+      return width * textScale * pixelRatio;
+    },
+    offsetY, strokeKey, strokeWidth * pixelRatio, text, textKey, 1
+  ]);
+  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
+    begin, end, baseline, declutterGroup,
+    textState.overflow, fillKey, textState.maxAngle,
+    function(text) {
+      var width = widths[text];
+      if (!width) {
+        width = widths[text] = ol.render.canvas.measureTextWidth(font, text);
+      }
+      return width * textScale;
+    },
+    offsetY, strokeKey, strokeWidth, text, textKey, 1 / pixelRatio
+  ]);
+};
 
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) {
-    var hasFeature = false;
+/**
+ * @inheritDoc
+ */
+ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle, declutterGroup) {
+  var textState, fillState, strokeState;
+  if (!textStyle) {
+    this.text_ = '';
+  } else {
+    this.declutterGroup_ = /** @type {ol.DeclutterGroup} */ (declutterGroup);
 
-    if (this.getGL().isContextLost()) {
-      return false;
+    var textFillStyle = textStyle.getFill();
+    if (!textFillStyle) {
+      fillState = this.textFillState_ = null;
+    } else {
+      fillState = this.textFillState_;
+      if (!fillState) {
+        fillState = this.textFillState_ = /** @type {ol.CanvasFillState} */ ({});
+      }
+      fillState.fillStyle = ol.colorlike.asColorLike(
+          textFillStyle.getColor() || ol.render.canvas.defaultFillStyle);
     }
 
-    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;
-        }
+    var textStrokeStyle = textStyle.getStroke();
+    if (!textStrokeStyle) {
+      strokeState = this.textStrokeState_ = null;
+    } else {
+      strokeState = this.textStrokeState_;
+      if (!strokeState) {
+        strokeState = this.textStrokeState_ = /** @type {ol.CanvasStrokeState} */ ({});
       }
-    }
-    return hasFeature;
-  };
+      var lineDash = textStrokeStyle.getLineDash();
+      var lineDashOffset = textStrokeStyle.getLineDashOffset();
+      var lineWidth = textStrokeStyle.getWidth();
+      var miterLimit = textStrokeStyle.getMiterLimit();
+      strokeState.lineCap = textStrokeStyle.getLineCap() || ol.render.canvas.defaultLineCap;
+      strokeState.lineDash = lineDash ? lineDash.slice() : ol.render.canvas.defaultLineDash;
+      strokeState.lineDashOffset =
+          lineDashOffset === undefined ? ol.render.canvas.defaultLineDashOffset : lineDashOffset;
+      strokeState.lineJoin = textStrokeStyle.getLineJoin() || ol.render.canvas.defaultLineJoin;
+      strokeState.lineWidth =
+          lineWidth === undefined ? ol.render.canvas.defaultLineWidth : lineWidth;
+      strokeState.miterLimit =
+          miterLimit === undefined ? ol.render.canvas.defaultMiterLimit : miterLimit;
+      strokeState.strokeStyle = ol.colorlike.asColorLike(
+          textStrokeStyle.getColor() || ol.render.canvas.defaultStrokeStyle);
+    }
+
+    textState = this.textState_;
+    var font = textStyle.getFont() || ol.render.canvas.defaultFont;
+    ol.render.canvas.checkFont(font);
+    var textScale = textStyle.getScale();
+    textState.overflow = textStyle.getOverflow();
+    textState.font = font;
+    textState.maxAngle = textStyle.getMaxAngle();
+    textState.placement = textStyle.getPlacement();
+    textState.textAlign = textStyle.getTextAlign();
+    textState.textBaseline = textStyle.getTextBaseline() || ol.render.canvas.defaultTextBaseline;
+    textState.backgroundFill = textStyle.getBackgroundFill();
+    textState.backgroundStroke = textStyle.getBackgroundStroke();
+    textState.padding = textStyle.getPadding() || ol.render.canvas.defaultPadding;
+    textState.scale = textScale === undefined ? 1 : textScale;
 
+    var textOffsetX = textStyle.getOffsetX();
+    var textOffsetY = textStyle.getOffsetY();
+    var textRotateWithView = textStyle.getRotateWithView();
+    var textRotation = textStyle.getRotation();
+    this.text_ = textStyle.getText() || '';
+    this.textOffsetX_ = textOffsetX === undefined ? 0 : textOffsetX;
+    this.textOffsetY_ = textOffsetY === undefined ? 0 : textOffsetY;
+    this.textRotateWithView_ = textRotateWithView === undefined ? false : textRotateWithView;
+    this.textRotation_ = textRotation === undefined ? 0 : textRotation;
+
+    this.strokeKey_ = strokeState ?
+      (typeof strokeState.strokeStyle == 'string' ? strokeState.strokeStyle : ol.getUid(strokeState.strokeStyle)) +
+      strokeState.lineCap + strokeState.lineDashOffset + '|' + strokeState.lineWidth +
+      strokeState.lineJoin + strokeState.miterLimit + '[' + strokeState.lineDash.join() + ']' :
+      '';
+    this.textKey_ = textState.font + textState.scale + (textState.textAlign || '?');
+    this.fillKey_ = fillState ?
+      (typeof fillState.fillStyle == 'string' ? fillState.fillStyle : ('|' + ol.getUid(fillState.fillStyle))) :
+      '';
+  }
+};
 
-  /**
-   * @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 = /** @type {ol.renderer.webgl.Layer} */ (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.render.canvas.ReplayGroup');
 
 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.array');
 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.geom.flat.transform');
 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.render.ReplayGroup');
+goog.require('ol.render.ReplayType');
+goog.require('ol.render.canvas.Replay');
+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');
 
 
 /**
- * @const
- * @type {string}
- */
-ol.OL_URL = 'https://openlayers.org/';
-
-
-/**
- * @const
- * @type {string}
- */
-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';
-
-
-/**
- * @type {Array.<ol.renderer.Type>}
- * @const
- */
-ol.DEFAULT_RENDERER_TYPES = [
-  ol.renderer.Type.CANVAS,
-  ol.renderer.Type.WEBGL
-];
-
-
-/**
- * @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.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
+ * @extends {ol.render.ReplayGroup}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {boolean} overlaps The replay group can have overlapping geometries.
+ * @param {?} declutterTree Declutter tree
+ * for declutter processing in postrender.
+ * @param {number=} opt_renderBuffer Optional rendering buffer.
+ * @struct
  */
-ol.Map = function(options) {
-
-  ol.Object.call(this);
-
-  var optionsInternal = ol.Map.createOptionsInternal(options);
+ol.render.canvas.ReplayGroup = function(
+    tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree, opt_renderBuffer) {
+  ol.render.ReplayGroup.call(this);
 
   /**
-   * @type {boolean}
+   * Declutter tree.
    * @private
    */
-  this.loadTilesWhileAnimating_ =
-      options.loadTilesWhileAnimating !== undefined ?
-          options.loadTilesWhileAnimating : false;
+  this.declutterTree_ = declutterTree;
 
   /**
-   * @type {boolean}
+   * @type {ol.DeclutterGroup}
    * @private
    */
-  this.loadTilesWhileInteracting_ =
-      options.loadTilesWhileInteracting !== undefined ?
-          options.loadTilesWhileInteracting : false;
+  this.declutterGroup_ = null;
 
   /**
    * @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 {number|undefined}
-   */
-  this.animationDelayKey_;
+  this.tolerance_ = tolerance;
 
   /**
    * @private
+   * @type {ol.Extent}
    */
-  this.animationDelay_ = function() {
-    this.animationDelayKey_ = undefined;
-    this.renderFrame_.call(this, Date.now());
-  }.bind(this);
+  this.maxExtent_ = maxExtent;
 
   /**
    * @private
-   * @type {ol.Transform}
+   * @type {boolean}
    */
-  this.coordinateToPixelTransform_ = ol.transform.create();
+  this.overlaps_ = overlaps;
 
   /**
    * @private
-   * @type {ol.Transform}
+   * @type {number}
    */
-  this.pixelToCoordinateTransform_ = ol.transform.create();
+  this.pixelRatio_ = pixelRatio;
 
   /**
    * @private
    * @type {number}
    */
-  this.frameIndex_ = 0;
+  this.resolution_ = resolution;
 
   /**
    * @private
-   * @type {?olx.FrameState}
+   * @type {number|undefined}
    */
-  this.frameState_ = null;
+  this.renderBuffer_ = opt_renderBuffer;
 
   /**
-   * The extent at the previous 'moveend' event.
    * @private
-   * @type {ol.Extent}
+   * @type {!Object.<string,
+   *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
    */
-  this.previousExtent_ = null;
+  this.replaysByZIndex_ = {};
 
   /**
    * @private
-   * @type {?ol.EventsKey}
+   * @type {CanvasRenderingContext2D}
    */
-  this.viewPropertyListenerKey_ = null;
+  this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1);
 
   /**
    * @private
-   * @type {?ol.EventsKey}
+   * @type {ol.Transform}
    */
-  this.viewChangeListenerKey_ = null;
+  this.hitDetectionTransform_ = ol.transform.create();
+};
+ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);
 
-  /**
-   * @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';
+/**
+ * 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.render.canvas.ReplayGroup.circleArrayCache_ = {
+  0: [[true]]
+};
 
-  /**
-   * @private
-   * @type {!Element}
-   */
-  this.overlayContainer_ = document.createElement('DIV');
-  this.overlayContainer_.className = 'ol-overlaycontainer';
-  this.viewport_.appendChild(this.overlayContainer_);
 
-  /**
-   * @private
-   * @type {!Element}
-   */
-  this.overlayContainerStopEvent_ = document.createElement('DIV');
-  this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
-  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 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.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;
+    }
   }
-  this.viewport_.appendChild(this.overlayContainerStopEvent_);
+};
 
-  /**
-   * @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);
+
+/**
+ * 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.render.canvas.ReplayGroup.getCircleArray_ = function(radius) {
+  if (ol.render.canvas.ReplayGroup.circleArrayCache_[radius] !== undefined) {
+    return ol.render.canvas.ReplayGroup.circleArrayCache_[radius];
   }
 
-  /**
-   * @private
-   * @type {Element|Document}
-   */
-  this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
+  var arraySize = radius * 2 + 1;
+  var arr = new Array(arraySize);
+  for (var i = 0; i < arraySize; i++) {
+    arr[i] = new Array(arraySize);
+  }
 
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.keyHandlerKeys_ = null;
+  var x = radius;
+  var y = 0;
+  var error = 0;
 
-  ol.events.listen(this.viewport_, ol.events.EventType.WHEEL,
-      this.handleBrowserEvent, this);
-  ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL,
-      this.handleBrowserEvent, this);
+  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);
 
-  /**
-   * @type {ol.Collection.<ol.control.Control>}
-   * @private
-   */
-  this.controls_ = optionsInternal.controls;
+    y++;
+    error += 1 + 2 * y;
+    if (2 * (error - x) + 1 > 0) {
+      x -= 1;
+      error += 1 - 2 * x;
+    }
+  }
 
-  /**
-   * @type {ol.Collection.<ol.interaction.Interaction>}
-   * @private
-   */
-  this.interactions_ = optionsInternal.interactions;
+  ol.render.canvas.ReplayGroup.circleArrayCache_[radius] = arr;
+  return arr;
+};
 
-  /**
-   * @type {ol.Collection.<ol.Overlay>}
-   * @private
-   */
-  this.overlays_ = optionsInternal.overlays;
 
-  /**
-   * A lookup of overlays by id.
-   * @private
-   * @type {Object.<string, ol.Overlay>}
-   */
-  this.overlayIdIndex_ = {};
+/**
+ * @param {!Object.<string, Array.<*>>} declutterReplays Declutter replays.
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {number} rotation Rotation.
+ */
+ol.render.canvas.ReplayGroup.replayDeclutter = function(declutterReplays, context, rotation) {
+  var zs = Object.keys(declutterReplays).map(Number).sort(ol.array.numberSafeCompareFunction);
+  var skippedFeatureUids = {};
+  for (var z = 0, zz = zs.length; z < zz; ++z) {
+    var replayData = declutterReplays[zs[z].toString()];
+    for (var i = 0, ii = replayData.length; i < ii;) {
+      var replay = replayData[i++];
+      var transform = replayData[i++];
+      replay.replay(context, transform, rotation, skippedFeatureUids);
+    }
+  }
+};
 
-  /**
-   * @type {ol.renderer.Map}
-   * @private
-   */
-  this.renderer_ = new /** @type {Function} */ (optionsInternal.rendererConstructor)(this.viewport_, this);
 
-  /**
-   * @type {function(Event)|undefined}
-   * @private
-   */
-  this.handleResize_;
+/**
+ * @param {boolean} group Group with previous replay.
+ * @return {ol.DeclutterGroup} Declutter instruction group.
+ */
+ol.render.canvas.ReplayGroup.prototype.addDeclutter = function(group) {
+  var declutter = null;
+  if (this.declutterTree_) {
+    if (group) {
+      declutter = this.declutterGroup_;
+      /** @type {number} */ (declutter[4])++;
+    } else {
+      declutter = this.declutterGroup_ = ol.extent.createEmpty();
+      declutter.push(1);
+    }
+  }
+  return declutter;
+};
 
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.focus_ = null;
 
-  /**
-   * @private
-   * @type {Array.<ol.PostRenderFunction>}
-   */
-  this.postRenderFunctions_ = [];
+/**
+ * @param {CanvasRenderingContext2D} context Context.
+ * @param {ol.Transform} transform Transform.
+ */
+ol.render.canvas.ReplayGroup.prototype.clip = function(context, transform) {
+  var flatClipCoords = this.getClipCoords(transform);
+  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();
+};
 
-  /**
-   * @private
-   * @type {ol.TileQueue}
-   */
-  this.tileQueue_ = new ol.TileQueue(
-      this.getTilePriority.bind(this),
-      this.handleTileChange_.bind(this));
 
-  /**
-   * Uids of features to skip at rendering time.
-   * @type {Object.<string, boolean>}
-   * @private
-   */
-  this.skippedFeatureUids_ = {};
+/**
+ * @param {Array.<ol.render.ReplayType>} replays Replays.
+ * @return {boolean} Has replays of the provided types.
+ */
+ol.render.canvas.ReplayGroup.prototype.hasReplays = function(replays) {
+  for (var zIndex in this.replaysByZIndex_) {
+    var candidates = this.replaysByZIndex_[zIndex];
+    for (var i = 0, ii = replays.length; i < ii; ++i) {
+      if (replays[i] in candidates) {
+        return true;
+      }
+    }
+  }
+  return false;
+};
 
-  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);
 
-  // setProperties will trigger the rendering of the map if the map
-  // is "defined" already.
-  this.setProperties(optionsInternal.values);
+/**
+ * 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();
+    }
+  }
+};
 
-  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);
+/**
+ * @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.
+ * @param {Object.<string, ol.DeclutterGroup>} declutterReplays Declutter
+ *     replays.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
+    coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback, declutterReplays) {
 
-  ol.events.listen(this.controls_, ol.CollectionEventType.REMOVE,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, this);
+  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_;
 
-  this.interactions_.forEach(
-      /**
-       * @param {ol.interaction.Interaction} interaction Interaction.
-       * @this {ol.Map}
-       */
-      function(interaction) {
-        interaction.setMap(this);
-      }, this);
-
-  ol.events.listen(this.interactions_, ol.CollectionEventType.ADD,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(this);
-      }, this);
+  if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) {
+    context.canvas.width = contextSize;
+    context.canvas.height = contextSize;
+  } else {
+    context.clearRect(0, 0, contextSize, contextSize);
+  }
 
-  ol.events.listen(this.interactions_, ol.CollectionEventType.REMOVE,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, this);
+  /**
+   * @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);
+  }
 
-  this.overlays_.forEach(this.addOverlayInternal_, this);
+  var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance);
+  var declutteredFeatures;
+  if (this.declutterTree_) {
+    declutteredFeatures = this.declutterTree_.all().map(function(entry) {
+      return entry.value;
+    });
+  }
 
-  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);
+  var replayType;
 
-  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()];
+  /**
+   * @param {ol.Feature|ol.render.Feature} feature Feature.
+   * @return {?} Callback result.
+   */
+  function featureCallback(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;
+            if (!(declutteredFeatures && (replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) ||
+                declutteredFeatures.indexOf(feature) !== -1) {
+              result = callback(feature);
+            }
+            if (result) {
+              return result;
+            } else {
+              context.clearRect(0, 0, contextSize, contextSize);
+              return undefined;
+            }
+          }
         }
-        event.element.setMap(null);
-      }, this);
+      }
+    }
+  }
+
+  /** @type {Array.<number>} */
+  var zs = Object.keys(this.replaysByZIndex_).map(Number);
+  zs.sort(ol.array.numberSafeCompareFunction);
 
+  var i, j, replays, replay, result;
+  for (i = zs.length - 1; i >= 0; --i) {
+    var zIndexKey = zs[i].toString();
+    replays = this.replaysByZIndex_[zIndexKey];
+    for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) {
+      replayType = ol.render.replay.ORDER[j];
+      replay = replays[replayType];
+      if (replay !== undefined) {
+        if (declutterReplays &&
+            (replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) {
+          var declutter = declutterReplays[zIndexKey];
+          if (!declutter) {
+            declutterReplays[zIndexKey] = [replay, transform.slice(0)];
+          } else {
+            declutter.push(replay, transform.slice(0));
+          }
+        } else {
+          result = replay.replayHitDetection(context, transform, rotation,
+              skippedFeaturesHash, featureCallback, hitExtent);
+          if (result) {
+            return result;
+          }
+        }
+      }
+    }
+  }
+  return undefined;
 };
-ol.inherits(ol.Map, ol.Object);
 
 
 /**
- * Add the given control to the map.
- * @param {ol.control.Control} control Control.
- * @api
+ * @param {ol.Transform} transform Transform.
+ * @return {Array.<number>} Clip coordinates.
  */
-ol.Map.prototype.addControl = function(control) {
-  this.getControls().push(control);
+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;
 };
 
 
 /**
- * Add the given interaction to the map.
- * @param {ol.interaction.Interaction} interaction Interaction to add.
- * @api
+ * @inheritDoc
  */
-ol.Map.prototype.addInteraction = function(interaction) {
-  this.getInteractions().push(interaction);
+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.pixelRatio_, this.overlaps_, this.declutterTree_);
+    replays[replayType] = replay;
+  }
+  return replay;
 };
 
 
 /**
- * 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
+ * @return {Object.<string, Object.<ol.render.ReplayType, ol.render.canvas.Replay>>} Replays.
  */
-ol.Map.prototype.addLayer = function(layer) {
-  var layers = this.getLayerGroup().getLayers();
-  layers.push(layer);
+ol.render.canvas.ReplayGroup.prototype.getReplays = function() {
+  return this.replaysByZIndex_;
 };
 
 
 /**
- * Add the given overlay to the map.
- * @param {ol.Overlay} overlay Overlay.
- * @api
+ * @inheritDoc
  */
-ol.Map.prototype.addOverlay = function(overlay) {
-  this.getOverlays().push(overlay);
+ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
+  return ol.obj.isEmpty(this.replaysByZIndex_);
 };
 
 
 /**
- * This deals with map's overlay collection changes.
- * @param {ol.Overlay} overlay Overlay.
- * @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 {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types
+ *     to replay. Default is {@link ol.render.replay.ORDER}
+ * @param {Object.<string, ol.DeclutterGroup>=} opt_declutterReplays Declutter
+ *     replays.
  */
-ol.Map.prototype.addOverlayInternal_ = function(overlay) {
-  var id = overlay.getId();
-  if (id !== undefined) {
-    this.overlayIdIndex_[id.toString()] = overlay;
-  }
-  overlay.setMap(this);
-};
+ol.render.canvas.ReplayGroup.prototype.replay = function(context,
+    transform, viewRotation, skippedFeaturesHash, opt_replayTypes, opt_declutterReplays) {
 
+  /** @type {Array.<number>} */
+  var zs = Object.keys(this.replaysByZIndex_).map(Number);
+  zs.sort(ol.array.numberSafeCompareFunction);
 
-/**
- *
- * @inheritDoc
- */
-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;
+  // setup clipping so that the parts of over-simplified geometries are not
+  // visible outside the current extent when panning
+  context.save();
+  this.clip(context, transform);
+
+  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) {
+    var zIndexKey = zs[i].toString();
+    replays = this.replaysByZIndex_[zIndexKey];
+    for (j = 0, jj = replayTypes.length; j < jj; ++j) {
+      var replayType = replayTypes[j];
+      replay = replays[replayType];
+      if (replay !== undefined) {
+        if (opt_declutterReplays &&
+            (replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) {
+          var declutter = opt_declutterReplays[zIndexKey];
+          if (!declutter) {
+            opt_declutterReplays[zIndexKey] = [replay, transform.slice(0)];
+          } else {
+            declutter.push(replay, transform.slice(0));
+          }
+        } else {
+          replay.replay(context, transform, viewRotation, skippedFeaturesHash);
+        }
+      }
+    }
   }
-  this.setTarget(null);
-  ol.Object.prototype.disposeInternal.call(this);
+
+  context.restore();
 };
 
 
 /**
- * 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
+ * @const
+ * @private
+ * @type {Object.<ol.render.ReplayType,
+ *                function(new: ol.render.canvas.Replay, number, ol.Extent,
+ *                number, number, boolean, Array.<ol.DeclutterGroup>)>}
  */
-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);
+ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
+  'Circle': ol.render.canvas.PolygonReplay,
+  'Default': ol.render.canvas.Replay,
+  'Image': ol.render.canvas.ImageReplay,
+  'LineString': ol.render.canvas.LineStringReplay,
+  'Polygon': ol.render.canvas.PolygonReplay,
+  'Text': ol.render.canvas.TextReplay
 };
 
+goog.provide('ol.renderer.vector');
 
-/**
- * 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
- */
-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);
-};
+goog.require('ol');
+goog.require('ol.ImageState');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.render.ReplayType');
 
 
 /**
- * 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
+ * @param {ol.Feature|ol.render.Feature} feature1 Feature 1.
+ * @param {ol.Feature|ol.render.Feature} feature2 Feature 2.
+ * @return {number} Order.
  */
-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);
+ol.renderer.vector.defaultOrder = function(feature1, feature2) {
+  return ol.getUid(feature1) - ol.getUid(feature2);
 };
 
 
 /**
- * Returns the coordinate in view projection for a browser event.
- * @param {Event} event Event.
- * @return {ol.Coordinate} Coordinate.
- * @api
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {number} Squared pixel tolerance.
  */
-ol.Map.prototype.getEventCoordinate = function(event) {
-  return this.getCoordinateFromPixel(this.getEventPixel(event));
+ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) {
+  var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
+  return tolerance * tolerance;
 };
 
 
 /**
- * Returns the map pixel position for a browser event relative to the viewport.
- * @param {Event} event Event.
- * @return {ol.Pixel} Pixel.
- * @api
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @return {number} Pixel tolerance.
  */
-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
-  ];
+ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
+  return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
 };
 
 
 /**
- * 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
+ * @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.Map.prototype.getTarget = function() {
-  return /** @type {Element|string|undefined} */ (
-      this.get(ol.MapProperty.TARGET));
+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, replayGroup.addDeclutter(false));
+    textReplay.drawText(geometry, feature);
+  }
 };
 
 
 /**
- * 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
+ * @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.Map.prototype.getTargetElement = function() {
-  var target = this.getTarget();
-  if (target !== undefined) {
-    return typeof target === 'string' ?
-      document.getElementById(target) :
-      target;
-  } else {
-    return null;
+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;
+    }
   }
+  ol.renderer.vector.renderFeature_(replayGroup, feature, style,
+      squaredTolerance);
+
+  return loading;
 };
 
 
 /**
- * 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
+ * @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.Map.prototype.getCoordinateFromPixel = function(pixel) {
-  var frameState = this.frameState_;
-  if (!frameState) {
-    return null;
+ol.renderer.vector.renderFeature_ = function(
+    replayGroup, feature, style, squaredTolerance) {
+  var geometry = style.getGeometryFunction()(feature);
+  if (!geometry) {
+    return;
+  }
+  var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
+  var renderer = style.getRenderer();
+  if (renderer) {
+    ol.renderer.vector.renderGeometry_(replayGroup, simplifiedGeometry, style, feature);
   } else {
-    return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice());
+    var geometryRenderer =
+        ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
+    geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
   }
 };
 
 
 /**
- * Get the map controls. Modifying this collection changes the controls
- * associated with the map.
- * @return {ol.Collection.<ol.control.Control>} Controls.
- * @api
+ * @param {ol.render.ReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {ol.style.Style} style Style.
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
  */
-ol.Map.prototype.getControls = function() {
-  return this.controls_;
+ol.renderer.vector.renderGeometry_ = function(replayGroup, geometry, style, feature) {
+  if (geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
+    var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
+    for (var i = 0, ii = geometries.length; i < ii; ++i) {
+      ol.renderer.vector.renderGeometry_(replayGroup, geometries[i], style, feature);
+    }
+    return;
+  }
+  var replay = replayGroup.getReplay(style.getZIndex(), ol.render.ReplayType.DEFAULT);
+  replay.drawCustom(/** @type {ol.geom.SimpleGeometry} */ (geometry), feature, style.getRenderer());
 };
 
 
 /**
- * Get the map overlays. Modifying this collection changes the overlays
- * associated with the map.
- * @return {ol.Collection.<ol.Overlay>} Overlays.
- * @api
+ * @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.Map.prototype.getOverlays = function() {
-  return this.overlays_;
+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);
+  }
 };
 
 
 /**
- * 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
+ * @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.Map.prototype.getOverlayById = function(id) {
-  var overlay = this.overlayIdIndex_[id.toString()];
-  return overlay !== undefined ? overlay : null;
+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, replayGroup.addDeclutter(false));
+    textReplay.drawText(geometry, feature);
+  }
 };
 
 
 /**
- * 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
+ * @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.Map.prototype.getInteractions = function() {
-  return this.interactions_;
+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, replayGroup.addDeclutter(false));
+    textReplay.drawText(geometry, feature);
+  }
 };
 
 
 /**
- * Get the layergroup associated with this map.
- * @return {ol.layer.Group} A layer group containing the layers in this map.
- * @observable
- * @api
+ * @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.Map.prototype.getLayerGroup = function() {
-  return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
+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, replayGroup.addDeclutter(false));
+    textReplay.drawText(geometry, feature);
+  }
 };
 
 
 /**
- * Get the collection of layers associated with this map.
- * @return {!ol.Collection.<ol.layer.Base>} Layers.
- * @api
- */
-ol.Map.prototype.getLayers = function() {
-  var layers = this.getLayerGroup().getLayers();
-  return layers;
+ * @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.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, replayGroup.addDeclutter(false));
+    imageReplay.drawPoint(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (textStyle) {
+    var textReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.TEXT);
+    textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
+    textReplay.drawText(geometry, feature);
+  }
 };
 
 
 /**
- * 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
+ * @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.Map.prototype.getPixelFromCoordinate = function(coordinate) {
-  var frameState = this.frameState_;
-  if (!frameState) {
-    return null;
-  } else {
-    return ol.transform.apply(frameState.coordinateToPixelTransform,
-        coordinate.slice(0, 2));
+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, replayGroup.addDeclutter(false));
+    imageReplay.drawMultiPoint(geometry, feature);
+  }
+  var textStyle = style.getText();
+  if (textStyle) {
+    var textReplay = replayGroup.getReplay(
+        style.getZIndex(), ol.render.ReplayType.TEXT);
+    textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
+    textReplay.drawText(geometry, feature);
   }
 };
 
 
 /**
- * Get the map renderer.
- * @return {ol.renderer.Map} Renderer
+ * @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.Map.prototype.getRenderer = function() {
-  return this.renderer_;
+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, replayGroup.addDeclutter(false));
+    textReplay.drawText(geometry, feature);
+  }
 };
 
 
 /**
- * Get the size of this map.
- * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
- * @observable
- * @api
+ * @const
+ * @private
+ * @type {Object.<ol.geom.GeometryType,
+ *                function(ol.render.ReplayGroup, ol.geom.Geometry,
+ *                         ol.style.Style, Object)>}
  */
-ol.Map.prototype.getSize = function() {
-  return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
+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');
 
-/**
- * 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
- */
-ol.Map.prototype.getView = function() {
-  return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
-};
+goog.require('ol');
+goog.require('ol.LayerType');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.ext.rbush');
+goog.require('ol.extent');
+goog.require('ol.render.EventType');
+goog.require('ol.render.canvas');
+goog.require('ol.render.canvas.ReplayGroup');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.renderer.vector');
 
 
 /**
- * Get the element that serves as the map viewport.
- * @return {Element} Viewport.
+ * @constructor
+ * @extends {ol.renderer.canvas.Layer}
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
  * @api
  */
-ol.Map.prototype.getViewport = function() {
-  return this.viewport_;
-};
+ol.renderer.canvas.VectorLayer = function(vectorLayer) {
 
+  ol.renderer.canvas.Layer.call(this, vectorLayer);
 
-/**
- * Get the element that serves as the container for overlays.  Elements added to
- * this container will let mousedown and touchstart events through to the map,
- * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent}
- * events.
- * @return {!Element} The map's overlay container.
- */
-ol.Map.prototype.getOverlayContainer = function() {
-  return this.overlayContainer_;
-};
+  /**
+   * Declutter tree.
+   * @private
+   */
+  this.declutterTree_ = vectorLayer.getDeclutter() ?
+    ol.ext.rbush(9) : null;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.dirty_ = false;
 
-/**
- * Get the element that serves as a container for overlays that don't allow
- * event propagation. Elements added to this container won't let mousedown and
- * touchstart events through to the map, so clicks and gestures on an overlay
- * don't trigger any {@link ol.MapBrowserEvent}.
- * @return {!Element} The map's overlay container that stops events.
- */
-ol.Map.prototype.getOverlayContainerStopEvent = function() {
-  return this.overlayContainerStopEvent_;
-};
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedRevision_ = -1;
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedResolution_ = NaN;
 
-/**
- * @param {ol.Tile} tile Tile.
- * @param {string} tileSourceKey Tile source key.
- * @param {ol.Coordinate} tileCenter Tile center.
- * @param {number} tileResolution Tile resolution.
- * @return {number} Tile priority.
- */
-ol.Map.prototype.getTilePriority = function(tile, tileSourceKey, tileCenter, tileResolution) {
-  // Filter out tiles at higher zoom levels than the current zoom level, or that
-  // are outside the visible extent.
-  var frameState = this.frameState_;
-  if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
-    return ol.structs.PriorityQueue.DROP;
-  }
-  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;
-};
+  /**
+   * @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;
+
+  /**
+   * A new replay group had to be created by `prepareFrame()`
+   * @type {boolean}
+   */
+  this.replayGroupChanged = true;
+
+  /**
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context = ol.dom.createCanvasContext2D();
+
+  ol.events.listen(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
 
-/**
- * @param {Event} browserEvent Browser event.
- * @param {string=} opt_type Type.
- */
-ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
-  var type = opt_type || browserEvent.type;
-  var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent);
-  this.handleMapBrowserEvent(mapBrowserEvent);
 };
+ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
 
 
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @param {ol.layer.Layer} layer The candidate layer.
+ * @return {boolean} The renderer can render the layer.
  */
-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 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;
-      }
-    }
-  }
+ol.renderer.canvas.VectorLayer['handles'] = function(type, layer) {
+  return type === ol.renderer.Type.CANVAS && layer.getType() === ol.LayerType.VECTOR;
 };
 
 
 /**
- * @protected
+ * Create a layer renderer.
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @param {ol.layer.Layer} layer The layer to be rendererd.
+ * @return {ol.renderer.canvas.VectorLayer} The layer renderer.
  */
-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;
-    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;
+ol.renderer.canvas.VectorLayer['create'] = function(mapRenderer, layer) {
+  return new ol.renderer.canvas.VectorLayer(/** @type {ol.layer.Vector} */ (layer));
 };
 
 
 /**
- * @private
+ * @inheritDoc
  */
-ol.Map.prototype.handleSizeChanged_ = function() {
-  this.render();
+ol.renderer.canvas.VectorLayer.prototype.disposeInternal = function() {
+  ol.events.unlisten(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
+  ol.renderer.canvas.Layer.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * @private
+ * @inheritDoc
  */
-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.
+ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) {
 
-  var targetElement;
-  if (this.getTarget()) {
-    targetElement = this.getTargetElement();
+  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()) {
+    if (this.declutterTree_) {
+      this.declutterTree_.clear();
+    }
+    var layer = /** @type {ol.layer.Vector} */ (this.getLayer());
+    var drawOffsetX = 0;
+    var drawOffsetY = 0;
+    var replayContext;
+    var transparentLayer = layerState.opacity !== 1;
+    var hasRenderListeners = layer.hasListener(ol.render.EventType.RENDER);
+    if (transparentLayer || hasRenderListeners) {
+      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;
+    }
 
-  if (this.keyHandlerKeys_) {
-    for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
-      ol.events.unlistenByKey(this.keyHandlerKeys_[i]);
+    var alpha = replayContext.globalAlpha;
+    if (!transparentLayer) {
+      // 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
+      replayContext.globalAlpha = layerState.opacity;
     }
-    this.keyHandlerKeys_ = null;
-  }
 
-  if (!targetElement) {
-    ol.dom.removeNode(this.viewport_);
-    if (this.handleResize_ !== undefined) {
-      window.removeEventListener(ol.events.EventType.RESIZE,
-          this.handleResize_, false);
-      this.handleResize_ = undefined;
+    if (replayContext != context) {
+      replayContext.translate(drawOffsetX, drawOffsetY);
     }
-  } else {
-    targetElement.appendChild(this.viewport_);
 
-    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)
-    ];
+    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, 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, 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, transform, rotation, skippedFeatureUids);
+        startX -= worldWidth;
+      }
+      // restore original transform for render and compose events
+      transform = this.getTransform(frameState, 0);
+    }
+    ol.render.canvas.rotateAtOffset(replayContext, rotation,
+        width / 2, height / 2);
 
-    if (!this.handleResize_) {
-      this.handleResize_ = this.updateSize.bind(this);
-      window.addEventListener(ol.events.EventType.RESIZE,
-          this.handleResize_, false);
+    if (replayContext != context) {
+      if (hasRenderListeners) {
+        this.dispatchRenderEvent(replayContext, frameState, transform);
+      }
+      if (transparentLayer) {
+        var mainContextAlpha = context.globalAlpha;
+        context.globalAlpha = layerState.opacity;
+        context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
+        context.globalAlpha = mainContextAlpha;
+      } else {
+        context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
+      }
+      replayContext.translate(-drawOffsetX, -drawOffsetY);
     }
-  }
 
-  this.updateSize();
-  // updateSize calls setSize, so no need to call this.render
-  // ourselves here.
-};
+    if (!transparentLayer) {
+      replayContext.globalAlpha = alpha;
+    }
+  }
 
+  if (clipped) {
+    context.restore();
+  }
+  this.postCompose(context, frameState, layerState, transform);
 
-/**
- * @private
- */
-ol.Map.prototype.handleTileChange_ = function() {
-  this.render();
 };
 
 
 /**
- * @private
+ * @inheritDoc
  */
-ol.Map.prototype.handleViewPropertyChanged_ = function() {
-  this.render();
+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 = /** @type {ol.layer.Vector} */ (this.getLayer());
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+    var result = 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);
+          }
+        }, null);
+    return result;
+  }
 };
 
 
 /**
- * @private
+ * @param {ol.events.Event} event Event.
  */
-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);
+ol.renderer.canvas.VectorLayer.prototype.handleFontsChanged_ = function(event) {
+  var layer = this.getLayer();
+  if (layer.getVisible() && this.replayGroup_) {
+    layer.changed();
   }
-  this.render();
 };
 
 
 /**
+ * Handle changes in image style state.
+ * @param {ol.events.Event} event Image style change event.
  * @private
  */
-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();
+ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
+  this.renderIfReadyAndVisible();
 };
 
 
 /**
- * @return {boolean} Is rendered.
+ * @inheritDoc
  */
-ol.Map.prototype.isRendered = function() {
-  return !!this.frameState_;
-};
+ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) {
 
+  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+  var vectorSource = vectorLayer.getSource();
 
-/**
- * Requests an immediate render in a synchronous manner.
- * @api
- */
-ol.Map.prototype.renderSync = function() {
-  if (this.animationDelayKey_) {
-    cancelAnimationFrame(this.animationDelayKey_);
-  }
-  this.animationDelay_();
-};
+  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();
 
-/**
- * Request a map rendering (at the next animation frame).
- * @api
- */
-ol.Map.prototype.render = function() {
-  if (this.animationDelayKey_ === undefined) {
-    this.animationDelayKey_ = requestAnimationFrame(
-        this.animationDelay_);
+  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;
+  }
 
-/**
- * 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
- */
-ol.Map.prototype.removeControl = function(control) {
-  return this.getControls().remove(control);
-};
-
+  var extent = ol.extent.buffer(frameStateExtent,
+      vectorLayerRenderBuffer * resolution);
+  var projectionExtent = viewState.projection.getExtent();
 
-/**
- * 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
- */
-ol.Map.prototype.removeInteraction = function(interaction) {
-  return this.getInteractions().remove(interaction);
+  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)) {
+    this.replayGroupChanged = false;
+    return true;
+  }
+
+  this.replayGroup_ = null;
+
+  this.dirty_ = false;
+
+  var replayGroup = new ol.render.canvas.ReplayGroup(
+      ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, resolution,
+      pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, 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 (styles) {
+      var dirty = this.renderFeature(
+          feature, resolution, pixelRatio, styles, replayGroup);
+      this.dirty_ = this.dirty_ || dirty;
+    }
+  }.bind(this);
+  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);
+    for (var i = 0, ii = features.length; i < ii; ++i) {
+      renderFeature(features[i]);
+    }
+  } else {
+    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
+  }
+  replayGroup.finish();
+
+  this.renderedResolution_ = resolution;
+  this.renderedRevision_ = vectorLayerRevision;
+  this.renderedRenderOrder_ = vectorLayerRenderOrder;
+  this.renderedExtent_ = extent;
+  this.replayGroup_ = replayGroup;
+
+  this.replayGroupChanged = true;
+  return true;
 };
 
 
 /**
- * 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
+ * @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.Map.prototype.removeLayer = function(layer) {
-  var layers = this.getLayerGroup().getLayers();
-  return layers.remove(layer);
+ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
+  if (!styles) {
+    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],
+          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);
+  }
+  return loading;
 };
 
+goog.provide('ol.layer.VectorTileRenderType');
 
 /**
- * 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).
+ * @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.Map.prototype.removeOverlay = function(overlay) {
-  return this.getOverlays().remove(overlay);
+ol.layer.VectorTileRenderType = {
+  IMAGE: 'image',
+  HYBRID: 'hybrid',
+  VECTOR: 'vector'
 };
 
+goog.provide('ol.renderer.canvas.VectorTileLayer');
+
+goog.require('ol');
+goog.require('ol.LayerType');
+goog.require('ol.TileState');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.ext.rbush');
+goog.require('ol.extent');
+goog.require('ol.layer.VectorTileRenderType');
+goog.require('ol.proj');
+goog.require('ol.proj.Units');
+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.Type');
+goog.require('ol.renderer.canvas.TileLayer');
+goog.require('ol.renderer.vector');
+goog.require('ol.transform');
+
 
 /**
- * @param {number} time Time.
- * @private
+ * @constructor
+ * @extends {ol.renderer.canvas.TileLayer}
+ * @param {ol.layer.VectorTile} layer VectorTile layer.
+ * @api
  */
-ol.Map.prototype.renderFrame_ = function(time) {
-  var i, ii, viewState;
-
-  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];
-    }
-    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: {}
-    });
-  }
+ol.renderer.canvas.VectorTileLayer = function(layer) {
 
-  if (frameState) {
-    frameState.extent = ol.extent.getForViewAndSize(viewState.center,
-        viewState.resolution, viewState.rotation, frameState.size, extent);
-  }
+  /**
+   * @type {CanvasRenderingContext2D}
+   */
+  this.context = null;
 
-  this.frameState_ = frameState;
-  this.renderer_.renderFrame(frameState);
+  ol.renderer.canvas.TileLayer.call(this, layer);
 
-  if (frameState) {
-    if (frameState.animate) {
-      this.render();
-    }
-    Array.prototype.push.apply(
-        this.postRenderFunctions_, frameState.postRenderFunctions);
+  /**
+   * Declutter tree.
+   * @private
+     */
+  this.declutterTree_ = layer.getDeclutter() ? ol.ext.rbush(9) : null;
 
-    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_);
-      }
-    }
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.dirty_ = false;
 
-    var idle = this.previousExtent_ &&
-        !frameState.viewHints[ol.ViewHint.ANIMATING] &&
-        !frameState.viewHints[ol.ViewHint.INTERACTING] &&
-        !ol.extent.equals(frameState.extent, this.previousExtent_);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderedLayerRevision_;
 
-    if (idle) {
-      this.dispatchEvent(
-          new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
-      ol.extent.clone(frameState.extent, this.previousExtent_);
-    }
-  }
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.tmpTransform_ = ol.transform.create();
 
-  this.dispatchEvent(
-      new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));
+  // Use lower resolution for pure vector rendering. Closest resolution otherwise.
+  this.zDirection =
+      layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0;
 
-  setTimeout(this.handlePostRender.bind(this), 0);
+  ol.events.listen(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
 
 };
+ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
 
 
 /**
- * Sets the layergroup of this map.
- * @param {ol.layer.Group} layerGroup A layer group containing the layers in
- *     this map.
- * @observable
- * @api
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @param {ol.layer.Layer} layer The candidate layer.
+ * @return {boolean} The renderer can render the layer.
  */
-ol.Map.prototype.setLayerGroup = function(layerGroup) {
-  this.set(ol.MapProperty.LAYERGROUP, layerGroup);
+ol.renderer.canvas.VectorTileLayer['handles'] = function(type, layer) {
+  return type === ol.renderer.Type.CANVAS && layer.getType() === ol.LayerType.VECTOR_TILE;
 };
 
 
 /**
- * Set the size of this map.
- * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
- * @observable
- * @api
+ * Create a layer renderer.
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @param {ol.layer.Layer} layer The layer to be rendererd.
+ * @return {ol.renderer.canvas.VectorTileLayer} The layer renderer.
  */
-ol.Map.prototype.setSize = function(size) {
-  this.set(ol.MapProperty.SIZE, size);
+ol.renderer.canvas.VectorTileLayer['create'] = function(mapRenderer, layer) {
+  return new ol.renderer.canvas.VectorTileLayer(/** @type {ol.layer.VectorTile} */ (layer));
 };
 
 
 /**
- * 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
+ * @const
+ * @type {!Object.<string, Array.<ol.render.ReplayType>>}
  */
-ol.Map.prototype.setTarget = function(target) {
-  this.set(ol.MapProperty.TARGET, target);
+ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = {
+  'image': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.CIRCLE,
+    ol.render.ReplayType.LINE_STRING, ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
+  'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
 };
 
 
 /**
- * Set the view for this map.
- * @param {ol.View} view The view that controls this map.
- * @observable
- * @api
+ * @const
+ * @type {!Object.<string, Array.<ol.render.ReplayType>>}
  */
-ol.Map.prototype.setView = function(view) {
-  this.set(ol.MapProperty.VIEW, view);
+ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = {
+  'image': [ol.render.ReplayType.DEFAULT],
+  'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT, ol.render.ReplayType.DEFAULT],
+  'vector': ol.render.replay.ORDER
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
+ * @inheritDoc
  */
-ol.Map.prototype.skipFeature = function(feature) {
-  var featureUid = ol.getUid(feature).toString();
-  this.skippedFeatureUids_[featureUid] = true;
-  this.render();
+ol.renderer.canvas.VectorTileLayer.prototype.disposeInternal = function() {
+  ol.events.unlisten(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
+  ol.renderer.canvas.TileLayer.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * Force a recalculation of the map viewport size.  This should be called when
- * third-party code changes the size of the map viewport.
- * @api
+ * @inheritDoc
  */
-ol.Map.prototype.updateSize = function() {
-  var targetElement = this.getTargetElement();
-
-  if (!targetElement) {
-    this.setSize(undefined);
-  } else {
-    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'])
-    ]);
+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.
- */
-ol.Map.prototype.unskipFeature = function(feature) {
-  var featureUid = ol.getUid(feature).toString();
-  delete this.skippedFeatureUids_[featureUid];
-  this.render();
-};
-
-
-/**
- * @param {olx.MapOptions} options Map options.
- * @return {ol.MapOptionsInternal} Internal map options.
+ * @param {ol.VectorImageTile} tile Tile.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-ol.Map.createOptionsInternal = function(options) {
+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;
 
-  /**
-   * @type {Element|Document}
-   */
-  var keyboardEventTarget = null;
-  if (options.keyboardEventTarget !== undefined) {
-    keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ?
-        document.getElementById(options.keyboardEventTarget) :
-        options.keyboardEventTarget;
+  var replayState = tile.getReplayState(layer);
+  if (!replayState.dirty && replayState.renderedRevision == revision &&
+      replayState.renderedRenderOrder == renderOrder) {
+    return;
   }
 
-  /**
-   * @type {Object.<string, *>}
-   */
-  var values = {};
+  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
+  var sourceTileGrid = source.getTileGrid();
+  var tileGrid = source.getTileGridForProjection(projection);
+  var resolution = tileGrid.getResolution(tile.tileCoord[0]);
+  var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
 
-  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;
+  var zIndexKeys = {};
+  for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
+    var sourceTile = tile.getTile(tile.tileKeys[t]);
+    if (sourceTile.getState() == ol.TileState.ERROR) {
+      continue;
     }
-  }
-
-  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();
+    var sourceTileCoord = sourceTile.tileCoord;
+    var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
+    var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent);
+    var bufferedExtent = ol.extent.equals(sourceTileExtent, sharedExtent) ? null :
+      ol.extent.buffer(sharedExtent, layer.getRenderBuffer() * resolution);
+    var tileProjection = sourceTile.getProjection();
+    var reproject = false;
+    if (!ol.proj.equivalent(projection, tileProjection)) {
+      reproject = true;
+      sourceTile.setProjection(projection);
+    }
+    replayState.dirty = false;
+    var replayGroup = new ol.render.canvas.ReplayGroup(0, sharedExtent, resolution,
+        pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer());
+    var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
+        resolution, pixelRatio);
 
-  /**
-   * @type {function(new: ol.renderer.Map, Element, ol.Map)}
-   */
-  var rendererConstructor = ol.renderer.Map;
+    /**
+     * @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) {
+        var dirty = this.renderFeature(feature, squaredTolerance, styles,
+            replayGroup);
+        this.dirty_ = this.dirty_ || dirty;
+        replayState.dirty = replayState.dirty || dirty;
+      }
+    };
 
-  /**
-   * @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);
+    var features = sourceTile.getFeatures();
+    if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
+      features.sort(renderOrder);
     }
-  } else {
-    rendererTypes = ol.DEFAULT_RENDERER_TYPES;
-  }
-
-  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;
+    var feature;
+    for (var i = 0, ii = features.length; i < ii; ++i) {
+      feature = features[i];
+      if (reproject) {
+        if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) {
+          // projected tile extent
+          tileProjection.setWorldExtent(sourceTileExtent);
+          // tile extent in tile pixel space
+          tileProjection.setExtent(sourceTile.getExtent());
+        }
+        feature.getGeometry().transform(tileProjection, projection);
       }
-    } else if (ol.ENABLE_WEBGL && rendererType == ol.renderer.Type.WEBGL) {
-      if (ol.has.WEBGL) {
-        rendererConstructor = ol.renderer.webgl.Map;
-        break;
+      if (!bufferedExtent || ol.extent.intersects(bufferedExtent, feature.getGeometry().getExtent())) {
+        renderFeature.call(this, feature);
       }
     }
-  }
-
-  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;
+    replayGroup.finish();
+    for (var r in replayGroup.getReplays()) {
+      zIndexKeys[r] = true;
     }
-  } else {
-    controls = ol.control.defaults();
+    sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), replayGroup);
   }
+  replayState.renderedRevision = revision;
+  replayState.renderedRenderOrder = renderOrder;
+};
 
-  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();
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function(
+    tile, frameState, layerState, x, y, w, h, gutter, transition) {
+  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);
   }
+};
 
-  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;
+
+/**
+ * @inheritDoc
+ */
+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 bufferedExtent, found;
+  var i, ii, replayGroup;
+  var tile, tileCoord, tileExtent;
+  for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
+    tile = renderedTiles[i];
+    tileCoord = tile.wrappedTileCoord;
+    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.getState() == ol.TileState.ERROR) {
+        continue;
+      }
+      replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString());
+      found = found || 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);
+            }
+          }, null);
+    }
+  }
+  return found;
+};
+
+
+/**
+ * @param {ol.VectorTile} tile Tile.
+ * @param {olx.FrameState} frameState Frame state.
+ * @return {ol.Transform} transform Transform.
+ * @private
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.getReplayTransform_ = function(tile, frameState) {
+  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]);
+  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);
+};
+
+
+/**
+ * @param {ol.events.Event} event Event.
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.handleFontsChanged_ = function(event) {
+  var layer = this.getLayer();
+  if (layer.getVisible() && this.renderedLayerRevision_ !== undefined) {
+    layer.changed();
+  }
+};
+
+
+/**
+ * Handle changes in image style state.
+ * @param {ol.events.Event} event Image style change event.
+ * @private
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(event) {
+  this.renderIfReadyAndVisible();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) {
+  var layer = this.getLayer();
+  var declutterReplays = layer.getDeclutter() ? {} : null;
+  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
+  var renderMode = layer.getRenderMode();
+  var replayTypes = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
+  var pixelRatio = frameState.pixelRatio;
+  var rotation = frameState.viewState.rotation;
+  var size = frameState.size;
+  var offsetX, offsetY;
+  if (rotation) {
+    offsetX = Math.round(pixelRatio * size[0] / 2);
+    offsetY = Math.round(pixelRatio * size[1] / 2);
+    ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
+  }
+  if (declutterReplays) {
+    this.declutterTree_.clear();
+  }
+  var tiles = this.renderedTiles;
+  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];
+    var transform = undefined;
+    for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
+      var sourceTile = tile.getTile(tile.tileKeys[t]);
+      if (sourceTile.getState() == ol.TileState.ERROR) {
+        continue;
+      }
+      var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString());
+      if (renderMode != ol.layer.VectorTileRenderType.VECTOR && !replayGroup.hasReplays(replayTypes)) {
+        continue;
+      }
+      if (!transform) {
+        transform = this.getTransform(frameState, worldOffset);
+      }
+      var currentZ = sourceTile.tileCoord[0];
+      var currentClip = replayGroup.getClipCoords(transform);
+      context.save();
+      context.globalAlpha = layerState.opacity;
+      // 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, transform, rotation, {}, replayTypes, declutterReplays);
+      context.restore();
+      clips.push(currentClip);
+      zs.push(currentZ);
+    }
+  }
+  if (declutterReplays) {
+    ol.render.canvas.ReplayGroup.replayDeclutter(declutterReplays, context, rotation);
+  }
+  if (rotation) {
+    ol.render.canvas.rotateAtOffset(context, rotation,
+        /** @type {number} */ (offsetX), /** @type {number} */ (offsetY));
+  }
+  ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
+};
+
+
+/**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
+ *     styles.
+ * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
+ * @return {boolean} `true` if an image is loading.
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) {
+  if (!styles) {
+    return false;
+  }
+  var loading = false;
+  if (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 {
-    overlays = new ol.Collection();
+    loading = ol.renderer.vector.renderFeature(
+        replayGroup, feature, styles, squaredTolerance,
+        this.handleStyleImageChange_, this);
   }
+  return loading;
+};
 
-  return {
-    controls: controls,
-    interactions: interactions,
-    keyboardEventTarget: keyboardEventTarget,
-    logos: logos,
-    overlays: overlays,
-    rendererConstructor: rendererConstructor,
-    values: values
-  };
 
+/**
+ * @param {ol.VectorImageTile} tile Tile.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @private
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
+    tile, frameState, layerState) {
+  var layer = this.getLayer();
+  var replayState = tile.getReplayState(layer);
+  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 = /** @type {ol.source.VectorTile} */ (layer.getSource());
+    var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
+    var resolution = tileGrid.getResolution(z);
+    var context = tile.getContext(layer);
+    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]);
+      if (sourceTile.getState() == ol.TileState.ERROR) {
+        continue;
+      }
+      var pixelScale = pixelRatio / resolution;
+      var transform = ol.transform.reset(this.tmpTransform_);
+      ol.transform.scale(transform, pixelScale, -pixelScale);
+      ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
+      var replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString());
+      replayGroup.replay(context, transform, 0, {}, replays);
+    }
+  }
 };
 
-goog.provide('ol.OverlayPositioning');
+goog.provide('ol.CanvasMap');
+
+goog.require('ol');
+goog.require('ol.PluggableMap');
+goog.require('ol.PluginType');
+goog.require('ol.control');
+goog.require('ol.interaction');
+goog.require('ol.obj');
+goog.require('ol.plugins');
+goog.require('ol.renderer.canvas.ImageLayer');
+goog.require('ol.renderer.canvas.Map');
+goog.require('ol.renderer.canvas.TileLayer');
+goog.require('ol.renderer.canvas.VectorLayer');
+goog.require('ol.renderer.canvas.VectorTileLayer');
+
+
+ol.plugins.register(ol.PluginType.MAP_RENDERER, ol.renderer.canvas.Map);
+ol.plugins.registerMultiple(ol.PluginType.LAYER_RENDERER, [
+  ol.renderer.canvas.ImageLayer,
+  ol.renderer.canvas.TileLayer,
+  ol.renderer.canvas.VectorLayer,
+  ol.renderer.canvas.VectorTileLayer
+]);
+
 
 /**
- * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
- * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
- * `'top-center'`, `'top-right'`
- * @enum {string}
+ * @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.CanvasMap({
+ *       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.PluggableMap}
+ * @param {olx.MapOptions} options Map options.
+ * @fires ol.MapBrowserEvent
+ * @fires ol.MapEvent
+ * @fires ol.render.Event#postcompose
+ * @fires ol.render.Event#precompose
+ * @api
  */
-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'
+ol.CanvasMap = function(options) {
+  options = ol.obj.assign({}, options);
+  delete options.renderer;
+  if (!options.controls) {
+    options.controls = ol.control.defaults();
+  }
+  if (!options.interactions) {
+    options.interactions = ol.interaction.defaults();
+  }
+
+  ol.PluggableMap.call(this, options);
 };
+ol.inherits(ol.CanvasMap, ol.PluggableMap);
 
-goog.provide('ol.Overlay');
+goog.provide('ol.control.FullScreen');
 
 goog.require('ol');
-goog.require('ol.MapEventType');
-goog.require('ol.Object');
-goog.require('ol.OverlayPositioning');
+goog.require('ol.control.Control');
 goog.require('ol.css');
 goog.require('ol.dom');
 goog.require('ol.events');
-goog.require('ol.extent');
+goog.require('ol.events.EventType');
 
 
 /**
  * @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.
+ * 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.
  *
- * Example:
+ * 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.
  *
- *     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.
+ * @extends {ol.control.Control}
+ * @param {olx.control.FullScreenOptions=} opt_options Options.
  * @api
  */
-ol.Overlay = function(options) {
+ol.control.FullScreen = function(opt_options) {
 
-  ol.Object.call(this);
+  var options = opt_options ? opt_options : {};
 
   /**
    * @private
-   * @type {number|string|undefined}
+   * @type {string}
    */
-  this.id_ = options.id;
+  this.cssClassName_ = options.className !== undefined ? options.className :
+    'ol-full-screen';
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.insertFirst_ = options.insertFirst !== undefined ?
-      options.insertFirst : true;
+  var label = options.label !== undefined ? options.label : '\u2922';
 
   /**
    * @private
-   * @type {boolean}
+   * @type {Node}
    */
-  this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true;
+  this.labelNode_ = typeof label === 'string' ?
+    document.createTextNode(label) : label;
+
+  var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7';
 
   /**
    * @private
-   * @type {Element}
+   * @type {Node}
    */
-  this.element_ = document.createElement('DIV');
-  this.element_.className = 'ol-overlay-container ' + ol.css.CLASS_SELECTABLE;
-  this.element_.style.position = 'absolute';
+  this.labelActiveNode_ = typeof labelActive === 'string' ?
+    document.createTextNode(labelActive) : labelActive;
 
-  /**
-   * @protected
-   * @type {boolean}
-   */
-  this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
+  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_);
 
-  /**
-   * @private
-   * @type {olx.OverlayPanOptions}
-   */
-  this.autoPanAnimation_ = options.autoPanAnimation ||
-      /** @type {olx.OverlayPanOptions} */ ({});
+  ol.events.listen(button, ol.events.EventType.CLICK,
+      this.handleClick_, this);
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.autoPanMargin_ = options.autoPanMargin !== undefined ?
-      options.autoPanMargin : 20;
+  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
+  });
 
   /**
    * @private
-   * @type {{bottom_: string,
-   *         left_: string,
-   *         right_: string,
-   *         top_: string,
-   *         visible: boolean}}
+   * @type {boolean}
    */
-  this.rendered_ = {
-    bottom_: '',
-    left_: '',
-    right_: '',
-    top_: '',
-    visible: true
-  };
+  this.keys_ = options.keys !== undefined ? options.keys : false;
 
   /**
    * @private
-   * @type {?ol.EventsKey}
+   * @type {Element|string|undefined}
    */
-  this.mapPostrenderListenerKey_ = null;
-
-  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);
-  }
+  this.source_ = options.source;
 
 };
-ol.inherits(ol.Overlay, ol.Object);
+ol.inherits(ol.control.FullScreen, ol.control.Control);
 
 
 /**
- * Get the DOM element of this overlay.
- * @return {Element|undefined} The Element containing the overlay.
- * @observable
- * @api
+ * @param {Event} event The event to handle
+ * @private
  */
-ol.Overlay.prototype.getElement = function() {
-  return /** @type {Element|undefined} */ (
-      this.get(ol.Overlay.Property_.ELEMENT));
+ol.control.FullScreen.prototype.handleClick_ = function(event) {
+  event.preventDefault();
+  this.handleFullScreen_();
 };
 
 
 /**
- * Get the overlay identifier which is set on constructor.
- * @return {number|string|undefined} Id.
- * @api
+ * @private
  */
-ol.Overlay.prototype.getId = function() {
-  return this.id_;
+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);
+    }
+  }
 };
 
 
 /**
- * Get the map associated with this overlay.
- * @return {ol.Map|undefined} The map that the overlay is part of.
- * @observable
- * @api
+ * @private
  */
-ol.Overlay.prototype.getMap = function() {
-  return /** @type {ol.Map|undefined} */ (
-      this.get(ol.Overlay.Property_.MAP));
+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();
+  }
 };
 
 
 /**
- * Get the offset of this overlay.
- * @return {Array.<number>} The offset.
- * @observable
+ * @inheritDoc
  * @api
  */
-ol.Overlay.prototype.getOffset = function() {
-  return /** @type {Array.<number>} */ (
-      this.get(ol.Overlay.Property_.OFFSET));
+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)
+    );
+  }
 };
 
-
 /**
- * Get the current position of this overlay.
- * @return {ol.Coordinate|undefined} The spatial point that the overlay is
- *     anchored at.
- * @observable
- * @api
+ * @return {boolean} Fullscreen is supported by the current platform.
  */
-ol.Overlay.prototype.getPosition = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.Overlay.Property_.POSITION));
+ol.control.FullScreen.isFullScreenSupported = function() {
+  var body = document.body;
+  return !!(
+    body.webkitRequestFullscreen ||
+    (body.mozRequestFullScreen && document.mozFullScreenEnabled) ||
+    (body.msRequestFullscreen && document.msFullscreenEnabled) ||
+    (body.requestFullscreen && document.fullscreenEnabled)
+  );
 };
 
-
 /**
- * Get the current positioning of this overlay.
- * @return {ol.OverlayPositioning} How the overlay is positioned
- *     relative to its point on the map.
- * @observable
- * @api
+ * @return {boolean} Element is currently in fullscreen.
  */
-ol.Overlay.prototype.getPositioning = function() {
-  return /** @type {ol.OverlayPositioning} */ (
-      this.get(ol.Overlay.Property_.POSITIONING));
+ol.control.FullScreen.isFullScreen = function() {
+  return !!(
+    document.webkitIsFullScreen || document.mozFullScreen ||
+    document.msFullscreenElement || document.fullscreenElement
+  );
 };
 
-
 /**
- * @protected
+ * Request to fullscreen an element.
+ * @param {Node} element Element to request fullscreen
  */
-ol.Overlay.prototype.handleElementChanged = function() {
-  ol.dom.removeChildren(this.element_);
-  var element = this.getElement();
-  if (element) {
-    this.element_.appendChild(element);
+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();
   }
 };
 
-
 /**
- * @protected
+ * Request to fullscreen an element with keyboard input.
+ * @param {Node} element Element to request fullscreen
  */
-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_);
-    }
+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);
   }
 };
 
-
 /**
- * @protected
+ * Exit fullscreen.
  */
-ol.Overlay.prototype.render = function() {
-  this.updatePixelPosition();
+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();
+  }
 };
 
-
 /**
- * @protected
+ * @return {string} Change type.
+ * @private
  */
-ol.Overlay.prototype.handleOffsetChanged = function() {
-  this.updatePixelPosition();
-};
+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;
+  };
+})();
+
+// FIXME should listen on appropriate pane, once it is defined
+
+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');
 
 
 /**
- * @protected
+ * @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
  */
-ol.Overlay.prototype.handlePositionChanged = function() {
-  this.updatePixelPosition();
-  if (this.get(ol.Overlay.Property_.POSITION) && this.autoPan) {
-    this.panIntoView_();
+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);
   }
-};
 
+  /**
+   * @private
+   * @type {string}
+   */
+  this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : '';
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.renderedHTML_ = element.innerHTML;
+
+  /**
+   * @private
+   * @type {ol.proj.Projection}
+   */
+  this.mapProjection_ = null;
+
+  /**
+   * @private
+   * @type {?ol.TransformFunction}
+   */
+  this.transform_ = null;
+
+  /**
+   * @private
+   * @type {ol.Pixel}
+   */
+  this.lastMouseMovePixel_ = null;
 
-/**
- * @protected
- */
-ol.Overlay.prototype.handlePositioningChanged = function() {
-  this.updatePixelPosition();
 };
+ol.inherits(ol.control.MousePosition, ol.control.Control);
 
 
 /**
- * Set the DOM element to be associated with this overlay.
- * @param {Element|undefined} element The Element containing the overlay.
- * @observable
+ * Update the mouseposition element.
+ * @param {ol.MapEvent} mapEvent Map event.
+ * @this {ol.control.MousePosition}
  * @api
  */
-ol.Overlay.prototype.setElement = function(element) {
-  this.set(ol.Overlay.Property_.ELEMENT, element);
+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_);
 };
 
 
 /**
- * Set the map to be associated with this overlay.
- * @param {ol.Map|undefined} map The map that the overlay is part of.
- * @observable
- * @api
+ * @private
  */
-ol.Overlay.prototype.setMap = function(map) {
-  this.set(ol.Overlay.Property_.MAP, map);
+ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
+  this.transform_ = null;
 };
 
 
 /**
- * Set the offset for this overlay.
- * @param {Array.<number>} offset Offset.
+ * 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
  */
-ol.Overlay.prototype.setOffset = function(offset) {
-  this.set(ol.Overlay.Property_.OFFSET, offset);
+ol.control.MousePosition.prototype.getCoordinateFormat = function() {
+  return /** @type {ol.CoordinateFormatType|undefined} */ (
+    this.get(ol.control.MousePosition.Property_.COORDINATE_FORMAT));
 };
 
 
 /**
- * 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.
+ * 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
  */
-ol.Overlay.prototype.setPosition = function(position) {
-  this.set(ol.Overlay.Property_.POSITION, position);
+ol.control.MousePosition.prototype.getProjection = function() {
+  return /** @type {ol.proj.Projection|undefined} */ (
+    this.get(ol.control.MousePosition.Property_.PROJECTION));
 };
 
 
 /**
- * Pan the map so that the overlay is entirely visible in the current viewport
- * (if necessary).
- * @private
+ * @param {Event} event Browser event.
+ * @protected
  */
-ol.Overlay.prototype.panIntoView_ = function() {
+ol.control.MousePosition.prototype.handleMouseMove = function(event) {
   var map = this.getMap();
-
-  if (!map || !map.getTargetElement()) {
-    return;
-  }
-
-  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)]);
-
-  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
-      });
-    }
-  }
+  this.lastMouseMovePixel_ = map.getEventPixel(event);
+  this.updateHTML_(this.lastMouseMovePixel_);
 };
 
 
 /**
- * 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
+ * @param {Event} event Browser event.
+ * @protected
  */
-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]
-  ];
+ol.control.MousePosition.prototype.handleMouseOut = function(event) {
+  this.updateHTML_(null);
+  this.lastMouseMovePixel_ = null;
 };
 
 
 /**
- * Set the positioning for this overlay.
- * @param {ol.OverlayPositioning} positioning how the overlay is
- *     positioned relative to its point on the map.
- * @observable
+ * @inheritDoc
  * @api
  */
-ol.Overlay.prototype.setPositioning = function(positioning) {
-  this.set(ol.Overlay.Property_.POSITIONING, positioning);
+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)
+    );
+  }
 };
 
 
 /**
- * Modify the visibility of the element.
- * @param {boolean} visible Element visibility.
- * @protected
+ * 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
  */
-ol.Overlay.prototype.setVisible = function(visible) {
-  if (this.rendered_.visible !== visible) {
-    this.element_.style.display = visible ? '' : 'none';
-    this.rendered_.visible = visible;
-  }
+ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
+  this.set(ol.control.MousePosition.Property_.COORDINATE_FORMAT, format);
 };
 
 
 /**
- * Update pixel position.
- * @protected
+ * Set the projection that is used to report the mouse position.
+ * @param {ol.ProjectionLike} projection The projection to report mouse
+ *     position in.
+ * @observable
+ * @api
  */
-ol.Overlay.prototype.updatePixelPosition = function() {
-  var map = this.getMap();
-  var position = this.getPosition();
-  if (!map || !map.isRendered() || !position) {
-    this.setVisible(false);
-    return;
-  }
-
-  var pixel = map.getPixelFromCoordinate(position);
-  var mapSize = map.getSize();
-  this.updateRenderedPosition(pixel, mapSize);
+ol.control.MousePosition.prototype.setProjection = function(projection) {
+  this.set(ol.control.MousePosition.Property_.PROJECTION, ol.proj.get(projection));
 };
 
 
 /**
- * @param {ol.Pixel} pixel The pixel location.
- * @param {ol.Size|undefined} mapSize The map size.
- * @protected
+ * @param {?ol.Pixel} pixel Pixel.
+ * @private
  */
-ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) {
-  var style = this.element_.style;
-  var offset = this.getOffset();
-
-  var positioning = this.getPositioning();
-
-  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;
+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 left = Math.round(pixel[0] + offsetX) + 'px';
-    if (this.rendered_.left_ != left) {
-      this.rendered_.left_ = style.left = left;
+    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 (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;
-    }
+  if (!this.renderedHTML_ || html != this.renderedHTML_) {
+    this.element.innerHTML = html;
+    this.renderedHTML_ = html;
   }
 };
 
@@ -32775,130 +32258,698 @@ ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) {
  * @enum {string}
  * @private
  */
-ol.Overlay.Property_ = {
-  ELEMENT: 'element',
-  MAP: 'map',
-  OFFSET: 'offset',
-  POSITION: 'position',
-  POSITIONING: 'positioning'
+ol.control.MousePosition.Property_ = {
+  PROJECTION: 'projection',
+  COORDINATE_FORMAT: 'coordinateFormat'
 };
 
-goog.provide('ol.control.OverviewMap');
+goog.provide('ol.OverlayPositioning');
+
+/**
+ * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
+ * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
+ * `'top-center'`, `'top-right'`
+ * @enum {string}
+ */
+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');
 
 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');
 
 
 /**
- * Create a new control with a map acting as an overview map for an other
- * defined map.
+ * @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.control.Control}
- * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options.
+ * @extends {ol.Object}
+ * @param {olx.OverlayOptions} options Overlay options.
  * @api
  */
-ol.control.OverviewMap = function(opt_options) {
+ol.Overlay = function(options) {
 
-  var options = opt_options ? opt_options : {};
+  ol.Object.call(this);
 
   /**
-   * @type {boolean}
-   * @private
+   * @protected
+   * @type {olx.OverlayOptions}
    */
-  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
+  this.options = options;
 
   /**
-   * @private
-   * @type {boolean}
+   * @protected
+   * @type {number|string|undefined}
    */
-  this.collapsible_ = options.collapsible !== undefined ?
-      options.collapsible : true;
-
-  if (!this.collapsible_) {
-    this.collapsed_ = false;
-  }
-
-  var className = options.className !== undefined ? options.className : 'ol-overviewmap';
+  this.id = options.id;
 
-  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Overview map';
-
-  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00AB';
-
-  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';
-
-
-  if (typeof label === 'string') {
-    /**
-     * @private
-     * @type {Node}
-     */
-    this.label_ = document.createElement('span');
-    this.label_.textContent = label;
-  } else {
-    this.label_ = label;
-  }
-
-  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);
+  /**
+   * @protected
+   * @type {boolean}
+   */
+  this.insertFirst = options.insertFirst !== undefined ?
+    options.insertFirst : true;
 
-  ol.events.listen(button, ol.events.EventType.CLICK,
-      this.handleClick_, this);
+  /**
+   * @protected
+   * @type {boolean}
+   */
+  this.stopEvent = options.stopEvent !== undefined ? options.stopEvent : true;
 
   /**
+   * @protected
    * @type {Element}
-   * @private
    */
-  this.ovmapDiv_ = document.createElement('DIV');
-  this.ovmapDiv_.className = 'ol-overviewmap-map';
+  this.element = document.createElement('DIV');
+  this.element.className = options.className !== undefined ?
+    options.className : 'ol-overlay-container ' + ol.css.CLASS_SELECTABLE;
+  this.element.style.position = 'absolute';
 
   /**
-   * @type {ol.Map}
-   * @private
+   * @protected
+   * @type {boolean}
    */
-  this.ovmap_ = new ol.Map({
-    controls: new ol.Collection(),
-    interactions: new ol.Collection(),
-    view: options.view
-  });
-  var ovmap = this.ovmap_;
+  this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
 
-  if (options.layers) {
-    options.layers.forEach(
-        /**
-       * @param {ol.layer.Layer} layer Layer.
-       */
-        function(layer) {
-          ovmap.addLayer(layer);
+  /**
+   * @protected
+   * @type {olx.OverlayPanOptions}
+   */
+  this.autoPanAnimation = options.autoPanAnimation ||
+    /** @type {olx.OverlayPanOptions} */ ({});
+
+  /**
+   * @protected
+   * @type {number}
+   */
+  this.autoPanMargin = options.autoPanMargin !== undefined ?
+    options.autoPanMargin : 20;
+
+  /**
+   * @protected
+   * @type {{bottom_: string,
+   *         left_: string,
+   *         right_: string,
+   *         top_: string,
+   *         visible: boolean}}
+   */
+  this.rendered = {
+    bottom_: '',
+    left_: '',
+    right_: '',
+    top_: '',
+    visible: true
+  };
+
+  /**
+   * @protected
+   * @type {?ol.EventsKey}
+   */
+  this.mapPostrenderListenerKey = null;
+
+  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);
+  }
+
+};
+ol.inherits(ol.Overlay, ol.Object);
+
+
+/**
+ * Get the DOM element of this overlay.
+ * @return {Element|undefined} The Element containing the overlay.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.getElement = function() {
+  return /** @type {Element|undefined} */ (
+    this.get(ol.Overlay.Property.ELEMENT));
+};
+
+
+/**
+ * Get the overlay identifier which is set on constructor.
+ * @return {number|string|undefined} Id.
+ * @api
+ */
+ol.Overlay.prototype.getId = function() {
+  return this.id;
+};
+
+
+/**
+ * Get the map associated with this overlay.
+ * @return {ol.PluggableMap|undefined} The map that the overlay is part of.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.getMap = function() {
+  return /** @type {ol.PluggableMap|undefined} */ (
+    this.get(ol.Overlay.Property.MAP));
+};
+
+
+/**
+ * Get the offset of this overlay.
+ * @return {Array.<number>} The offset.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.getOffset = function() {
+  return /** @type {Array.<number>} */ (
+    this.get(ol.Overlay.Property.OFFSET));
+};
+
+
+/**
+ * Get the current position of this overlay.
+ * @return {ol.Coordinate|undefined} The spatial point that the overlay is
+ *     anchored at.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.getPosition = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+    this.get(ol.Overlay.Property.POSITION));
+};
+
+
+/**
+ * Get the current positioning of this overlay.
+ * @return {ol.OverlayPositioning} How the overlay is positioned
+ *     relative to its point on the map.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.getPositioning = function() {
+  return /** @type {ol.OverlayPositioning} */ (
+    this.get(ol.Overlay.Property.POSITIONING));
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleElementChanged = function() {
+  ol.dom.removeChildren(this.element);
+  var element = this.getElement();
+  if (element) {
+    this.element.appendChild(element);
+  }
+};
+
+
+/**
+ * @protected
+ */
+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);
+    }
+  }
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.render = function() {
+  this.updatePixelPosition();
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handleOffsetChanged = function() {
+  this.updatePixelPosition();
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handlePositionChanged = function() {
+  this.updatePixelPosition();
+  if (this.get(ol.Overlay.Property.POSITION) && this.autoPan) {
+    this.panIntoView();
+  }
+};
+
+
+/**
+ * @protected
+ */
+ol.Overlay.prototype.handlePositioningChanged = function() {
+  this.updatePixelPosition();
+};
+
+
+/**
+ * Set the DOM element to be associated with this overlay.
+ * @param {Element|undefined} element The Element containing the overlay.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.setElement = function(element) {
+  this.set(ol.Overlay.Property.ELEMENT, element);
+};
+
+
+/**
+ * Set the map to be associated with this overlay.
+ * @param {ol.PluggableMap|undefined} map The map that the overlay is part of.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.setMap = function(map) {
+  this.set(ol.Overlay.Property.MAP, map);
+};
+
+
+/**
+ * Set the offset for this overlay.
+ * @param {Array.<number>} offset Offset.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.setOffset = function(offset) {
+  this.set(ol.Overlay.Property.OFFSET, offset);
+};
+
+
+/**
+ * Set the position for this overlay. If the position is `undefined` the
+ * overlay is hidden.
+ * @param {ol.Coordinate|undefined} position The spatial point that the overlay
+ *     is anchored at.
+ * @observable
+ * @api
+ */
+ol.Overlay.prototype.setPosition = function(position) {
+  this.set(ol.Overlay.Property.POSITION, position);
+};
+
+
+/**
+ * Pan the map so that the overlay is entirely visible in the current viewport
+ * (if necessary).
+ * @protected
+ */
+ol.Overlay.prototype.panIntoView = function() {
+  var map = this.getMap();
+
+  if (!map || !map.getTargetElement()) {
+    return;
+  }
+
+  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)]);
+
+  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
+      });
+    }
+  }
+};
+
+
+/**
+ * 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.
+ * @protected
+ */
+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]
+  ];
+};
+
+
+/**
+ * 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.Overlay.prototype.setPositioning = function(positioning) {
+  this.set(ol.Overlay.Property.POSITIONING, positioning);
+};
+
+
+/**
+ * Modify the visibility of the element.
+ * @param {boolean} visible Element visibility.
+ * @protected
+ */
+ol.Overlay.prototype.setVisible = function(visible) {
+  if (this.rendered.visible !== visible) {
+    this.element.style.display = visible ? '' : 'none';
+    this.rendered.visible = visible;
+  }
+};
+
+
+/**
+ * Update pixel position.
+ * @protected
+ */
+ol.Overlay.prototype.updatePixelPosition = function() {
+  var map = this.getMap();
+  var position = this.getPosition();
+  if (!map || !map.isRendered() || !position) {
+    this.setVisible(false);
+    return;
+  }
+
+  var pixel = map.getPixelFromCoordinate(position);
+  var mapSize = map.getSize();
+  this.updateRenderedPosition(pixel, mapSize);
+};
+
+
+/**
+ * @param {ol.Pixel} pixel The pixel location.
+ * @param {ol.Size|undefined} mapSize The map size.
+ * @protected
+ */
+ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) {
+  var style = this.element.style;
+  var offset = this.getOffset();
+
+  var positioning = this.getPositioning();
+
+  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;
+    }
+  }
+  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;
+    }
+  }
+};
+
+
+/**
+ * returns the options this Overlay has been created with
+ * @public
+ * @return {olx.OverlayOptions} overlay options
+ */
+ol.Overlay.prototype.getOptions = function() {
+  return this.options;
+};
+
+
+/**
+ * @enum {string}
+ * @protected
+ */
+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.PluggableMap');
+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');
+
+
+/**
+ * Create a new control with a map acting as an overview map for an other
+ * defined map.
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options.
+ * @api
+ */
+ol.control.OverviewMap = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.collapsible_ = options.collapsible !== undefined ?
+    options.collapsible : true;
+
+  if (!this.collapsible_) {
+    this.collapsed_ = false;
+  }
+
+  var className = options.className !== undefined ? options.className : 'ol-overviewmap';
+
+  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Overview map';
+
+  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00AB';
+
+  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';
+
+
+  if (typeof label === 'string') {
+    /**
+     * @private
+     * @type {Node}
+     */
+    this.label_ = document.createElement('span');
+    this.label_.textContent = label;
+  } else {
+    this.label_ = label;
+  }
+
+  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);
+
+  /**
+   * @type {Element}
+   * @private
+   */
+  this.ovmapDiv_ = document.createElement('DIV');
+  this.ovmapDiv_.className = 'ol-overviewmap-map';
+
+  /**
+   * @type {ol.PluggableMap}
+   * @private
+   */
+  this.ovmap_ = new ol.PluggableMap({
+    controls: new ol.Collection(),
+    interactions: new ol.Collection(),
+    view: options.view
+  });
+  var ovmap = this.ovmap_;
+
+  if (options.layers) {
+    options.layers.forEach(
+        /**
+       * @param {ol.layer.Layer} layer Layer.
+       */
+        function(layer) {
+          ovmap.addLayer(layer);
         }, this);
   }
 
@@ -33336,7 +33387,7 @@ ol.control.OverviewMap.prototype.getCollapsed = function() {
 
 /**
  * Return the overview map.
- * @return {ol.Map} Overview map.
+ * @return {ol.PluggableMap} Overview map.
  * @api
  */
 ol.control.OverviewMap.prototype.getOverviewMap = function() {
@@ -33472,7 +33523,7 @@ ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5];
  */
 ol.control.ScaleLine.prototype.getUnits = function() {
   return /** @type {ol.control.ScaleLineUnits|undefined} */ (
-      this.get(ol.control.ScaleLine.Property_.UNITS));
+    this.get(ol.control.ScaleLine.Property_.UNITS));
 };
 
 
@@ -33528,17 +33579,25 @@ ol.control.ScaleLine.prototype.updateElement_ = function() {
 
   var center = viewState.center;
   var projection = viewState.projection;
-  var metersPerUnit = projection.getMetersPerUnit();
+  var units = this.getUnits();
+  var pointResolutionUnits = units == ol.control.ScaleLineUnits.DEGREES ?
+    ol.proj.Units.DEGREES :
+    ol.proj.Units.METERS;
   var pointResolution =
-      ol.proj.getPointResolution(projection, viewState.resolution, center) *
-      metersPerUnit;
+      ol.proj.getPointResolution(projection, viewState.resolution, center, pointResolutionUnits);
+  if (units != ol.control.ScaleLineUnits.DEGREES) {
+    pointResolution *= projection.getMetersPerUnit();
+  }
 
   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 (projection.getUnits() == ol.proj.Units.DEGREES) {
+      nominalCount *= metersPerDegree;
+    } else {
+      pointResolution /= metersPerDegree;
+    }
     if (nominalCount < metersPerDegree / 60) {
       suffix = '\u2033'; // seconds
       pointResolution *= 3600;
@@ -34029,21 +34088,21 @@ ol.control.ZoomToExtent = function(opt_options) {
 
   /**
    * @type {ol.Extent}
-   * @private
+   * @protected
    */
-  this.extent_ = options.extent ? options.extent : null;
+  this.extent = options.extent ? options.extent : null;
 
   var className = options.className !== undefined ? options.className :
-      'ol-zoom-extent';
+    'ol-zoom-extent';
 
   var label = options.label !== undefined ? options.label : 'E';
   var tipLabel = options.tipLabel !== undefined ?
-      options.tipLabel : 'Fit to extent';
+    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
+      typeof label === 'string' ? document.createTextNode(label) : label
   );
 
   ol.events.listen(button, ol.events.EventType.CLICK,
@@ -34069,17 +34128,17 @@ ol.inherits(ol.control.ZoomToExtent, ol.control.Control);
  */
 ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
   event.preventDefault();
-  this.handleZoomToExtent_();
+  this.handleZoomToExtent();
 };
 
 
 /**
- * @private
+ * @protected
  */
-ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() {
+ol.control.ZoomToExtent.prototype.handleZoomToExtent = function() {
   var map = this.getMap();
   var view = map.getView();
-  var extent = !this.extent_ ? view.getProjection().getExtent() : this.extent_;
+  var extent = !this.extent ? view.getProjection().getExtent() : this.extent;
   view.fit(extent);
 };
 
@@ -34142,6 +34201,8 @@ goog.require('ol.math');
  *
  * @see {@link http://www.w3.org/TR/orientation-event/}
  *
+ * @deprecated This class is deprecated and will removed in the next major release.
+ *
  * @constructor
  * @extends {ol.Object}
  * @param {olx.DeviceOrientationOptions=} opt_options Options.
@@ -34217,7 +34278,7 @@ ol.DeviceOrientation.prototype.orientationChange_ = function(originalEvent) {
  */
 ol.DeviceOrientation.prototype.getAlpha = function() {
   return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property_.ALPHA));
+    this.get(ol.DeviceOrientation.Property_.ALPHA));
 };
 
 
@@ -34230,7 +34291,7 @@ ol.DeviceOrientation.prototype.getAlpha = function() {
  */
 ol.DeviceOrientation.prototype.getBeta = function() {
   return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property_.BETA));
+    this.get(ol.DeviceOrientation.Property_.BETA));
 };
 
 
@@ -34243,7 +34304,7 @@ ol.DeviceOrientation.prototype.getBeta = function() {
  */
 ol.DeviceOrientation.prototype.getGamma = function() {
   return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property_.GAMMA));
+    this.get(ol.DeviceOrientation.Property_.GAMMA));
 };
 
 
@@ -34256,7 +34317,7 @@ ol.DeviceOrientation.prototype.getGamma = function() {
  */
 ol.DeviceOrientation.prototype.getHeading = function() {
   return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property_.HEADING));
+    this.get(ol.DeviceOrientation.Property_.HEADING));
 };
 
 
@@ -34268,7 +34329,7 @@ ol.DeviceOrientation.prototype.getHeading = function() {
  */
 ol.DeviceOrientation.prototype.getTracking = function() {
   return /** @type {boolean} */ (
-      this.get(ol.DeviceOrientation.Property_.TRACKING));
+    this.get(ol.DeviceOrientation.Property_.TRACKING));
 };
 
 
@@ -34313,18 +34374,6 @@ ol.DeviceOrientation.Property_ = {
   TRACKING: 'tracking'
 };
 
-goog.provide('ol.ImageState');
-
-/**
- * @enum {number}
- */
-ol.ImageState = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3
-};
-
 goog.provide('ol.style.Image');
 
 
@@ -34629,7 +34678,7 @@ ol.style.RegularShape = function(options) {
    * @type {number}
    */
   this.radius_ = /** @type {number} */ (options.radius !== undefined ?
-      options.radius : options.radius1);
+    options.radius : options.radius1);
 
   /**
    * @private
@@ -34685,13 +34734,13 @@ ol.style.RegularShape = function(options) {
    * @type {boolean}
    */
   var snapToPixel = options.snapToPixel !== undefined ?
-      options.snapToPixel : true;
+    options.snapToPixel : true;
 
   /**
    * @type {boolean}
    */
   var rotateWithView = options.rotateWithView !== undefined ?
-      options.rotateWithView : false;
+    options.rotateWithView : false;
 
   ol.style.Image.call(this, {
     opacity: 1,
@@ -34884,6 +34933,7 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) {
   var lineJoin = '';
   var miterLimit = 0;
   var lineDash = null;
+  var lineDashOffset = 0;
   var strokeStyle;
   var strokeWidth = 0;
 
@@ -34898,8 +34948,10 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) {
       strokeWidth = ol.render.canvas.defaultLineWidth;
     }
     lineDash = this.stroke_.getLineDash();
+    lineDashOffset = this.stroke_.getLineDashOffset();
     if (!ol.has.CANVAS_LINE_DASH) {
       lineDash = null;
+      lineDashOffset = 0;
     }
     lineJoin = this.stroke_.getLineJoin();
     if (lineJoin === undefined) {
@@ -34924,6 +34976,7 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) {
     size: size,
     lineCap: lineCap,
     lineDash: lineDash,
+    lineDashOffset: lineDashOffset,
     lineJoin: lineJoin,
     miterLimit: miterLimit
   };
@@ -35009,7 +35062,7 @@ ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
       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));
+          renderOptions.size / 2 + radiusC * Math.sin(angle0));
     }
   }
 
@@ -35027,6 +35080,7 @@ ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
     context.lineWidth = renderOptions.strokeWidth;
     if (renderOptions.lineDash) {
       context.setLineDash(renderOptions.lineDash);
+      context.lineDashOffset = renderOptions.lineDashOffset;
     }
     context.lineCap = renderOptions.lineCap;
     context.lineJoin = renderOptions.lineJoin;
@@ -35089,7 +35143,7 @@ ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions
       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));
+          renderOptions.size / 2 + radiusC * Math.sin(angle0));
     }
   }
 
@@ -35100,6 +35154,7 @@ ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions
     context.lineWidth = renderOptions.strokeWidth;
     if (renderOptions.lineDash) {
       context.setLineDash(renderOptions.lineDash);
+      context.lineDashOffset = renderOptions.lineDashOffset;
     }
     context.stroke();
   }
@@ -35112,9 +35167,9 @@ ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions
  */
 ol.style.RegularShape.prototype.getChecksum = function() {
   var strokeChecksum = this.stroke_ ?
-      this.stroke_.getChecksum() : '-';
+    this.stroke_.getChecksum() : '-';
   var fillChecksum = this.fill_ ?
-      this.fill_.getChecksum() : '-';
+    this.fill_.getChecksum() : '-';
 
   var recalculate = !this.checksums_ ||
       (strokeChecksum != this.checksums_[1] ||
@@ -35273,19 +35328,301 @@ ol.style.Fill.prototype.setColor = function(color) {
 ol.style.Fill.prototype.getChecksum = function() {
   if (this.checksum_ === undefined) {
     if (
-        this.color_ instanceof CanvasPattern ||
+      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_) : '-');
+        ol.color.asString(this.color_) : '-');
     }
   }
 
   return this.checksum_;
 };
 
+goog.provide('ol.style.Stroke');
+
+goog.require('ol');
+
+
+/**
+ * @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.
+ * @api
+ */
+ol.style.Stroke = 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.lineCap_ = options.lineCap;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;
+
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.lineDashOffset_ = options.lineDashOffset;
+
+  /**
+   * @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;
+};
+
+
+/**
+ * Clones the style.
+ * @return {ol.style.Stroke} The cloned style.
+ * @api
+ */
+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()
+  });
+};
+
+
+/**
+ * Get the stroke color.
+ * @return {ol.Color|ol.ColorLike} 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 dash offset for the stroke.
+ * @return {number|undefined} Line dash offset.
+ * @api
+ */
+ol.style.Stroke.prototype.getLineDashOffset = function() {
+  return this.lineDashOffset_;
+};
+
+
+/**
+ * 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|ol.ColorLike} 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.
+ *
+ * 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.
+ *
+ * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility
+ *
+ * @param {Array.<number>} lineDash Line dash.
+ * @api
+ */
+ol.style.Stroke.prototype.setLineDash = function(lineDash) {
+  this.lineDash_ = lineDash;
+  this.checksum_ = undefined;
+};
+
+
+/**
+ * Set the line dash offset.
+ *
+ * @param {number|undefined} lineDashOffset Line dash offset.
+ * @api
+ */
+ol.style.Stroke.prototype.setLineDashOffset = function(lineDashOffset) {
+  this.lineDashOffset_ = lineDashOffset;
+  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;
+};
+
+
+/**
+ * @return {string} The checksum.
+ */
+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 {
+      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() : '-');
+  }
+
+  return this.checksum_;
+};
+
 goog.provide('ol.style.Style');
 
 goog.require('ol.asserts');
@@ -35338,6 +35675,12 @@ ol.style.Style = function(opt_options) {
    */
   this.image_ = options.image !== undefined ? options.image : null;
 
+  /**
+   * @private
+   * @type {ol.StyleRenderFunction|null}
+   */
+  this.renderer_ = options.renderer !== undefined ? options.renderer : null;
+
   /**
    * @private
    * @type {ol.style.Stroke}
@@ -35380,6 +35723,28 @@ ol.style.Style.prototype.clone = function() {
 };
 
 
+/**
+ * Get the custom renderer function that was configured with
+ * {@link #setRenderer} or the `renderer` constructor option.
+ * @return {ol.StyleRenderFunction|null} Custom renderer function.
+ * @api
+ */
+ol.style.Style.prototype.getRenderer = function() {
+  return this.renderer_;
+};
+
+
+/**
+ * Sets a custom renderer function for this style. When set, `fill`, `stroke`
+ * and `image` options of the style will be ignored.
+ * @param {ol.StyleRenderFunction|null} renderer Custom renderer function.
+ * @api
+ */
+ol.style.Style.prototype.setRenderer = function(renderer) {
+  this.renderer_ = renderer;
+};
+
+
 /**
  * Get the geometry to be rendered.
  * @return {string|ol.geom.Geometry|ol.StyleGeometryFunction}
@@ -35828,7 +36193,7 @@ ol.Feature.prototype.clone = function() {
  */
 ol.Feature.prototype.getGeometry = function() {
   return /** @type {ol.geom.Geometry|undefined} */ (
-      this.get(this.geometryName_));
+    this.get(this.geometryName_));
 };
 
 
@@ -35928,7 +36293,7 @@ ol.Feature.prototype.setGeometry = function(geometry) {
 ol.Feature.prototype.setStyle = function(style) {
   this.style_ = style;
   this.styleFunction_ = !style ?
-      undefined : ol.Feature.createStyleFunction(style);
+    undefined : ol.Feature.createStyleFunction(style);
   this.changed();
 };
 
@@ -36145,18 +36510,18 @@ ol.xml.parse = function(xml) {
  */
 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.<*>} */
+    /**
+     * @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);
-        }
-      });
+        ol.array.extend(array, value);
+      }
+    });
 };
 
 
@@ -36170,18 +36535,18 @@ ol.xml.makeArrayExtender = function(valueReader, opt_this) {
  */
 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);
-        }
-      });
+    /**
+     * @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);
+      }
+    });
 };
 
 
@@ -36195,17 +36560,17 @@ ol.xml.makeArrayPusher = function(valueReader, opt_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;
-        }
-      });
+    /**
+     * @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;
+      }
+    });
 };
 
 
@@ -36220,27 +36585,27 @@ ol.xml.makeReplacer = function(valueReader, opt_this) {
  */
 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} */
+    /**
+     * @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);
+        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);
+      }
+    });
 };
 
 
@@ -36254,21 +36619,21 @@ ol.xml.makeObjectPropertyPusher = function(valueReader, opt_property, opt_this)
  */
 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} */
+    /**
+     * @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;
-        }
-      });
+        var property = opt_property !== undefined ?
+          opt_property : node.localName;
+        object[property] = value;
+      }
+    });
 };
 
 
@@ -36337,25 +36702,25 @@ ol.xml.makeArraySerializer = function(nodeWriter, opt_this) {
 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));
+    /**
+     * @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));
+    }
   );
 };
 
@@ -36537,7 +36902,7 @@ goog.require('ol.xml');
 /**
  * @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
+ * @param {function(this:ol.VectorTile, Array.<ol.Feature>, ol.proj.Projection, ol.Extent)|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
@@ -36547,60 +36912,60 @@ goog.require('ol.xml');
  */
 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';
+      }
       /**
-       * @param {ol.Extent} extent Extent.
-       * @param {number} resolution Resolution.
-       * @param {ol.proj.Projection} projection Projection.
-       * @this {ol.source.Vector|ol.VectorTile}
+       * @param {Event} event Event.
+       * @private
        */
-      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';
-        }
-        /**
-         * @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 ||
+      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);
+            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), format.getLastExtent());
           } else {
             failure.call(this);
           }
-        }.bind(this);
-        /**
-         * @private
-         */
-        xhr.onerror = function() {
+        } else {
           failure.call(this);
-        }.bind(this);
-        xhr.send();
-      });
+        }
+      }.bind(this);
+      /**
+       * @private
+       */
+      xhr.onerror = function() {
+        failure.call(this);
+      }.bind(this);
+      xhr.send();
+    });
 };
 
 
@@ -36674,7 +37039,7 @@ ol.format.Feature.prototype.getReadOptions = function(source, opt_options) {
   if (opt_options) {
     options = {
       dataProjection: opt_options.dataProjection ?
-          opt_options.dataProjection : this.readProjection(source),
+        opt_options.dataProjection : this.readProjection(source),
       featureProjection: opt_options.featureProjection
     };
   }
@@ -36699,6 +37064,15 @@ ol.format.Feature.prototype.adaptOptions = function(options) {
 };
 
 
+/**
+ * Get the extent from the source of the last {@link readFeatures} call.
+ * @return {ol.Extent} Tile extent.
+ */
+ol.format.Feature.prototype.getLastExtent = function() {
+  return null;
+};
+
+
 /**
  * @abstract
  * @return {ol.format.FormatType} Format.
@@ -36793,9 +37167,9 @@ ol.format.Feature.prototype.writeGeometry = function(geometry, opt_options) {};
 ol.format.Feature.transformWithOptions = function(
     geometry, write, opt_options) {
   var featureProjection = opt_options ?
-      ol.proj.get(opt_options.featureProjection) : null;
+    ol.proj.get(opt_options.featureProjection) : null;
   var dataProjection = opt_options ?
-      ol.proj.get(opt_options.dataProjection) : null;
+    ol.proj.get(opt_options.dataProjection) : null;
   /**
    * @type {ol.geom.Geometry|ol.Extent}
    */
@@ -37190,48 +37564,6 @@ ol.geom.flat.interpolate.lineStringsCoordinateAtM = function(
   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('ol');
@@ -37919,7 +38251,7 @@ ol.geom.MultiPoint.prototype.getCoordinates = function() {
  */
 ol.geom.MultiPoint.prototype.getPoint = function(index) {
   var n = !this.flatCoordinates ?
-      0 : this.flatCoordinates.length / this.stride;
+    0 : this.flatCoordinates.length / this.stride;
   if (index < 0 || n <= index) {
     return null;
   }
@@ -38261,12 +38593,13 @@ ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() {
 
 /**
  * Return the interior points as {@link ol.geom.MultiPoint multipoint}.
- * @return {ol.geom.MultiPoint} Interior points.
+ * @return {ol.geom.MultiPoint} Interior points as XYM coordinates, where M is
+ * the length of the horizontal intersection that the point belongs to.
  * @api
  */
 ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
   var interiorPoints = new ol.geom.MultiPoint(null);
-  interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY,
+  interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XYM,
       this.getFlatInteriorPoints().slice());
   return interiorPoints;
 };
@@ -38413,7 +38746,7 @@ ol.geom.MultiPolygon.prototype.setCoordinates = function(coordinates, opt_layout
     } else {
       var lastEnds = endss[endss.length - 1];
       this.flatCoordinates.length = lastEnds.length === 0 ?
-          0 : lastEnds[lastEnds.length - 1];
+        0 : lastEnds[lastEnds.length - 1];
     }
     this.changed();
   }
@@ -38542,8 +38875,8 @@ ol.format.EsriJSON.readGeometry_ = function(object, opt_options) {
   }
   var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type];
   return /** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(
-          geometryReader(object), false, opt_options));
+    ol.format.Feature.transformWithOptions(
+        geometryReader(object), false, opt_options));
 };
 
 
@@ -38580,9 +38913,11 @@ ol.format.EsriJSON.convertRings_ = function(rings, layout) {
     // 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())) {
+      var containsHole = ol.extent.containsExtent(
+          new ol.geom.LinearRing(outerRing).getExtent(),
+          new ol.geom.LinearRing(hole).getExtent()
+      );
+      if (containsHole) {
         // the hole is contained push it into our polygon
         outerRings[i].push(hole);
         matched = true;
@@ -38747,9 +39082,9 @@ ol.format.EsriJSON.getHasZM_ = function(geometry) {
   var layout = geometry.getLayout();
   return {
     hasZ: (layout === ol.geom.GeometryLayout.XYZ ||
-        layout === ol.geom.GeometryLayout.XYZM),
+      layout === ol.geom.GeometryLayout.XYZM),
     hasM: (layout === ol.geom.GeometryLayout.XYM ||
-        layout === ol.geom.GeometryLayout.XYZM)
+      layout === ol.geom.GeometryLayout.XYZM)
   };
 };
 
@@ -38761,7 +39096,7 @@ ol.format.EsriJSON.getHasZM_ = function(geometry) {
  * @return {EsriJSONPolyline} EsriJSON geometry.
  */
 ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.LineString} */ (geometry));
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.LineString} */(geometry));
   return /** @type {EsriJSONPolyline} */ ({
     hasZ: hasZM.hasZ,
     hasM: hasZM.hasM,
@@ -38780,7 +39115,7 @@ ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
  */
 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));
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.Polygon} */(geometry));
   return /** @type {EsriJSONPolygon} */ ({
     hasZ: hasZM.hasZ,
     hasM: hasZM.hasM,
@@ -38796,7 +39131,7 @@ ol.format.EsriJSON.writePolygonGeometry_ = function(geometry, opt_options) {
  * @return {EsriJSONPolyline} EsriJSON geometry.
  */
 ol.format.EsriJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiLineString} */ (geometry));
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiLineString} */(geometry));
   return /** @type {EsriJSONPolyline} */ ({
     hasZ: hasZM.hasZ,
     hasM: hasZM.hasM,
@@ -38812,7 +39147,7 @@ ol.format.EsriJSON.writeMultiLineStringGeometry_ = function(geometry, opt_option
  * @return {EsriJSONMultipoint} EsriJSON geometry.
  */
 ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPoint} */ (geometry));
+  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPoint} */(geometry));
   return /** @type {EsriJSONMultipoint} */ ({
     hasZ: hasZM.hasZ,
     hasM: hasZM.hasM,
@@ -38829,7 +39164,7 @@ ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
  */
 ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry,
     opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPolygon} */ (geometry));
+  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++) {
@@ -38852,17 +39187,17 @@ ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry,
  */
 ol.format.EsriJSON.GEOMETRY_READERS_ = {};
 ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POINT] =
-    ol.format.EsriJSON.readPointGeometry_;
+  ol.format.EsriJSON.readPointGeometry_;
 ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.LINE_STRING] =
-    ol.format.EsriJSON.readLineStringGeometry_;
+  ol.format.EsriJSON.readLineStringGeometry_;
 ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POLYGON] =
-    ol.format.EsriJSON.readPolygonGeometry_;
+  ol.format.EsriJSON.readPolygonGeometry_;
 ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POINT] =
-    ol.format.EsriJSON.readMultiPointGeometry_;
+  ol.format.EsriJSON.readMultiPointGeometry_;
 ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_LINE_STRING] =
-    ol.format.EsriJSON.readMultiLineStringGeometry_;
+  ol.format.EsriJSON.readMultiLineStringGeometry_;
 ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POLYGON] =
-    ol.format.EsriJSON.readMultiPolygonGeometry_;
+  ol.format.EsriJSON.readMultiPolygonGeometry_;
 
 
 /**
@@ -38872,17 +39207,17 @@ ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POLYGON] =
  */
 ol.format.EsriJSON.GEOMETRY_WRITERS_ = {};
 ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POINT] =
-    ol.format.EsriJSON.writePointGeometry_;
+  ol.format.EsriJSON.writePointGeometry_;
 ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.LINE_STRING] =
-    ol.format.EsriJSON.writeLineStringGeometry_;
+  ol.format.EsriJSON.writeLineStringGeometry_;
 ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POLYGON] =
-    ol.format.EsriJSON.writePolygonGeometry_;
+  ol.format.EsriJSON.writePolygonGeometry_;
 ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POINT] =
-    ol.format.EsriJSON.writeMultiPointGeometry_;
+  ol.format.EsriJSON.writeMultiPointGeometry_;
 ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_LINE_STRING] =
-    ol.format.EsriJSON.writeMultiLineStringGeometry_;
+  ol.format.EsriJSON.writeMultiLineStringGeometry_;
 ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POLYGON] =
-    ol.format.EsriJSON.writeMultiPolygonGeometry_;
+  ol.format.EsriJSON.writeMultiPolygonGeometry_;
 
 
 /**
@@ -38925,9 +39260,9 @@ ol.format.EsriJSON.prototype.readFeatureFromObject = function(
   }
   feature.setGeometry(geometry);
   if (opt_options && opt_options.idField &&
-      esriJSONFeature.attributes[opt_options.idField]) {
+    esriJSONFeature.attributes[opt_options.idField]) {
     feature.setId(/** @type {number} */(
-        esriJSONFeature.attributes[opt_options.idField]));
+      esriJSONFeature.attributes[opt_options.idField]));
   }
   if (esriJSONFeature.attributes) {
     feature.setProperties(esriJSONFeature.attributes);
@@ -38945,7 +39280,7 @@ ol.format.EsriJSON.prototype.readFeaturesFromObject = function(
   var options = opt_options ? opt_options : {};
   if (esriJSONObject.features) {
     var esriJSONFeatureCollection = /** @type {EsriJSONFeatureCollection} */
-        (object);
+      (object);
     /** @type {Array.<ol.Feature>} */
     var features = [];
     var esriJSONFeatures = esriJSONFeatureCollection.features;
@@ -38980,7 +39315,7 @@ ol.format.EsriJSON.prototype.readGeometry;
 ol.format.EsriJSON.prototype.readGeometryFromObject = function(
     object, opt_options) {
   return ol.format.EsriJSON.readGeometry_(
-      /** @type {EsriJSONGeometry} */ (object), opt_options);
+      /** @type {EsriJSONGeometry} */(object), opt_options);
 };
 
 
@@ -39017,9 +39352,9 @@ ol.format.EsriJSON.prototype.readProjectionFromObject = function(object) {
  */
 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);
+  return geometryWriter(/** @type {ol.geom.Geometry} */(
+    ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
+  opt_options);
 };
 
 
@@ -39079,7 +39414,13 @@ ol.format.EsriJSON.prototype.writeFeatureObject = function(
   var geometry = feature.getGeometry();
   if (geometry) {
     object['geometry'] =
-        ol.format.EsriJSON.writeGeometry_(geometry, opt_options);
+      ol.format.EsriJSON.writeGeometry_(geometry, opt_options);
+    if (opt_options && opt_options.featureProjection) {
+      object['geometry']['spatialReference'] = /** @type {EsriJSONCRS} */({
+        wkid: ol.proj.get(
+            opt_options.featureProjection).getCode().split(':').pop()
+      });
+    }
   }
   var properties = feature.getProperties();
   delete properties[feature.getGeometryName()];
@@ -39088,12 +39429,6 @@ ol.format.EsriJSON.prototype.writeFeatureObject = function(
   } 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;
 };
 
@@ -39139,7 +39474,10 @@ goog.provide('ol.format.filter.Filter');
  * Abstract class; normally only used for creating subclasses and not instantiated in apps.
  * Base class for WFS GetFeature filters.
  *
+ * deprecated: This class will no longer be exported starting from the next major version.
+ *
  * @constructor
+ * @abstract
  * @param {!string} tagName The XML tag name for this filter.
  * @struct
  * @api
@@ -39174,6 +39512,7 @@ goog.require('ol.format.filter.Filter');
  * Base class for WFS GetFeature n-ary logical filters.
  *
  * @constructor
+ * @abstract
  * @param {!string} tagName The XML tag name for this filter.
  * @param {...ol.format.filter.Filter} conditions Conditions.
  * @extends {ol.format.filter.Filter}
@@ -39200,7 +39539,10 @@ goog.require('ol.format.filter.LogicalNary');
  * @classdesc
  * Represents a logical `<And>` operator between two or more filter conditions.
  *
+ * deprecated: This class will no longer be exported starting from the next major version.
+ *
  * @constructor
+ * @abstract
  * @param {...ol.format.filter.Filter} conditions Conditions.
  * @extends {ol.format.filter.LogicalNary}
  * @api
@@ -39254,6 +39596,80 @@ ol.format.filter.Bbox = function(geometryName, extent, opt_srsName) {
 };
 ol.inherits(ol.format.filter.Bbox, ol.format.filter.Filter);
 
+goog.provide('ol.format.filter.Spatial');
+
+goog.require('ol');
+goog.require('ol.format.filter.Filter');
+
+
+/**
+ * @classdesc
+ * Abstract class; normally only used for creating subclasses and not instantiated in apps.
+ * Represents a spatial operator to test whether a geometry-valued property
+ * relates to a given geometry.
+ *
+ * deprecated: This class will no longer be exported starting from the next major version.
+ *
+ * @constructor
+ * @abstract
+ * @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
+ */
+ol.format.filter.Spatial = function(tagName, geometryName, geometry, opt_srsName) {
+
+  ol.format.filter.Filter.call(this, tagName);
+
+  /**
+   * @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.Contains');
+
+goog.require('ol');
+goog.require('ol.format.filter.Spatial');
+
+
+/**
+ * @classdesc
+ * Represents a `<Contains>` operator to test whether a geometry-valued property
+ * contains 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.Contains = function(geometryName, geometry, opt_srsName) {
+
+  ol.format.filter.Spatial.call(this, 'Contains', geometryName, geometry, opt_srsName);
+
+};
+ol.inherits(ol.format.filter.Contains, ol.format.filter.Spatial);
+
 goog.provide('ol.format.filter.Comparison');
 
 goog.require('ol');
@@ -39265,7 +39681,10 @@ goog.require('ol.format.filter.Filter');
  * Abstract class; normally only used for creating subclasses and not instantiated in apps.
  * Base class for WFS GetFeature property comparison filters.
  *
+ * deprecated: This class will no longer be exported starting from the next major version.
+ *
  * @constructor
+ * @abstract
  * @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}
@@ -39328,7 +39747,10 @@ goog.require('ol.format.filter.Comparison');
  * Abstract class; normally only used for creating subclasses and not instantiated in apps.
  * Base class for WFS GetFeature property binary comparison filters.
  *
+ * deprecated: This class will no longer be exported starting from the next major version.
+ *
  * @constructor
+ * @abstract
  * @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.
@@ -39419,50 +39841,6 @@ ol.format.filter.GreaterThanOrEqualTo = function(propertyName, expression) {
 };
 ol.inherits(ol.format.filter.GreaterThanOrEqualTo, ol.format.filter.ComparisonBinary);
 
-goog.provide('ol.format.filter.Spatial');
-
-goog.require('ol');
-goog.require('ol.format.filter.Filter');
-
-
-/**
- * @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
- */
-ol.format.filter.Spatial = function(tagName, geometryName, geometry, opt_srsName) {
-
-  ol.format.filter.Filter.call(this, tagName);
-
-  /**
-   * @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');
@@ -39744,6 +40122,7 @@ goog.provide('ol.format.filter');
 
 goog.require('ol.format.filter.And');
 goog.require('ol.format.filter.Bbox');
+goog.require('ol.format.filter.Contains');
 goog.require('ol.format.filter.During');
 goog.require('ol.format.filter.EqualTo');
 goog.require('ol.format.filter.GreaterThan');
@@ -39813,6 +40192,21 @@ ol.format.filter.bbox = function(geometryName, extent, opt_srsName) {
   return new ol.format.filter.Bbox(geometryName, extent, opt_srsName);
 };
 
+/**
+ * Create a `<Contains>` operator to test whether a geometry-valued property
+ * contains 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.Contains} `<Contains>` operator.
+ * @api
+ */
+ol.format.filter.contains = function(geometryName, geometry, opt_srsName) {
+  return new ol.format.filter.Contains(geometryName, geometry, opt_srsName);
+};
+
 /**
  * Create a `<Intersects>` operator to test whether a geometry-valued property
  * intersects a given geometry.
@@ -39971,7 +40365,7 @@ ol.format.filter.between = function(propertyName, lowerBoundary, upperBoundary)
 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);
+      opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase);
 };
 
 
@@ -40355,7 +40749,7 @@ ol.format.GeoJSON = function(opt_options) {
    */
   this.defaultDataProjection = ol.proj.get(
       options.defaultDataProjection ?
-          options.defaultDataProjection : 'EPSG:4326');
+        options.defaultDataProjection : 'EPSG:4326');
 
 
   if (options.featureProjection) {
@@ -40369,6 +40763,13 @@ ol.format.GeoJSON = function(opt_options) {
    */
   this.geometryName_ = options.geometryName;
 
+  /**
+   * Look for the geometry name in the feature GeoJSON
+   * @type {boolean|undefined}
+   * @private
+   */
+  this.extractGeometryName_ = options.extractGeometryName;
+
 };
 ol.inherits(ol.format.GeoJSON, ol.format.JSONFeature);
 
@@ -40385,8 +40786,8 @@ ol.format.GeoJSON.readGeometry_ = function(object, opt_options) {
   }
   var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type];
   return /** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(
-          geometryReader(object), false, opt_options));
+    ol.format.Feature.transformWithOptions(
+        geometryReader(object), false, opt_options));
 };
 
 
@@ -40479,8 +40880,8 @@ ol.format.GeoJSON.readPolygonGeometry_ = function(object) {
 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);
+    ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
+  opt_options);
 };
 
 
@@ -40693,6 +41094,8 @@ ol.format.GeoJSON.prototype.readFeatureFromObject = function(
   var feature = new ol.Feature();
   if (this.geometryName_) {
     feature.setGeometryName(this.geometryName_);
+  } else if (this.extractGeometryName_ && geoJSONFeature.geometry_name !== undefined) {
+    feature.setGeometryName(geoJSONFeature.geometry_name);
   }
   feature.setGeometry(geometry);
   if (geoJSONFeature.id !== undefined) {
@@ -40773,12 +41176,6 @@ ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) {
   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
     }
@@ -41336,12 +41733,12 @@ ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) {
       var parsers = {};
       for (i = 0, ii = featureTypes.length; i < ii; ++i) {
         var featurePrefix = featureTypes[i].indexOf(':') === -1 ?
-            defaultPrefix : featureTypes[i].split(':')[0];
+          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);
+                ol.xml.makeArrayPusher(this.readFeatureElement, this) :
+                ol.xml.makeReplacer(this.readFeatureElement, this);
         }
       }
       parsersNS[featureNS[p]] = parsers;
@@ -41367,12 +41764,13 @@ ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) {
 ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) {
   var context = /** @type {Object} */ (objectStack[0]);
   context['srsName'] = node.firstElementChild.getAttribute('srsName');
+  context['srsDimension'] = node.firstElementChild.getAttribute('srsDimension');
   /** @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));
+      ol.format.Feature.transformWithOptions(geometry, false, context));
   } else {
     return undefined;
   }
@@ -41759,7 +42157,7 @@ ol.format.GMLBase.prototype.readFeaturesFromNode = function(node, opt_options) {
  */
 ol.format.GMLBase.prototype.readProjectionFromNode = function(node) {
   return ol.proj.get(this.srsName ? this.srsName :
-      node.firstElementChild.getAttribute('srsName'));
+    node.firstElementChild.getAttribute('srsName'));
 };
 
 goog.provide('ol.format.XSD');
@@ -41984,20 +42382,27 @@ ol.format.GML3 = function(opt_options) {
    * @type {boolean}
    */
   this.multiCurve_ = options.multiCurve !== undefined ?
-      options.multiCurve : true;
+    options.multiCurve : true;
 
   /**
    * @private
    * @type {boolean}
    */
   this.multiSurface_ = options.multiSurface !== undefined ?
-      options.multiSurface : true;
+    options.multiSurface : true;
 
   /**
    * @inheritDoc
    */
   this.schemaLocation = options.schemaLocation ?
-      options.schemaLocation : ol.format.GML3.schemaLocation_;
+    options.schemaLocation : ol.format.GML3.schemaLocation_;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.hasZ = options.hasZ !== undefined ?
+    options.hasZ : false;
 
 };
 ol.inherits(ol.format.GML3, ol.format.GMLBase);
@@ -42277,7 +42682,7 @@ 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 contextDimension = context['srsDimension'];
   var axisOrientation = 'enu';
   if (containerSrs) {
     var proj = ol.proj.get(containerSrs);
@@ -42292,8 +42697,11 @@ ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) {
   } else if (node.getAttribute('dimension')) {
     dim = ol.format.XSD.readNonNegativeIntegerString(
         node.getAttribute('dimension'));
-  } else if (containerDimension) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
+  } else if (node.parentNode.getAttribute('srsDimension')) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(
+        node.parentNode.getAttribute('srsDimension'));
+  } else if (contextDimension) {
+    dim = ol.format.XSD.readNonNegativeIntegerString(contextDimension);
   }
   var x, y, z;
   var flatCoordinates = [];
@@ -42498,6 +42906,8 @@ ol.format.GML3.prototype.SEGMENTS_PARSERS_ = {
 ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) {
   var context = objectStack[objectStack.length - 1];
   var hasZ = context['hasZ'];
+  var srsDimension = hasZ ? 3 : 2;
+  node.setAttribute('srsDimension', srsDimension);
   var srsName = context['srsName'];
   var axisOrientation = 'enu';
   if (srsName) {
@@ -42533,8 +42943,8 @@ ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName, opt_hasZ) {
     axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
   }
   var coords = ((axisOrientation.substr(0, 2) === 'en') ?
-      point[0] + ' ' + point[1] :
-      point[1] + ' ' + point[0]);
+    point[0] + ' ' + point[1] :
+    point[1] + ' ' + point[0]);
   if (opt_hasZ) {
     // For newly created points, Z can be undefined.
     var z = point[2] || 0;
@@ -42554,6 +42964,8 @@ ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName, opt_hasZ) {
 ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) {
   var context = objectStack[objectStack.length - 1];
   var hasZ = context['hasZ'];
+  var srsDimension = hasZ ? 3 : 2;
+  node.setAttribute('srsDimension', srsDimension);
   var srsName = context['srsName'];
   // only 2d for simple features profile
   var points = value.getCoordinates();
@@ -43254,7 +43666,7 @@ ol.format.GML2 = function(opt_options) {
    * @inheritDoc
    */
   this.schemaLocation = options.schemaLocation ?
-      options.schemaLocation : ol.format.GML2.schemaLocation_;
+    options.schemaLocation : ol.format.GML2.schemaLocation_;
 
 };
 ol.inherits(ol.format.GML2, ol.format.GMLBase);
@@ -43688,8 +44100,8 @@ ol.format.GML2.prototype.getCoords_ = function(point, opt_srsName, opt_hasZ) {
     axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
   }
   var coords = ((axisOrientation.substr(0, 2) === 'en') ?
-      point[0] + ',' + point[1] :
-      point[1] + ',' + point[0]);
+    point[0] + ',' + point[1] :
+    point[1] + ',' + point[0]);
   if (opt_hasZ) {
     // For newly created points, Z can be undefined.
     var z = point[2] || 0;
@@ -44604,8 +45016,8 @@ ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) {
       // pass
   }
   var orderedKeys = (node.nodeName == 'rtept') ?
-      ol.format.GPX.RTEPT_TYPE_SEQUENCE_[namespaceURI] :
-      ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI];
+    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}),
@@ -44627,7 +45039,7 @@ ol.format.GPX.writeRte_ = function(node, feature, objectStack) {
   var geometry = feature.getGeometry();
   if (geometry) {
     geometry = /** @type {ol.geom.LineString} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
+      (ol.format.Feature.transformWithOptions(geometry, true, options));
     context['geometryLayout'] = geometry.getLayout();
     properties['rtept'] = geometry.getCoordinates();
   }
@@ -44654,7 +45066,7 @@ ol.format.GPX.writeTrk_ = function(node, feature, objectStack) {
   var geometry = feature.getGeometry();
   if (geometry) {
     geometry = /** @type {ol.geom.MultiLineString} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
+      (ol.format.Feature.transformWithOptions(geometry, true, options));
     properties['trkseg'] = geometry.getLineStrings();
   }
   var parentNode = objectStack[objectStack.length - 1].node;
@@ -44695,7 +45107,7 @@ ol.format.GPX.writeWpt_ = function(node, feature, objectStack) {
   var geometry = feature.getGeometry();
   if (geometry) {
     geometry = /** @type {ol.geom.Point} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
+      (ol.format.Feature.transformWithOptions(geometry, true, options));
     context['geometryLayout'] = geometry.getLayout();
     ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack);
   }
@@ -45169,7 +45581,7 @@ ol.format.IGC = function(opt_options) {
    * @type {ol.format.IGCZ}
    */
   this.altitudeMode_ = options.altitudeMode ?
-      options.altitudeMode : ol.format.IGCZ.NONE;
+    options.altitudeMode : ol.format.IGCZ.NONE;
 
 };
 ol.inherits(ol.format.IGC, ol.format.TextFeature);
@@ -45292,7 +45704,7 @@ ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) {
   }
   var lineString = new ol.geom.LineString(null);
   var layout = altitudeMode == ol.format.IGCZ.NONE ?
-      ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM;
+    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));
@@ -45398,7 +45810,7 @@ goog.require('ol.style');
  * @extends {ol.events.EventTarget}
  */
 ol.style.IconImage = function(image, src, size, crossOrigin, imageState,
-                               color) {
+    color) {
 
   ol.events.EventTarget.call(this);
 
@@ -45423,8 +45835,8 @@ ol.style.IconImage = function(image, src, size, crossOrigin, imageState,
    * @type {HTMLCanvasElement}
    */
   this.canvas_ = color ?
-      /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) :
-      null;
+    /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) :
+    null;
 
   /**
    * @private
@@ -45479,7 +45891,7 @@ ol.inherits(ol.style.IconImage, ol.events.EventTarget);
  * @return {ol.style.IconImage} Icon image.
  */
 ol.style.IconImage.get = function(image, src, size, crossOrigin, imageState,
-                                   color) {
+    color) {
   var iconImageCache = ol.style.iconImageCache;
   var iconImage = iconImageCache.get(src, crossOrigin, color);
   if (!iconImage) {
@@ -45710,21 +46122,21 @@ ol.style.Icon = function(opt_options) {
    * @type {ol.style.IconOrigin}
    */
   this.anchorOrigin_ = options.anchorOrigin !== undefined ?
-      options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;
+    options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;
 
   /**
    * @private
    * @type {ol.style.IconAnchorUnits}
    */
   this.anchorXUnits_ = options.anchorXUnits !== undefined ?
-      options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;
+    options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;
 
   /**
    * @private
    * @type {ol.style.IconAnchorUnits}
    */
   this.anchorYUnits_ = options.anchorYUnits !== undefined ?
-      options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;
+    options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;
 
   /**
    * @private
@@ -45763,14 +46175,14 @@ ol.style.Icon = function(opt_options) {
    * @type {ol.ImageState}
    */
   var imageState = options.src !== undefined ?
-      ol.ImageState.IDLE : ol.ImageState.LOADED;
+    ol.ImageState.IDLE : ol.ImageState.LOADED;
 
   /**
    * @private
    * @type {ol.Color}
    */
   this.color_ = options.color !== undefined ? ol.color.asArray(options.color) :
-      null;
+    null;
 
   /**
    * @private
@@ -45790,7 +46202,7 @@ ol.style.Icon = function(opt_options) {
    * @type {ol.style.IconOrigin}
    */
   this.offsetOrigin_ = options.offsetOrigin !== undefined ?
-      options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT;
+    options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT;
 
   /**
    * @private
@@ -45813,7 +46225,7 @@ ol.style.Icon = function(opt_options) {
    * @type {boolean}
    */
   var rotateWithView = options.rotateWithView !== undefined ?
-      options.rotateWithView : false;
+    options.rotateWithView : false;
 
   /**
    * @type {number}
@@ -45829,7 +46241,7 @@ ol.style.Icon = function(opt_options) {
    * @type {boolean}
    */
   var snapToPixel = options.snapToPixel !== undefined ?
-      options.snapToPixel : true;
+    options.snapToPixel : true;
 
   ol.style.Image.call(this, {
     opacity: opacity,
@@ -45844,24 +46256,11 @@ ol.inherits(ol.style.Icon, ol.style.Image);
 
 
 /**
- * Clones the style.
+ * Clones the style. The underlying Image/HTMLCanvasElement is not cloned.
  * @return {ol.style.Icon} The cloned style.
  * @api
  */
 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_,
@@ -45869,9 +46268,7 @@ ol.style.Icon.prototype.clone = function() {
     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(),
+    src: this.getSrc(),
     offset: this.offset_.slice(),
     offsetOrigin: this.offsetOrigin_,
     size: this.size_ !== null ? this.size_.slice() : undefined,
@@ -46067,6 +46464,7 @@ goog.provide('ol.style.Text');
 
 
 goog.require('ol.style.Fill');
+goog.require('ol.style.TextPlacement');
 
 
 /**
@@ -46128,7 +46526,28 @@ ol.style.Text = function(opt_options) {
    * @type {ol.style.Fill}
    */
   this.fill_ = options.fill !== undefined ? options.fill :
-      new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});
+    new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.maxAngle_ = options.maxAngle !== undefined ? options.maxAngle : Math.PI / 4;
+
+  /**
+   * @private
+   * @type {ol.style.TextPlacement|string}
+   */
+  this.placement_ = options.placement !== undefined ? options.placement : ol.style.TextPlacement.POINT;
+
+  //TODO Use options.overflow directly after removing @deprecated exceedLength
+  var overflow = options.overflow === undefined ? options.exceedLength : options.overflow;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.overflow_ = overflow !== undefined ? overflow : false;
 
   /**
    * @private
@@ -46147,6 +46566,24 @@ ol.style.Text = function(opt_options) {
    * @type {number}
    */
   this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0;
+
+  /**
+   * @private
+   * @type {ol.style.Fill}
+   */
+  this.backgroundFill_ = options.backgroundFill ? options.backgroundFill : null;
+
+  /**
+   * @private
+   * @type {ol.style.Stroke}
+   */
+  this.backgroundStroke_ = options.backgroundStroke ? options.backgroundStroke : null;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.padding_ = options.padding === undefined ? null : options.padding;
 };
 
 
@@ -46168,6 +46605,9 @@ ol.style.Text.DEFAULT_FILL_COLOR_ = '#333';
 ol.style.Text.prototype.clone = function() {
   return new ol.style.Text({
     font: this.getFont(),
+    placement: this.getPlacement(),
+    maxAngle: this.getMaxAngle(),
+    overflow: this.getOverflow(),
     rotation: this.getRotation(),
     rotateWithView: this.getRotateWithView(),
     scale: this.getScale(),
@@ -46182,6 +46622,16 @@ ol.style.Text.prototype.clone = function() {
 };
 
 
+/**
+ * Get the `overflow` configuration.
+ * @return {boolean} Let text overflow the length of the path they follow.
+ * @api
+ */
+ol.style.Text.prototype.getOverflow = function() {
+  return this.overflow_;
+};
+
+
 /**
  * Get the font name.
  * @return {string|undefined} Font.
@@ -46192,6 +46642,26 @@ ol.style.Text.prototype.getFont = function() {
 };
 
 
+/**
+ * Get the maximum angle between adjacent characters.
+ * @return {number} Angle in radians.
+ * @api
+ */
+ol.style.Text.prototype.getMaxAngle = function() {
+  return this.maxAngle_;
+};
+
+
+/**
+ * Get the label placement.
+ * @return {ol.style.TextPlacement|string} Text placement.
+ * @api
+ */
+ol.style.Text.prototype.getPlacement = function() {
+  return this.placement_;
+};
+
+
 /**
  * Get the x-offset for the text.
  * @return {number} Horizontal text offset.
@@ -46292,6 +46762,47 @@ ol.style.Text.prototype.getTextBaseline = function() {
 };
 
 
+/**
+ * Get the background fill style for the text.
+ * @return {ol.style.Fill} Fill style.
+ * @api
+ */
+ol.style.Text.prototype.getBackgroundFill = function() {
+  return this.backgroundFill_;
+};
+
+
+/**
+ * Get the background stroke style for the text.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.getBackgroundStroke = function() {
+  return this.backgroundStroke_;
+};
+
+
+/**
+ * Get the padding for the text.
+ * @return {Array.<number>} Padding.
+ * @api
+ */
+ol.style.Text.prototype.getPadding = function() {
+  return this.padding_;
+};
+
+
+/**
+ * Set the `overflow` property.
+ *
+ * @param {boolean} overflow Let text overflow the path that it follows.
+ * @api
+ */
+ol.style.Text.prototype.setOverflow = function(overflow) {
+  this.overflow_ = overflow;
+};
+
+
 /**
  * Set the font.
  *
@@ -46303,6 +46814,17 @@ ol.style.Text.prototype.setFont = function(font) {
 };
 
 
+/**
+ * Set the maximum angle between adjacent characters.
+ *
+ * @param {number} maxAngle Angle in radians.
+ * @api
+ */
+ol.style.Text.prototype.setMaxAngle = function(maxAngle) {
+  this.maxAngle_ = maxAngle;
+};
+
+
 /**
  * Set the x offset.
  *
@@ -46325,6 +46847,17 @@ ol.style.Text.prototype.setOffsetY = function(offsetY) {
 };
 
 
+/**
+ * Set the text placement.
+ *
+ * @param {ol.style.TextPlacement|string} placement Placement.
+ * @api
+ */
+ol.style.Text.prototype.setPlacement = function(placement) {
+  this.placement_ = placement;
+};
+
+
 /**
  * Set the fill.
  *
@@ -46401,6 +46934,39 @@ ol.style.Text.prototype.setTextBaseline = function(textBaseline) {
   this.textBaseline_ = textBaseline;
 };
 
+
+/**
+ * Set the background fill.
+ *
+ * @param {ol.style.Fill} fill Fill style.
+ * @api
+ */
+ol.style.Text.prototype.setBackgroundFill = function(fill) {
+  this.backgroundFill_ = fill;
+};
+
+
+/**
+ * Set the background stroke.
+ *
+ * @param {ol.style.Stroke} stroke Stroke style.
+ * @api
+ */
+ol.style.Text.prototype.setBackgroundStroke = function(stroke) {
+  this.backgroundStroke_ = stroke;
+};
+
+
+/**
+ * Set the padding (`[top, right, bottom, left]`).
+ *
+ * @param {!Array.<number>} padding Padding.
+ * @api
+ */
+ol.style.Text.prototype.setPadding = function(padding) {
+  this.padding_ = padding;
+};
+
 // FIXME http://earth.google.com/kml/1.0 namespace?
 // FIXME why does node.getAttribute return an unknown type?
 // FIXME serialize arbitrary feature properties
@@ -46469,21 +47035,21 @@ ol.format.KML = function(opt_options) {
    * @type {Array.<ol.style.Style>}
    */
   this.defaultStyle_ = options.defaultStyle ?
-      options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_;
+    options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_;
 
   /**
    * @private
    * @type {boolean}
    */
   this.extractStyles_ = options.extractStyles !== undefined ?
-      options.extractStyles : true;
+    options.extractStyles : true;
 
   /**
    * @private
    * @type {boolean}
    */
   this.writeStyles_ = options.writeStyles !== undefined ?
-      options.writeStyles : true;
+    options.writeStyles : true;
 
   /**
    * @private
@@ -46496,7 +47062,7 @@ ol.format.KML = function(opt_options) {
    * @type {boolean}
    */
   this.showPointNames_ = options.showPointNames !== undefined ?
-      options.showPointNames : true;
+    options.showPointNames : true;
 
 };
 ol.inherits(ol.format.KML, ol.format.XMLFeature);
@@ -46756,53 +47322,53 @@ 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() ===
+    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 (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 (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_(defaultStyle[0],
+          nameStyle = ol.format.KML.createNameStyleFunction_(foundStyle[0],
               name);
-          return defaultStyle.concat(nameStyle);
+          return foundStyle.concat(nameStyle);
         }
-        return defaultStyle;
-      });
+        return foundStyle;
+      }
+      if (drawName) {
+        nameStyle = ol.format.KML.createNameStyleFunction_(defaultStyle[0],
+            name);
+        return defaultStyle.concat(nameStyle);
+      }
+      return defaultStyle;
+    });
 };
 
 
@@ -46891,8 +47457,12 @@ ol.format.KML.readFlatCoordinates_ = function(node) {
  */
 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);
+  var baseURI = node.baseURI;
+  if (!baseURI || baseURI == 'about:blank') {
+    baseURI = window.location.href;
+  }
+  if (baseURI) {
+    var url = new URL(s, baseURI);
     return url.href;
   } else {
     return s;
@@ -46952,7 +47522,7 @@ ol.format.KML.readStyleMapValue_ = function(node, objectStack) {
   return ol.xml.pushParseAndPop(undefined,
       ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack);
 };
- /**
+/**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
@@ -47210,7 +47780,7 @@ ol.format.KML.readGxTrack_ = function(node, objectStack) {
   var whens = gxTrackObject.whens;
   var i, ii;
   for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii;
-       ++i) {
+    ++i) {
     flatCoordinates[4 * i + 3] = whens[i];
   }
   var lineString = new ol.geom.LineString(null);
@@ -47424,23 +47994,23 @@ ol.format.KML.readStyle_ = function(node, objectStack) {
   }
   var fillStyle = /** @type {ol.style.Fill} */
       ('fillStyle' in styleObject ?
-          styleObject['fillStyle'] : ol.format.KML.DEFAULT_FILL_STYLE_);
+        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_);
+        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_);
+        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_);
+        styleObject['strokeStyle'] : ol.format.KML.DEFAULT_STROKE_STYLE_);
   var outline = /** @type {boolean|undefined} */
       (styleObject['outline']);
   if (outline !== undefined && !outline) {
@@ -47468,19 +48038,25 @@ ol.format.KML.setCommonGeometryProperties_ = function(multiGeometry,
     geometries) {
   var ii = geometries.length;
   var extrudes = new Array(geometries.length);
+  var tessellates = new Array(geometries.length);
   var altitudeModes = new Array(geometries.length);
-  var geometry, i, hasExtrude, hasAltitudeMode;
-  hasExtrude = hasAltitudeMode = false;
+  var geometry, i, hasExtrude, hasTessellate, hasAltitudeMode;
+  hasExtrude = hasTessellate = hasAltitudeMode = false;
   for (i = 0; i < ii; ++i) {
     geometry = geometries[i];
     extrudes[i] = geometry.get('extrude');
+    tessellates[i] = geometry.get('tessellate');
     altitudeModes[i] = geometry.get('altitudeMode');
     hasExtrude = hasExtrude || extrudes[i] !== undefined;
+    hasTessellate = hasTessellate || tessellates[i] !== undefined;
     hasAltitudeMode = hasAltitudeMode || altitudeModes[i];
   }
   if (hasExtrude) {
     multiGeometry.set('extrude', extrudes);
   }
+  if (hasTessellate) {
+    multiGeometry.set('tessellate', tessellates);
+  }
   if (hasAltitudeMode) {
     multiGeometry.set('altitudeMode', altitudeModes);
   }
@@ -47495,13 +48071,13 @@ ol.format.KML.setCommonGeometryProperties_ = function(multiGeometry,
 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]);
+  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;
   }
+  delete featureObject['value'];
 };
 
 
@@ -47774,6 +48350,7 @@ ol.format.KML.LOD_PARSERS_ = ol.xml.makeStructureNS(
 ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_ = ol.xml.makeStructureNS(
     ol.format.KML.NAMESPACE_URIS_, {
       'extrude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
+      'tessellate': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
       'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
     });
 
@@ -48140,8 +48717,12 @@ ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) {
     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);
+      var baseURI = node.baseURI;
+      if (!baseURI || baseURI == 'about:blank') {
+        baseURI = window.location.href;
+      }
+      if (baseURI) {
+        var url = new URL('#' + id, baseURI);
         styleUri = url.href;
       } else {
         styleUri = '#' + id;
@@ -48167,8 +48748,12 @@ ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) {
     return;
   }
   var styleUri;
-  if (node.baseURI && node.baseURI !== 'about:blank') {
-    var url = new URL('#' + id, node.baseURI);
+  var baseURI = node.baseURI;
+  if (!baseURI || baseURI == 'about:blank') {
+    baseURI = window.location.href;
+  }
+  if (baseURI) {
+    var url = new URL('#' + id, baseURI);
     styleUri = url.href;
   } else {
     styleUri = '#' + id;
@@ -48547,16 +49132,16 @@ ol.format.KML.writeDataNode_ = function(node, pair, objectStack) {
   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']);
+          ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.displayName], objectStack, ['displayName']);
     }
 
     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']);
+          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']);
+        ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value], objectStack, ['value']);
   }
 };
 
@@ -48609,7 +49194,7 @@ ol.format.KML.writeExtendedData_ = function(node, namesAndValues, objectStack) {
 
   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);
+        ol.format.KML.DATA_NODE_FACTORY_, [{name: names[i], value: values[i]}], objectStack);
   }
 };
 
@@ -48663,7 +49248,7 @@ ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
       iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]);
     }
 
-    if (anchor && anchor[0] !== 0 && anchor[1] !== size[1]) {
+    if (anchor && (anchor[0] !== size[0] / 2 || anchor[1] !== size[1] / 2)) {
       var /** @type {ol.KMLVec2_} */ hotSpot = {
         x: anchor[0],
         xunits: ol.style.IconAnchorUnits.PIXELS,
@@ -48823,7 +49408,7 @@ ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
     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);
+        ol.format.KML.EXTENDEDDATA_NODE_FACTORY_, [namesAndValues], objectStack);
   }
 
   var styleFunction = feature.getStyleFunction();
@@ -48871,10 +49456,16 @@ ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) {
   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);
+
+  // serialize properties (properties unknown to KML are not serialized)
+  var properties = geometry.getProperties();
+  properties.coordinates = flatCoordinates;
+
+  var parentNode = objectStack[objectStack.length - 1].node;
+  var orderedKeys = ol.format.KML.PRIMITIVE_GEOMETRY_SEQUENCE_[parentNode.namespaceURI];
+  var values = ol.xml.makeSequence(properties, orderedKeys);
+  ol.xml.pushSerializeAndPop(context, ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_,
+      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
 };
 
 
@@ -49035,7 +49626,6 @@ ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = {
   'GeometryCollection': 'MultiGeometry'
 };
 
-
 /**
  * @const
  * @type {Object.<string, Array.<string>>}
@@ -49211,6 +49801,17 @@ ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
     });
 
 
+/**
+ * @const
+ * @type {Object.<string, Array.<string>>}
+ * @private
+ */
+ol.format.KML.PRIMITIVE_GEOMETRY_SEQUENCE_ = ol.xml.makeStructureNS(
+    ol.format.KML.NAMESPACE_URIS_, [
+      'extrude', 'tessellate', 'altitudeMode', 'coordinates'
+    ]);
+
+
 /**
  * @const
  * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
@@ -49218,6 +49819,9 @@ ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
  */
 ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
     ol.format.KML.NAMESPACE_URIS_, {
+      'extrude': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode),
+      'tessellate': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode),
+      'altitudeMode': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
       'coordinates': ol.xml.makeChildAppender(
           ol.format.KML.writeCoordinatesTextNode_)
     });
@@ -49329,16 +49933,6 @@ ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack,
 ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color');
 
 
-/**
- * A factory for creating coordinates nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.COORDINATES_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('coordinates');
-
-
 /**
  * A factory for creating Data nodes.
  * @const
@@ -49483,7 +50077,7 @@ ol.ext.PBF = function() {};
 
 var read = function (buffer, offset, isLE, mLen, nBytes) {
   var e, m;
-  var eLen = nBytes * 8 - mLen - 1;
+  var eLen = (nBytes * 8) - mLen - 1;
   var eMax = (1 << eLen) - 1;
   var eBias = eMax >> 1;
   var nBits = -7;
@@ -49494,11 +50088,11 @@ var read = function (buffer, offset, isLE, mLen, nBytes) {
   e = s & ((1 << (-nBits)) - 1);
   s >>= (-nBits);
   nBits += eLen;
-  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+  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) {}
+  for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}
   if (e === 0) {
     e = 1 - eBias;
   } else if (e === eMax) {
@@ -49511,7 +50105,7 @@ var read = function (buffer, offset, isLE, mLen, nBytes) {
 };
 var write = function (buffer, value, offset, isLE, mLen, nBytes) {
   var e, m, c;
-  var eLen = nBytes * 8 - mLen - 1;
+  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);
@@ -49541,7 +50135,7 @@ var write = function (buffer, value, offset, isLE, mLen, nBytes) {
       m = 0;
       e = eMax;
     } else if (e + eBias >= 1) {
-      m = (value * c - 1) * Math.pow(2, mLen);
+      m = ((value * c) - 1) * Math.pow(2, mLen);
       e = e + eBias;
     } else {
       m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
@@ -49554,12 +50148,12 @@ var write = function (buffer, value, offset, isLE, mLen, nBytes) {
   for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
   buffer[offset + i - d] |= s * 128;
 };
-var index$2 = {
+var ieee754 = {
 	read: read,
 	write: write
 };
 
-var index = Pbf;
+var pbf = Pbf;
 function Pbf(buf) {
     this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
     this.pos = 0;
@@ -49612,12 +50206,12 @@ Pbf.prototype = {
         return val;
     },
     readFloat: function() {
-        var val = index$2.read(this.buf, this.pos, true, 23, 4);
+        var val = ieee754.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);
+        var val = ieee754.read(this.buf, this.pos, true, 52, 8);
         this.pos += 8;
         return val;
     },
@@ -49787,12 +50381,12 @@ Pbf.prototype = {
     },
     writeFloat: function(val) {
         this.realloc(4);
-        index$2.write(this.buf, val, this.pos, true, 23, 4);
+        ieee754.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);
+        ieee754.write(this.buf, val, this.pos, true, 52, 8);
         this.pos += 8;
     },
     writeBytes: function(buffer) {
@@ -50070,415 +50664,28 @@ function writeUtf8(buf, str, pos) {
     return pos;
 }
 
-exports['default'] = index;
+exports['default'] = pbf;
 
 }((this.PBF = this.PBF || {})));}).call(ol.ext);
 ol.ext.PBF = ol.ext.PBF.default;
 
-
-/**
- * @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.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;
-};
-
-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;
-}
-
-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);
-};
-
-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.array');
 goog.require('ol.extent');
 goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.flat.center');
+goog.require('ol.geom.flat.interiorpoint');
+goog.require('ol.geom.flat.interpolate');
+goog.require('ol.geom.flat.transform');
+goog.require('ol.transform');
 
 
 /**
  * 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.
+ * structure, optimized for vector tile rendering and styling. Geometry access
+ * through the API is limited to getting the type and extent of the geometry.
  *
  * @constructor
  * @param {ol.geom.GeometryType} type Geometry type.
@@ -50513,929 +50720,2594 @@ ol.render.Feature = function(type, flatCoordinates, ends, properties, id) {
    */
   this.flatCoordinates_ = flatCoordinates;
 
-  /**
-   * @private
-   * @type {Array.<number>|Array.<Array.<number>>}
-   */
-  this.ends_ = ends;
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.flatInteriorPoints_ = null;
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.flatMidpoints_ = null;
+
+  /**
+   * @private
+   * @type {Array.<number>|Array.<Array.<number>>}
+   */
+  this.ends_ = ends;
+
+  /**
+   * @private
+   * @type {Object.<string, *>}
+   */
+  this.properties_ = properties;
+
+
+  /**
+   * @private
+   * @type {ol.Transform}
+   */
+  this.tmpTransform_ = ol.transform.create();
+};
+
+
+/**
+ * Get a feature property by its key.
+ * @param {string} key Key
+ * @return {*} Value for the requested key.
+ * @api
+ */
+ol.render.Feature.prototype.get = function(key) {
+  return this.properties_[key];
+};
+
+
+/**
+ * @return {Array.<number>|Array.<Array.<number>>} Ends or endss.
+ */
+ol.render.Feature.prototype.getEnds =
+ol.render.Feature.prototype.getEndss = function() {
+  return this.ends_;
+};
+
+
+/**
+ * Get the extent of this feature's geometry.
+ * @return {ol.Extent} Extent.
+ * @api
+ */
+ol.render.Feature.prototype.getExtent = function() {
+  if (!this.extent_) {
+    this.extent_ = this.type_ === ol.geom.GeometryType.POINT ?
+      ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) :
+      ol.extent.createOrUpdateFromFlatCoordinates(
+          this.flatCoordinates_, 0, this.flatCoordinates_.length, 2);
+
+  }
+  return this.extent_;
+};
+
+
+/**
+ * @return {Array.<number>} Flat interior points.
+ */
+ol.render.Feature.prototype.getFlatInteriorPoint = function() {
+  if (!this.flatInteriorPoints_) {
+    var flatCenter = ol.extent.getCenter(this.getExtent());
+    this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRings(
+        this.flatCoordinates_, 0, this.ends_, 2, flatCenter, 0);
+  }
+  return this.flatInteriorPoints_;
+};
+
+
+/**
+ * @return {Array.<number>} Flat interior points.
+ */
+ol.render.Feature.prototype.getFlatInteriorPoints = function() {
+  if (!this.flatInteriorPoints_) {
+    var flatCenters = ol.geom.flat.center.linearRingss(
+        this.flatCoordinates_, 0, this.ends_, 2);
+    this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss(
+        this.flatCoordinates_, 0, this.ends_, 2, flatCenters);
+  }
+  return this.flatInteriorPoints_;
+};
+
+
+/**
+ * @return {Array.<number>} Flat midpoint.
+ */
+ol.render.Feature.prototype.getFlatMidpoint = function() {
+  if (!this.flatMidpoints_) {
+    this.flatMidpoints_ = ol.geom.flat.interpolate.lineString(
+        this.flatCoordinates_, 0, this.flatCoordinates_.length, 2, 0.5);
+  }
+  return this.flatMidpoints_;
+};
+
+
+/**
+ * @return {Array.<number>} Flat midpoints.
+ */
+ol.render.Feature.prototype.getFlatMidpoints = function() {
+  if (!this.flatMidpoints_) {
+    this.flatMidpoints_ = [];
+    var flatCoordinates = this.flatCoordinates_;
+    var offset = 0;
+    var ends = this.ends_;
+    for (var i = 0, ii = ends.length; i < ii; ++i) {
+      var end = ends[i];
+      var midpoint = ol.geom.flat.interpolate.lineString(
+          flatCoordinates, offset, end, 2, 0.5);
+      ol.array.extend(this.flatMidpoints_, midpoint);
+      offset = end;
+    }
+  }
+  return this.flatMidpoints_;
+};
+
+/**
+ * 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.render.Feature.prototype.getId = function() {
+  return this.id_;
+};
+
+
+/**
+ * @return {Array.<number>} Flat coordinates.
+ */
+ol.render.Feature.prototype.getOrientedFlatCoordinates = function() {
+  return this.flatCoordinates_;
+};
+
+
+/**
+ * @return {Array.<number>} Flat coordinates.
+ */
+ol.render.Feature.prototype.getFlatCoordinates =
+    ol.render.Feature.prototype.getOrientedFlatCoordinates;
+
+
+/**
+ * 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.render.Feature.prototype.getGeometry = function() {
+  return this;
+};
+
+
+/**
+ * Get the feature properties.
+ * @return {Object.<string, *>} Feature properties.
+ * @api
+ */
+ol.render.Feature.prototype.getProperties = function() {
+  return this.properties_;
+};
+
+
+/**
+ * Get the feature for working with its geometry.
+ * @return {ol.render.Feature} Feature.
+ */
+ol.render.Feature.prototype.getSimplifiedGeometry =
+    ol.render.Feature.prototype.getGeometry;
+
+
+/**
+ * @return {number} Stride.
+ */
+ol.render.Feature.prototype.getStride = function() {
+  return 2;
+};
+
+
+/**
+ * @return {undefined}
+ */
+ol.render.Feature.prototype.getStyleFunction = ol.nullFunction;
+
+
+/**
+ * Get the type of this feature's geometry.
+ * @return {ol.geom.GeometryType} Geometry type.
+ * @api
+ */
+ol.render.Feature.prototype.getType = function() {
+  return this.type_;
+};
+
+/**
+ * Transform geometry coordinates from tile pixel space to projected.
+ * The SRS of the source and destination are expected to be the same.
+ *
+ * @param {ol.ProjectionLike} source The current projection
+ * @param {ol.ProjectionLike} destination The desired projection.
+ */
+ol.render.Feature.prototype.transform = function(source, destination) {
+  var pixelExtent = source.getExtent();
+  var projectedExtent = source.getWorldExtent();
+  var scale = ol.extent.getHeight(projectedExtent) / ol.extent.getHeight(pixelExtent);
+  var transform = this.tmpTransform_;
+  ol.transform.compose(transform,
+      projectedExtent[0], projectedExtent[3],
+      scale, -scale, 0,
+      0, 0);
+  ol.geom.flat.transform.transform2D(this.flatCoordinates_, 0, this.flatCoordinates_.length, 2,
+      transform, this.flatCoordinates_);
+};
+
+//FIXME Implement projection handling
+
+goog.provide('ol.format.MVT');
+
+goog.require('ol');
+goog.require('ol.asserts');
+goog.require('ol.ext.PBF');
+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.MultiPolygon');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.geom.flat.orient');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.render.Feature');
+
+
+/**
+ * @classdesc
+ * Feature format for reading data in the Mapbox MVT format.
+ *
+ * @constructor
+ * @extends {ol.format.Feature}
+ * @param {olx.format.MVTOptions=} opt_options Options.
+ * @api
+ */
+ol.format.MVT = function(opt_options) {
+
+  ol.format.Feature.call(this);
+
+  var options = opt_options ? opt_options : {};
+
+  /**
+   * @type {ol.proj.Projection}
+   */
+  this.defaultDataProjection = new ol.proj.Projection({
+    code: 'EPSG:3857',
+    units: ol.proj.Units.TILE_PIXELS
+  });
+
+  /**
+   * @private
+   * @type {function((ol.geom.Geometry|Object.<string,*>)=)|
+   *     function(ol.geom.GeometryType,Array.<number>,
+   *         (Array.<number>|Array.<Array.<number>>),Object.<string,*>,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;
+
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.extent_ = null;
+
+};
+ol.inherits(ol.format.MVT, ol.format.Feature);
+
+
+/**
+ * Reader callbacks for parsing the PBF.
+ * @type {Object.<string, function(number, Object, ol.ext.PBF)>}
+ */
+ol.format.MVT.pbfReaders_ = {
+  layers: function(tag, layers, pbf) {
+    if (tag === 3) {
+      var layer = {
+        keys: [],
+        values: [],
+        features: []
+      };
+      var end = pbf.readVarint() + pbf.pos;
+      pbf.readFields(ol.format.MVT.pbfReaders_.layer, layer, end);
+      layer.length = layer.features.length;
+      if (layer.length) {
+        layers[layer.name] = layer;
+      }
+    }
+  },
+  layer: function(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) {
+      var value = null;
+      var end = pbf.readVarint() + pbf.pos;
+      while (pbf.pos < end) {
+        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;
+      }
+      layer.values.push(value);
+    }
+  },
+  feature: function(tag, feature, pbf) {
+    if (tag == 1) {
+      feature.id = pbf.readVarint();
+    } else if (tag == 2) {
+      var end = pbf.readVarint() + pbf.pos;
+      while (pbf.pos < end) {
+        var key = feature.layer.keys[pbf.readVarint()];
+        var value = feature.layer.values[pbf.readVarint()];
+        feature.properties[key] = value;
+      }
+    } else if (tag == 3) {
+      feature.type = pbf.readVarint();
+    } else if (tag == 4) {
+      feature.geometry = pbf.pos;
+    }
+  }
+};
+
+
+/**
+ * Read a raw feature from the pbf offset stored at index `i` in the raw layer.
+ * @suppress {missingProperties}
+ * @private
+ * @param {ol.ext.PBF} pbf PBF.
+ * @param {Object} layer Raw layer.
+ * @param {number} i Index of the feature in the raw layer's `features` array.
+ * @return {Object} Raw feature.
+ */
+ol.format.MVT.readRawFeature_ = function(pbf, layer, i) {
+  pbf.pos = layer.features[i];
+  var end = pbf.readVarint() + pbf.pos;
+
+  var feature = {
+    layer: layer,
+    type: 0,
+    properties: {}
+  };
+  pbf.readFields(ol.format.MVT.pbfReaders_.feature, feature, end);
+  return feature;
+};
+
+
+/**
+ * Read the raw geometry from the pbf offset stored in a raw feature's geometry
+ * proeprty.
+ * @suppress {missingProperties}
+ * @private
+ * @param {ol.ext.PBF} pbf PBF.
+ * @param {Object} feature Raw feature.
+ * @param {Array.<number>} flatCoordinates Array to store flat coordinates in.
+ * @param {Array.<number>} ends Array to store ends in.
+ */
+ol.format.MVT.readRawGeometry_ = function(pbf, feature, flatCoordinates, ends) {
+  pbf.pos = feature.geometry;
+
+  var end = pbf.readVarint() + pbf.pos;
+  var cmd = 1;
+  var length = 0;
+  var x = 0;
+  var y = 0;
+  var coordsLen = 0;
+  var currentEnd = 0;
+
+  while (pbf.pos < end) {
+    if (!length) {
+      var cmdLen = pbf.readVarint();
+      cmd = cmdLen & 0x7;
+      length = cmdLen >> 3;
+    }
+
+    length--;
+
+    if (cmd === 1 || cmd === 2) {
+      x += pbf.readSVarint();
+      y += pbf.readSVarint();
+
+      if (cmd === 1) { // moveTo
+        if (coordsLen > currentEnd) {
+          ends.push(coordsLen);
+          currentEnd = coordsLen;
+        }
+      }
+
+      flatCoordinates.push(x, y);
+      coordsLen += 2;
+
+    } else if (cmd === 7) {
+
+      if (coordsLen > currentEnd) {
+        // close polygon
+        flatCoordinates.push(
+            flatCoordinates[currentEnd], flatCoordinates[currentEnd + 1]);
+        coordsLen += 2;
+      }
+
+    } else {
+      ol.asserts.assert(false, 59); // Invalid command found in the PBF
+    }
+  }
+
+  if (coordsLen > currentEnd) {
+    ends.push(coordsLen);
+    currentEnd = coordsLen;
+  }
+
+};
+
+
+/**
+ * @suppress {missingProperties}
+ * @private
+ * @param {number} type The raw feature's geometry type
+ * @param {number} numEnds Number of ends of the flat coordinates of the
+ * geometry.
+ * @return {ol.geom.GeometryType} The geometry type.
+ */
+ol.format.MVT.getGeometryType_ = function(type, numEnds) {
+  /** @type {ol.geom.GeometryType} */
+  var geometryType;
+  if (type === 1) {
+    geometryType = numEnds === 1 ?
+      ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT;
+  } else if (type === 2) {
+    geometryType = numEnds === 1 ?
+      ol.geom.GeometryType.LINE_STRING :
+      ol.geom.GeometryType.MULTI_LINE_STRING;
+  } else if (type === 3) {
+    geometryType = ol.geom.GeometryType.POLYGON;
+    // MultiPolygon not relevant for rendering - winding order determines
+    // outer rings of polygons.
+  }
+  return geometryType;
+};
+
+/**
+ * @private
+ * @param {ol.ext.PBF} pbf PBF
+ * @param {Object} rawFeature Raw Mapbox feature.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature|ol.render.Feature} Feature.
+ */
+ol.format.MVT.prototype.createFeature_ = function(pbf, rawFeature, opt_options) {
+  var type = rawFeature.type;
+  if (type === 0) {
+    return null;
+  }
+
+  var feature;
+  var id = rawFeature.id;
+  var values = rawFeature.properties;
+  values[this.layerName_] = rawFeature.layer.name;
+
+  var flatCoordinates = [];
+  var ends = [];
+  ol.format.MVT.readRawGeometry_(pbf, rawFeature, flatCoordinates, ends);
+
+  var geometryType = ol.format.MVT.getGeometryType_(type, ends.length);
+
+  if (this.featureClass_ === ol.render.Feature) {
+    feature = new this.featureClass_(geometryType, flatCoordinates, ends, values, id);
+  } else {
+    var geom;
+    if (geometryType == ol.geom.GeometryType.POLYGON) {
+      var endss = [];
+      var offset = 0;
+      var prevEndIndex = 0;
+      for (var i = 0, ii = ends.length; i < ii; ++i) {
+        var end = ends[i];
+        if (!ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, offset, end, 2)) {
+          endss.push(ends.slice(prevEndIndex, i));
+          prevEndIndex = i;
+        }
+        offset = end;
+      }
+      if (endss.length > 1) {
+        ends = endss;
+        geom = new ol.geom.MultiPolygon(null);
+      } else {
+        geom = new ol.geom.Polygon(null);
+      }
+    } else {
+      geom = geometryType === ol.geom.GeometryType.POINT ? new ol.geom.Point(null) :
+        geometryType === ol.geom.GeometryType.LINE_STRING ? new ol.geom.LineString(null) :
+          geometryType === ol.geom.GeometryType.POLYGON ? new ol.geom.Polygon(null) :
+            geometryType === ol.geom.GeometryType.MULTI_POINT ? new ol.geom.MultiPoint (null) :
+              geometryType === ol.geom.GeometryType.MULTI_LINE_STRING ? new ol.geom.MultiLineString(null) :
+                null;
+    }
+    geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, ends);
+    feature = new this.featureClass_();
+    if (this.geometryName_) {
+      feature.setGeometryName(this.geometryName_);
+    }
+    var geometry = ol.format.Feature.transformWithOptions(geom, false, this.adaptOptions(opt_options));
+    feature.setGeometry(geometry);
+    feature.setId(id);
+    feature.setProperties(values);
+  }
+
+  return feature;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.format.MVT.prototype.getLastExtent = function() {
+  return this.extent_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.MVT.prototype.getType = function() {
+  return ol.format.FormatType.ARRAY_BUFFER;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.format.MVT.prototype.readFeatures = function(source, opt_options) {
+  var layers = this.layers_;
+
+  var pbf = new ol.ext.PBF(/** @type {ArrayBuffer} */ (source));
+  var pbfLayers = pbf.readFields(ol.format.MVT.pbfReaders_.layers, {});
+  /** @type {Array.<ol.Feature|ol.render.Feature>} */
+  var features = [];
+  var pbfLayer;
+  for (var name in pbfLayers) {
+    if (layers && layers.indexOf(name) == -1) {
+      continue;
+    }
+    pbfLayer = pbfLayers[name];
+
+    var rawFeature;
+    for (var i = 0, ii = pbfLayer.length; i < ii; ++i) {
+      rawFeature = ol.format.MVT.readRawFeature_(pbf, pbfLayer, i);
+      features.push(this.createFeature_(pbf, rawFeature));
+    }
+    this.extent_ = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;
+  }
+
+  return features;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.format.MVT.prototype.readProjection = function(source) {
+  return this.defaultDataProjection;
+};
+
+
+/**
+ * Sets the layers that features will be read from.
+ * @param {Array.<string>} layers Layers.
+ * @api
+ */
+ol.format.MVT.prototype.setLayers = function(layers) {
+  this.layers_ = layers;
+};
+
+
+/**
+ * Not implemented.
+ * @override
+ */
+ol.format.MVT.prototype.readFeature = function() {};
+
+
+/**
+ * Not implemented.
+ * @override
+ */
+ol.format.MVT.prototype.readGeometry = function() {};
+
+
+/**
+ * Not implemented.
+ * @override
+ */
+ol.format.MVT.prototype.writeFeature = function() {};
+
+
+/**
+ * Not implemented.
+ * @override
+ */
+ol.format.MVT.prototype.writeGeometry = function() {};
+
+
+/**
+ * Not implemented.
+ * @override
+ */
+ol.format.MVT.prototype.writeFeatures = function() {};
+
+// FIXME add typedef for stack state objects
+goog.provide('ol.format.OSMXML');
+
+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');
+
+
+/**
+ * @classdesc
+ * Feature format for reading data in the
+ * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML).
+ *
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @api
+ */
+ol.format.OSMXML = function() {
+  ol.format.XMLFeature.call(this);
+
+  /**
+   * @inheritDoc
+   */
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+};
+ol.inherits(ol.format.OSMXML, ol.format.XMLFeature);
+
+
+/**
+ * @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);
+  }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readWay_ = function(node, objectStack) {
+  var id = node.getAttribute('id');
+  var values = ol.xml.pushParseAndPop({
+    id: id,
+    ndrefs: [],
+    tags: {}
+  }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack);
+  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  state.ways.push(values);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readNd_ = function(node, objectStack) {
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  values.ndrefs.push(node.getAttribute('ref'));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readTag_ = function(node, objectStack) {
+  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+  values.tags[node.getAttribute('k')] = node.getAttribute('v');
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.OSMXML.NAMESPACE_URIS_ = [
+  null
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
+ */
+ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'nd': ol.format.OSMXML.readNd_,
+      'tag': ol.format.OSMXML.readTag_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
+ */
+ol.format.OSMXML.PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'node': ol.format.OSMXML.readNode_,
+      'way': ol.format.OSMXML.readWay_
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
+ */
+ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OSMXML.NAMESPACE_URIS_, {
+      'tag': ol.format.OSMXML.readTag_
+    });
+
+
+/**
+ * Read all features from an OSM source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
+ */
+ol.format.OSMXML.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+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: {},
+      ways: [],
+      features: []
+    }, ol.format.OSMXML.PARSERS_, node, [options]);
+    // parse nodes in ways
+    for (var j = 0; j < state.ways.length; j++) {
+      var values = /** @type {Object} */ (state.ways[j]);
+      /** @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(values.id);
+      feature.setProperties(values.tags);
+      state.features.push(feature);
+    }
+    if (state.features) {
+      return state.features;
+    }
+  }
+  return [];
+};
+
+
+/**
+ * Read the projection from an OSM source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
+ */
+ol.format.OSMXML.prototype.readProjection;
+
+
+/**
+ * Not implemented.
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.writeFeatureNode = function(feature, opt_options) {};
+
+
+/**
+ * Not implemented.
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.writeFeaturesNode = function(features, opt_options) {};
+
+
+/**
+ * Not implemented.
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.writeGeometryNode = function(geometry, opt_options) {};
+
+goog.provide('ol.format.XLink');
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
+
+
+/**
+ * @param {Node} node Node.
+ * @return {boolean|undefined} Boolean.
+ */
+ol.format.XLink.readHref = function(node) {
+  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
+};
+
+goog.provide('ol.format.XML');
+
+goog.require('ol.xml');
+
+
+/**
+ * @classdesc
+ * Generic format for reading non-feature XML data
+ *
+ * @constructor
+ * @abstract
+ * @struct
+ */
+ol.format.XML = function() {
+};
+
+
+/**
+ * @param {Document|Node|string} source Source.
+ * @return {Object} The parsed result.
+ */
+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;
+  }
+};
+
+
+/**
+ * @abstract
+ * @param {Document} doc Document.
+ * @return {Object} Object
+ */
+ol.format.XML.prototype.readFromDocument = function(doc) {};
+
+
+/**
+ * @abstract
+ * @param {Node} node Node.
+ * @return {Object} Object
+ */
+ol.format.XML.prototype.readFromNode = function(node) {};
+
+goog.provide('ol.format.OWS');
+
+goog.require('ol');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
+goog.require('ol.xml');
+
+
+/**
+ * @constructor
+ * @extends {ol.format.XML}
+ */
+ol.format.OWS = function() {
+  ol.format.XML.call(this);
+};
+ol.inherits(ol.format.OWS, ol.format.XML);
+
+
+/**
+ * @inheritDoc
+ */
+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;
+};
+
+
+/**
+ * @inheritDoc
+ */
+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.
+ * @private
+ * @return {Object|undefined} The address.
+ */
+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.
+ * @private
+ * @return {Object|undefined} The values.
+ */
+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.
+ * @private
+ * @return {Object|undefined} The constraint.
+ */
+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.
+ * @private
+ * @return {Object|undefined} The contact info.
+ */
+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.
+ * @private
+ * @return {Object|undefined} The DCP.
+ */
+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.
+ * @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);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} The HTTP object.
+ */
+ol.format.OWS.readHttp_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_,
+      node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} The operation.
+ */
+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;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} The operations metadata.
+ */
+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.
+ * @private
+ * @return {Object|undefined} The phone.
+ */
+ol.format.OWS.readPhone_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.OWS.PHONE_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {Object|undefined} The service identification.
+ */
+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.
+ * @private
+ * @return {Object|undefined} The service contact.
+ */
+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.
+ * @private
+ * @return {Object|undefined} The service provider.
+ */
+ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node,
+      objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {string|undefined} The value.
+ */
+ol.format.OWS.readValue_ = function(node, objectStack) {
+  return ol.format.XSD.readString(node);
+};
+
+
+/**
+ * @const
+ * @type {Array.<string>}
+ * @private
+ */
+ol.format.OWS.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/ows/1.1'
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
+ */
+ol.format.OWS.PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'ServiceIdentification': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readServiceIdentification_),
+      'ServiceProvider': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readServiceProvider_),
+      'OperationsMetadata': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readOperationsMetadata_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @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)
+    });
+
 
-  /**
-   * @private
-   * @type {Object.<string, *>}
-   */
-  this.properties_ = properties;
-};
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
+ */
+ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_)
+    });
 
 
 /**
- * Get a feature property by its key.
- * @param {string} key Key
- * @return {*} Value for the requested key.
- * @api
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.get = function(key) {
-  return this.properties_[key];
-};
+ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'AllowedValues': ol.xml.makeObjectPropertySetter(
+          ol.format.OWS.readAllowedValues_)
+    });
 
 
 /**
- * @return {Array.<number>|Array.<Array.<number>>} Ends or endss.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getEnds = function() {
-  return this.ends_;
-};
+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_)
+    });
 
 
 /**
- * Get the extent of this feature's geometry.
- * @return {ol.Extent} Extent.
- * @api
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-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);
+ol.format.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_)
+    });
 
-  }
-  return this.extent_;
-};
 
 /**
- * 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
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getId = function() {
-  return this.id_;
-};
+ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_),
+      'Post': undefined // TODO
+    });
 
 
 /**
- * @return {Array.<number>} Flat coordinates.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getOrientedFlatCoordinates = function() {
-  return this.flatCoordinates_;
-};
+ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_)
+    });
 
 
 /**
- * @return {Array.<number>} Flat coordinates.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getFlatCoordinates =
-    ol.render.Feature.prototype.getOrientedFlatCoordinates;
+ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Operation': ol.format.OWS.readOperation_
+    });
 
 
 /**
- * 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
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getGeometry = function() {
-  return this;
-};
+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)
+    });
 
 
 /**
- * Get the feature properties.
- * @return {Object.<string, *>} Feature properties.
- * @api
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getProperties = function() {
-  return this.properties_;
-};
+ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.OWS.NAMESPACE_URIS_, {
+      'Constraint': ol.xml.makeObjectPropertyPusher(
+          ol.format.OWS.readConstraint_)
+    });
 
 
 /**
- * Get the feature for working with its geometry.
- * @return {ol.render.Feature} Feature.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getSimplifiedGeometry =
-    ol.render.Feature.prototype.getGeometry;
+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_)
+        });
 
 
 /**
- * @return {number} Stride.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getStride = function() {
-  return 2;
-};
+ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
+    ol.xml.makeStructureNS(
+        ol.format.OWS.NAMESPACE_URIS_, {
+          'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+          'AccessConstraints': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+          'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+          'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+          'ServiceTypeVersion': ol.xml.makeObjectPropertySetter(
+              ol.format.XSD.readString),
+          'ServiceType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
+        });
 
 
 /**
- * @return {undefined}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.render.Feature.prototype.getStyleFunction = ol.nullFunction;
+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');
 
 
 /**
- * Get the type of this feature's geometry.
- * @return {ol.geom.GeometryType} Geometry type.
- * @api
+ * @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.render.Feature.prototype.getType = function() {
-  return this.type_;
+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 {
+    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;
 };
 
-//FIXME Implement projection handling
-
-goog.provide('ol.format.MVT');
+goog.provide('ol.format.Polyline');
 
 goog.require('ol');
-goog.require('ol.ext.PBF');
-goog.require('ol.ext.vectortile.VectorTile');
+goog.require('ol.asserts');
+goog.require('ol.Feature');
 goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
+goog.require('ol.format.TextFeature');
 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');
+goog.require('ol.geom.SimpleGeometry');
+goog.require('ol.geom.flat.flip');
+goog.require('ol.geom.flat.inflate');
+goog.require('ol.proj');
 
 
 /**
  * @classdesc
- * Feature format for reading data in the Mapbox MVT format.
+ * Feature format for reading and writing data in the Encoded
+ * Polyline Algorithm Format.
  *
  * @constructor
- * @extends {ol.format.Feature}
- * @param {olx.format.MVTOptions=} opt_options Options.
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.PolylineOptions=} opt_options
+ *     Optional configuration object.
  * @api
  */
-ol.format.MVT = function(opt_options) {
-
-  ol.format.Feature.call(this);
+ol.format.Polyline = function(opt_options) {
 
   var options = opt_options ? opt_options : {};
 
-  /**
-   * @type {ol.proj.Projection}
-   */
-  this.defaultDataProjection = new ol.proj.Projection({
-    code: '',
-    units: ol.proj.Units.TILE_PIXELS
-  });
+  ol.format.TextFeature.call(this);
 
   /**
-   * @private
-   * @type {function((ol.geom.Geometry|Object.<string,*>)=)|
-   *     function(ol.geom.GeometryType,Array.<number>,
-   *         (Array.<number>|Array.<Array.<number>>),Object.<string,*>,number)}
+   * @inheritDoc
    */
-  this.featureClass_ = options.featureClass ?
-      options.featureClass : ol.render.Feature;
+  this.defaultDataProjection = ol.proj.get('EPSG:4326');
 
   /**
    * @private
-   * @type {string|undefined}
+   * @type {number}
    */
-  this.geometryName_ = options.geometryName;
+  this.factor_ = options.factor ? options.factor : 1e5;
 
   /**
    * @private
-   * @type {string}
+   * @type {ol.geom.GeometryLayout}
    */
-  this.layerName_ = options.layerName ? options.layerName : 'layer';
+  this.geometryLayout_ = options.geometryLayout ?
+    options.geometryLayout : ol.geom.GeometryLayout.XY;
+};
+ol.inherits(ol.format.Polyline, ol.format.TextFeature);
 
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.layers_ = options.layers ? options.layers : null;
 
+/**
+ * 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);
 };
-ol.inherits(ol.format.MVT, ol.format.Feature);
 
 
 /**
- * @inheritDoc
+ * 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.MVT.prototype.getType = function() {
-  return ol.format.FormatType.ARRAY_BUFFER;
+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;
 };
 
 
 /**
- * @private
- * @param {Object} rawFeature Raw Mapbox feature.
- * @param {string} layer Layer.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
+ * 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.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_);
+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);
   }
-  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;
+
+  return ol.format.Polyline.encodeSignedIntegers(numbers);
 };
 
 
 /**
- * @private
- * @param {Object} rawFeature Raw Mapbox feature.
- * @param {string} layer Layer.
- * @return {ol.render.Feature} Feature.
+ * 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.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) {
-  var coords = rawFeature.loadGeometry();
-  var ends = [];
-  var flatCoordinates = [];
-  ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);
+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;
+};
 
-  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;
+
+/**
+ * Encode a list of signed integers and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * @param {Array.<number>} numbers A list of signed integers.
+ * @return {string} The encoded string.
+ */
+ol.format.Polyline.encodeSignedIntegers = function(numbers) {
+  var i, ii;
+  for (i = 0, ii = numbers.length; i < ii; ++i) {
+    var num = numbers[i];
+    numbers[i] = (num < 0) ? ~(num << 1) : (num << 1);
   }
+  return ol.format.Polyline.encodeUnsignedIntegers(numbers);
+};
 
-  var values = rawFeature.properties;
-  values[this.layerName_] = layer;
-  var id = rawFeature.id;
 
-  return new this.featureClass_(geometryType, flatCoordinates, ends, values, id);
+/**
+ * Decode a list of signed integers from an encoded string
+ *
+ * @param {string} encoded An encoded string.
+ * @return {Array.<number>} A list of signed integers.
+ */
+ol.format.Polyline.decodeSignedIntegers = function(encoded) {
+  var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded);
+  var i, ii;
+  for (i = 0, ii = numbers.length; i < ii; ++i) {
+    var num = numbers[i];
+    numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1);
+  }
+  return numbers;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * 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.MVT.prototype.readFeatures = function(source, opt_options) {
-  var layers = this.layers_;
+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 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);
+/**
+ * Decode a list of unsigned integers from an encoded string
+ *
+ * @param {string} encoded An encoded string.
+ * @return {Array.<number>} A list of unsigned integers.
+ */
+ol.format.Polyline.decodeUnsignedIntegers = function(encoded) {
+  var numbers = [];
+  var current = 0;
+  var shift = 0;
+  var i, ii;
+  for (i = 0, ii = encoded.length; i < ii; ++i) {
+    var b = encoded.charCodeAt(i) - 63;
+    current |= (b & 0x1f) << shift;
+    if (b < 0x20) {
+      numbers.push(current);
+      current = 0;
+      shift = 0;
+    } else {
+      shift += 5;
     }
   }
+  return numbers;
+};
 
-  return features;
+
+/**
+ * Encode one single unsigned integer and return an encoded string
+ *
+ * @param {number} num Unsigned integer that should be encoded.
+ * @return {string} The encoded string.
+ */
+ol.format.Polyline.encodeUnsignedInteger = function(num) {
+  var value, encoded = '';
+  while (num >= 0x20) {
+    value = (0x20 | (num & 0x1f)) + 63;
+    encoded += String.fromCharCode(value);
+    num >>= 5;
+  }
+  value = num + 63;
+  encoded += String.fromCharCode(value);
+  return encoded;
 };
 
 
 /**
- * @inheritDoc
+ * 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
  */
-ol.format.MVT.prototype.readProjection = function(source) {
-  return this.defaultDataProjection;
+ol.format.Polyline.prototype.readFeature;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) {
+  var geometry = this.readGeometryFromText(text, opt_options);
+  return new ol.Feature(geometry);
 };
 
 
 /**
- * Sets the layers that features will be read from.
- * @param {Array.<string>} layers Layers.
+ * 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.MVT.prototype.setLayers = function(layers) {
-  this.layers_ = layers;
-};
+ol.format.Polyline.prototype.readFeatures;
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-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);
-    }
-    end += 2 * j;
-    ends.push(end);
-  }
+ol.format.Polyline.prototype.readFeaturesFromText = function(text, opt_options) {
+  var feature = this.readFeatureFromText(text, opt_options);
+  return [feature];
 };
 
 
 /**
- * @private
- * @param {Object} rawFeature Raw Mapbox feature.
+ * Read the geometry from the source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
  * @return {ol.geom.Geometry} Geometry.
+ * @api
  */
-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);
+ol.format.Polyline.prototype.readGeometry;
 
-  var geom;
-  if (type === 1) {
-    geom = coords.length === 1 ?
-        new ol.geom.Point(null) : new ol.geom.MultiPoint(null);
-  } else if (type === 2) {
-    if (coords.length === 1) {
-      geom = new ol.geom.LineString(null);
-    } else {
-      geom = new ol.geom.MultiLineString(null);
-    }
-  } else if (type === 3) {
-    geom = new ol.geom.Polygon(null);
-  }
 
-  geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
-      ends);
+/**
+ * @inheritDoc
+ */
+ol.format.Polyline.prototype.readGeometryFromText = function(text, opt_options) {
+  var stride = ol.geom.SimpleGeometry.getStrideForLayout(this.geometryLayout_);
+  var flatCoordinates = ol.format.Polyline.decodeDeltas(
+      text, stride, this.factor_);
+  ol.geom.flat.flip.flipXY(
+      flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates);
+  var coordinates = ol.geom.flat.inflate.coordinates(
+      flatCoordinates, 0, flatCoordinates.length, stride);
 
-  return geom;
+  return /** @type {ol.geom.Geometry} */ (
+    ol.format.Feature.transformWithOptions(
+        new ol.geom.LineString(coordinates, this.geometryLayout_), false,
+        this.adaptOptions(opt_options)));
 };
 
 
 /**
- * Not implemented.
- * @override
+ * Read the projection from a Polyline source.
+ *
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-ol.format.MVT.prototype.readFeature = function() {};
+ol.format.Polyline.prototype.readProjection;
 
 
 /**
- * Not implemented.
- * @override
+ * @inheritDoc
  */
-ol.format.MVT.prototype.readGeometry = function() {};
+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 '';
+  }
+};
 
 
 /**
- * Not implemented.
- * @override
+ * @inheritDoc
  */
-ol.format.MVT.prototype.writeFeature = function() {};
+ol.format.Polyline.prototype.writeFeaturesText = function(features, opt_options) {
+  return this.writeFeatureText(features[0], opt_options);
+};
 
 
 /**
- * Not implemented.
- * @override
+ * 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.MVT.prototype.writeGeometry = function() {};
+ol.format.Polyline.prototype.writeGeometry;
 
 
 /**
- * Not implemented.
- * @override
+ * @inheritDoc
  */
-ol.format.MVT.prototype.writeFeatures = function() {};
+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_);
+};
 
-// FIXME add typedef for stack state objects
-goog.provide('ol.format.OSMXML');
+goog.provide('ol.format.TopoJSON');
 
 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.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.obj');
 goog.require('ol.proj');
-goog.require('ol.xml');
 
 
 /**
  * @classdesc
- * Feature format for reading data in the
- * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML).
+ * Feature format for reading data in the TopoJSON format.
  *
  * @constructor
- * @extends {ol.format.XMLFeature}
+ * @extends {ol.format.JSONFeature}
+ * @param {olx.format.TopoJSONOptions=} opt_options Options.
  * @api
  */
-ol.format.OSMXML = function() {
-  ol.format.XMLFeature.call(this);
+ol.format.TopoJSON = function(opt_options) {
+
+  var options = opt_options ? opt_options : {};
+
+  ol.format.JSONFeature.call(this);
+
+  /**
+   * @private
+   * @type {string|undefined}
+   */
+  this.layerName_ = options.layerName;
+
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.layers_ = options.layers ? options.layers : null;
 
   /**
    * @inheritDoc
    */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
+  this.defaultDataProjection = ol.proj.get(
+      options.defaultDataProjection ?
+        options.defaultDataProjection : 'EPSG:4326');
+
 };
-ol.inherits(ol.format.OSMXML, ol.format.XMLFeature);
+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
  */
-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);
+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;
 };
 
 
 /**
- * @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.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);
+ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) {
+  var coordinates = object.coordinates;
+  if (scale && translate) {
+    ol.format.TopoJSON.transformVertex_(coordinates, scale, translate);
   }
-  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);
+  return new ol.geom.Point(coordinates);
+};
+
+
+/**
+ * Create a multi-point from a TopoJSON geometry object.
+ *
+ * @param {TopoJSONGeometry} object TopoJSON object.
+ * @param {Array.<number>} scale Scale for each dimension.
+ * @param {Array.<number>} translate Translation for each dimension.
+ * @return {ol.geom.MultiPoint} Geometry.
+ * @private
+ */
+ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale,
+    translate) {
+  var coordinates = object.coordinates;
+  var i, ii;
+  if (scale && translate) {
+    for (i = 0, ii = coordinates.length; i < ii; ++i) {
+      ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate);
+    }
   }
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setId(id);
-  feature.setProperties(values.tags);
-  state.features.push(feature);
+  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
  */
-ol.format.OSMXML.readNd_ = function(node, objectStack) {
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  values.ndrefs.push(node.getAttribute('ref'));
+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
  */
-ol.format.OSMXML.readTag_ = function(node, objectStack) {
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  values.tags[node.getAttribute('k')] = node.getAttribute('v');
+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);
 };
 
 
 /**
- * @const
+ * 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
- * @type {Array.<string>}
  */
-ol.format.OSMXML.NAMESPACE_URIS_ = [
-  null
-];
+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);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * Create a multi-polygon from a TopoJSON geometry object.
+ *
+ * @param {TopoJSONGeometry} object TopoJSON object.
+ * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
+ * @return {ol.geom.MultiPolygon} Geometry.
  * @private
  */
-ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'nd': ol.format.OSMXML.readNd_,
-      'tag': ol.format.OSMXML.readTag_
-    });
+ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) {
+  var coordinates = [];
+  var polyArray, ringCoords, j, jj;
+  var i, ii;
+  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
+    // for each polygon
+    polyArray = object.arcs[i];
+    ringCoords = [];
+    for (j = 0, jj = polyArray.length; j < jj; ++j) {
+      // for each ring
+      ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs);
+    }
+    coordinates[i] = ringCoords;
+  }
+  return new ol.geom.MultiPolygon(coordinates);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * 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.OSMXML.PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'node': ol.format.OSMXML.readNode_,
-      'way': ol.format.OSMXML.readWay_
-    });
+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.XmlParser>>}
+ * 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.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'tag': ol.format.OSMXML.readTag_
-    });
+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;
+};
 
 
 /**
- * Read all features from an OSM source.
+ * Read all features from a TopoJSON 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.OSMXML.prototype.readFeatures;
+ol.format.TopoJSON.prototype.readFeatures;
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.TopoJSON.prototype.readFeaturesFromObject = function(
+    object, opt_options) {
+  if (object.type == 'Topology') {
+    var topoJSONTopology = /** @type {TopoJSONTopology} */ (object);
+    var transform, scale = null, translate = null;
+    if (topoJSONTopology.transform) {
+      transform = topoJSONTopology.transform;
+      scale = transform.scale;
+      translate = transform.translate;
+    }
+    var arcs = topoJSONTopology.arcs;
+    if (transform) {
+      ol.format.TopoJSON.transformArcs_(arcs, scale, translate);
+    }
+    /** @type {Array.<ol.Feature>} */
+    var features = [];
+    var topoJSONFeatures = 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 [];
+  }
+};
+
+
+/**
+ * Apply a linear transform to array of arcs.  The provided array of arcs is
+ * modified in place.
+ *
+ * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
+ * @param {Array.<number>} scale Scale for each dimension.
+ * @param {Array.<number>} translate Translation for each dimension.
+ * @private
+ */
+ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) {
+  var i, ii;
+  for (i = 0, ii = arcs.length; i < ii; ++i) {
+    ol.format.TopoJSON.transformArc_(arcs[i], scale, translate);
+  }
+};
+
+
+/**
+ * Apply a linear transform to an arc.  The provided arc is modified in place.
+ *
+ * @param {Array.<ol.Coordinate>} arc Arc.
+ * @param {Array.<number>} scale Scale for each dimension.
+ * @param {Array.<number>} translate Translation for each dimension.
+ * @private
+ */
+ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) {
+  var x = 0;
+  var y = 0;
+  var vertex;
+  var i, ii;
+  for (i = 0, ii = arc.length; i < ii; ++i) {
+    vertex = arc[i];
+    x += vertex[0];
+    y += vertex[1];
+    vertex[0] = x;
+    vertex[1] = y;
+    ol.format.TopoJSON.transformVertex_(vertex, scale, translate);
+  }
+};
+
+
+/**
+ * Apply a linear transform to a vertex.  The provided vertex is modified in
+ * place.
+ *
+ * @param {ol.Coordinate} vertex Vertex.
+ * @param {Array.<number>} scale Scale for each dimension.
+ * @param {Array.<number>} translate Translation for each dimension.
+ * @private
+ */
+ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
+  vertex[0] = vertex[0] * scale[0] + translate[0];
+  vertex[1] = vertex[1] * scale[1] + translate[1];
+};
+
+
+/**
+ * Read the projection from a TopoJSON source.
+ *
+ * @param {Document|Node|Object|string} object Source.
+ * @return {ol.proj.Projection} Projection.
+ * @override
+ * @api
+ */
+ol.format.TopoJSON.prototype.readProjection;
 
 
 /**
  * @inheritDoc
  */
-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 [];
+ol.format.TopoJSON.prototype.readProjectionFromObject = function(object) {
+  return this.defaultDataProjection;
 };
 
 
 /**
- * Read the projection from an OSM source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api
+ * @const
+ * @private
+ * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>}
  */
-ol.format.OSMXML.prototype.readProjection;
+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_
+};
 
 
 /**
  * Not implemented.
  * @inheritDoc
  */
-ol.format.OSMXML.prototype.writeFeatureNode = function(feature, opt_options) {};
+ol.format.TopoJSON.prototype.writeFeatureObject = function(feature, opt_options) {};
 
 
 /**
  * Not implemented.
  * @inheritDoc
  */
-ol.format.OSMXML.prototype.writeFeaturesNode = function(features, opt_options) {};
+ol.format.TopoJSON.prototype.writeFeaturesObject = function(features, opt_options) {};
 
 
 /**
  * Not implemented.
  * @inheritDoc
  */
-ol.format.OSMXML.prototype.writeGeometryNode = function(geometry, opt_options) {};
-
-goog.provide('ol.format.XLink');
+ol.format.TopoJSON.prototype.writeGeometryObject = function(geometry, opt_options) {};
 
 
 /**
- * @const
- * @type {string}
+ * Not implemented.
+ * @override
  */
-ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
+ol.format.TopoJSON.prototype.readGeometryFromObject = function() {};
 
 
 /**
- * @param {Node} node Node.
- * @return {boolean|undefined} Boolean.
+ * Not implemented.
+ * @override
  */
-ol.format.XLink.readHref = function(node) {
-  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
-};
+ol.format.TopoJSON.prototype.readFeatureFromObject = function() {};
 
-goog.provide('ol.format.XML');
+goog.provide('ol.format.WFS');
 
+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');
 
 
 /**
  * @classdesc
- * Generic format for reading non-feature XML data
+ * 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
- * @abstract
- * @struct
+ * @param {olx.format.WFSOptions=} opt_options
+ *     Optional configuration object.
+ * @extends {ol.format.XMLFeature}
+ * @api
  */
-ol.format.XML = function() {
+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];
+
+  ol.format.XMLFeature.call(this);
 };
+ol.inherits(ol.format.WFS, ol.format.XMLFeature);
 
 
 /**
- * @param {Document|Node|string} source Source.
- * @return {Object} The parsed result.
+ * @const
+ * @type {string}
  */
-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;
-  }
-};
+ol.format.WFS.FEATURE_PREFIX = 'feature';
 
 
 /**
- * @abstract
- * @param {Document} doc Document.
- * @return {Object} Object
+ * @const
+ * @type {string}
  */
-ol.format.XML.prototype.readFromDocument = function(doc) {};
+ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';
 
 
 /**
- * @abstract
- * @param {Node} node Node.
- * @return {Object} Object
+ * @const
+ * @type {string}
  */
-ol.format.XML.prototype.readFromNode = function(node) {};
+ol.format.WFS.OGCNS = 'http://www.opengis.net/ogc';
 
-goog.provide('ol.format.OWS');
 
-goog.require('ol');
-goog.require('ol.format.XLink');
-goog.require('ol.format.XML');
-goog.require('ol.format.XSD');
-goog.require('ol.xml');
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WFS.WFSNS = 'http://www.opengis.net/wfs';
 
 
 /**
- * @constructor
- * @extends {ol.format.XML}
+ * @const
+ * @type {string}
  */
-ol.format.OWS = function() {
-  ol.format.XML.call(this);
-};
-ol.inherits(ol.format.OWS, ol.format.XML);
+ol.format.WFS.FESNS = 'http://www.opengis.net/fes';
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {Object.<string, string>}
  */
-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;
+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'
 };
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {string}
  */
-ol.format.OWS.prototype.readFromNode = function(node) {
-  var owsObject = ol.xml.pushParseAndPop({},
-      ol.format.OWS.PARSERS_, node, []);
-  return owsObject ? owsObject : null;
-};
+ol.format.WFS.DEFAULT_VERSION = '1.1.0';
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The address.
+ * @return {Array.<string>|string|undefined} featureType
  */
-ol.format.OWS.readAddress_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.ADDRESS_PARSERS_, node, objectStack);
+ol.format.WFS.prototype.getFeatureType = function() {
+  return this.featureType_;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The values.
+ * @param {Array.<string>|string|undefined} featureType Feature type(s) to parse.
  */
-ol.format.OWS.readAllowedValues_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack);
+ol.format.WFS.prototype.setFeatureType = function(featureType) {
+  this.featureType_ = featureType;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The constraint.
+ * 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.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);
-};
+ol.format.WFS.prototype.readFeatures;
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The contact info.
+ * @inheritDoc
  */
-ol.format.OWS.readContactInfo_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.CONTACT_INFO_PARSERS_, node, 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 {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The DCP.
+ * Read transaction response of the source.
+ *
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.WFSTransactionResponse|undefined} Transaction response.
+ * @api
  */
-ol.format.OWS.readDcp_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.DCP_PARSERS_, node, 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 {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The GET object.
+ * Read feature collection metadata of the source.
+ *
+ * @param {Document|Node|Object|string} source Source.
+ * @return {ol.WFSFeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
+ * @api
  */
-ol.format.OWS.readGet_ = function(node, objectStack) {
-  var href = ol.format.XLink.readHref(node);
-  if (!href) {
+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;
   }
-  return ol.xml.pushParseAndPop({'href': href},
-      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The HTTP object.
+ * @param {Document} doc Document.
+ * @return {ol.WFSFeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
  */
-ol.format.OWS.readHttp_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_,
-      node, 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 {Array.<*>} objectStack Object stack.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
- * @return {Object|undefined} The operation.
  */
-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;
+ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = {
+  'http://www.opengis.net/gml': {
+    'boundedBy': ol.xml.makeObjectPropertySetter(
+        ol.format.GMLBase.prototype.readGeometryElement, 'bounds')
   }
-  var object = /** @type {Object} */
-      (objectStack[objectStack.length - 1]);
-  object[name] = value;
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The operations metadata.
+ * @return {ol.WFSFeatureCollectionMetadata|undefined}
+ *     FeatureCollection metadata.
  */
-ol.format.OWS.readOperationsMetadata_ = function(node,
-    objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node,
-      objectStack);
+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 {Array.<*>} objectStack Object stack.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
- * @return {Object|undefined} The phone.
  */
-ol.format.OWS.readPhone_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.PHONE_PARSERS_, node, objectStack);
+ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = {
+  'http://www.opengis.net/wfs': {
+    'totalInserted': ol.xml.makeObjectPropertySetter(
+        ol.format.XSD.readNonNegativeInteger),
+    'totalUpdated': ol.xml.makeObjectPropertySetter(
+        ol.format.XSD.readNonNegativeInteger),
+    'totalDeleted': ol.xml.makeObjectPropertySetter(
+        ol.format.XSD.readNonNegativeInteger)
+  }
 };
 
 
 /**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Transaction Summary.
  * @private
- * @return {Object|undefined} The service identification.
  */
-ol.format.OWS.readServiceIdentification_ = function(node,
-    objectStack) {
+ol.format.WFS.readTransactionSummary_ = function(node, objectStack) {
   return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node,
-      objectStack);
+      {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
- * @return {Object|undefined} The service contact.
  */
-ol.format.OWS.readServiceContact_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node,
-      objectStack);
+ol.format.WFS.OGC_FID_PARSERS_ = {
+  'http://www.opengis.net/ogc': {
+    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
+      return node.getAttribute('fid');
+    })
+  }
 };
 
 
@@ -51443,35 +53315,34 @@ ol.format.OWS.readServiceContact_ = function(node, objectStack) {
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
- * @return {Object|undefined} The service provider.
  */
-ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node,
-      objectStack);
+ol.format.WFS.fidParser_ = function(node, objectStack) {
+  ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
- * @return {string|undefined} The value.
  */
-ol.format.OWS.readValue_ = function(node, objectStack) {
-  return ol.format.XSD.readString(node);
+ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
+  'http://www.opengis.net/wfs': {
+    'Feature': ol.format.WFS.fidParser_
+  }
 };
 
 
 /**
- * @const
- * @type {Array.<string>}
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Insert results.
  * @private
  */
-ol.format.OWS.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/ows/1.1'
-];
+ol.format.WFS.readInsertResults_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
+};
 
 
 /**
@@ -51479,14212 +53350,13929 @@ ol.format.OWS.NAMESPACE_URIS_ = [
  * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.OWS.PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'ServiceIdentification': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceIdentification_),
-      'ServiceProvider': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceProvider_),
-      'OperationsMetadata': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readOperationsMetadata_)
-    });
+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, Object.<string, ol.XmlParser>>}
- * @private
+ * @param {Document} doc Document.
+ * @return {ol.WFSTransactionResponse|undefined} Transaction response.
  */
-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)
-    });
+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
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @return {ol.WFSTransactionResponse|undefined} Transaction response.
+ */
+ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) {
+  return ol.xml.pushParseAndPop(
+      /** @type {ol.WFSTransactionResponse} */({}),
+      ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []);
+};
+
+
+/**
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @private
  */
-ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_)
-    });
+ol.format.WFS.QUERY_SERIALIZERS_ = {
+  'http://www.opengis.net/wfs': {
+    'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
  * @private
  */
-ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'AllowedValues': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readAllowedValues_)
-    });
+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);
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {number|string} fid Feature identifier.
+ * @param {Array.<*>} objectStack Node stack.
  * @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_)
-    });
+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);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @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.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_)
-    });
+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;
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
  * @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
-    });
+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);
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {ol.Feature} feature Feature.
+ * @param {Array.<*>} objectStack Node stack.
  * @private
  */
-ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_)
-    });
+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);
+  var geometryName = feature.getGeometryName();
+  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) {
+        var name = keys[i];
+        if (value instanceof ol.geom.Geometry) {
+          name = geometryName;
+        }
+        values.push({name: name, 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);
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {Object} pair Property name and value.
+ * @param {Array.<*>} objectStack Node stack.
  * @private
  */
-ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Operation': ol.format.OWS.readOperation_
-    });
+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);
+    }
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {{vendorId: string, safeToIgnore: boolean, value: string}}
+ *     nativeElement The native element.
+ * @param {Array.<*>} objectStack Node stack.
  * @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.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);
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @private
  */
-ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Constraint': ol.xml.makeObjectPropertyPusher(
-          ol.format.OWS.readConstraint_)
-    });
+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_)
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {string} featureType Feature type.
+ * @param {Array.<*>} objectStack Node stack.
  * @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.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 {
+    typeName = featureType;
+  }
+  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);
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {ol.format.filter.Filter} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
  * @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.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);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {Node} node Node.
+ * @param {ol.format.filter.Bbox} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
  * @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_)
-    });
+ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  context['srsName'] = filter.srsName;
 
-goog.provide('ol.geom.flat.flip');
+  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
+  ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack);
+};
 
 
 /**
- * @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.
+ * @param {Node} node Node.
+ * @param {ol.format.filter.Contains} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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 {
-    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');
+ol.format.WFS.writeContainsFilter_ = function(node, filter, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  context['srsName'] = filter.srsName;
 
-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');
+  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
+  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
+};
 
 
 /**
- * @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
+ * @param {Node} node Node.
+ * @param {ol.format.filter.Intersects} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.format.Polyline = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+ol.format.WFS.writeIntersectsFilter_ = function(node, filter, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  context['srsName'] = filter.srsName;
 
-  ol.format.TextFeature.call(this);
+  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
+  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
+};
 
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.factor_ = options.factor ? options.factor : 1e5;
+/**
+ * @param {Node} node Node.
+ * @param {ol.format.filter.Within} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+ol.format.WFS.writeWithinFilter_ = function(node, filter, objectStack) {
+  var context = objectStack[objectStack.length - 1];
+  context['srsName'] = filter.srsName;
 
-  /**
-   * @private
-   * @type {ol.geom.GeometryLayout}
-   */
-  this.geometryLayout_ = options.geometryLayout ?
-      options.geometryLayout : ol.geom.GeometryLayout.XY;
+  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
+  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
 };
-ol.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
+ * @param {Node} node Node.
+ * @param {ol.format.filter.During} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) {
-  var factor = opt_factor ? opt_factor : 1e5;
-  var d;
+ol.format.WFS.writeDuringFilter_ = function(node, filter, objectStack) {
 
-  var lastNumbers = new Array(stride);
-  for (d = 0; d < stride; ++d) {
-    lastNumbers[d] = 0;
-  }
+  var valueReference = ol.xml.createElementNS(ol.format.WFS.FESNS, 'ValueReference');
+  ol.format.XSD.writeStringTextNode(valueReference, filter.propertyName);
+  node.appendChild(valueReference);
 
-  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;
+  var timePeriod = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'TimePeriod');
 
-      numbers[i] = delta;
-    }
-  }
+  node.appendChild(timePeriod);
 
-  return ol.format.Polyline.encodeFloats(numbers, factor);
+  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);
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {ol.format.filter.LogicalNary} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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;
+ol.format.WFS.writeLogicalFilter_ = function(node, filter, objectStack) {
+  /** @type {ol.XmlNodeStackItem} */
+  var item = {node: node};
+  var conditions = filter.conditions;
+  for (var i = 0, ii = conditions.length; i < ii; ++i) {
+    var condition = conditions[i];
+    ol.xml.pushSerializeAndPop(item,
+        ol.format.WFS.GETFEATURE_SERIALIZERS_,
+        ol.xml.makeSimpleNodeFactory(condition.getTagName()),
+        [condition], objectStack);
   }
+};
 
-  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 {Node} node Node.
+ * @param {ol.format.filter.Not} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
+ */
+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);
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {ol.format.filter.ComparisonBinary} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @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);
+ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) {
+  if (filter.matchCase !== undefined) {
+    node.setAttribute('matchCase', filter.matchCase.toString());
   }
-
-  return ol.format.Polyline.encodeSignedIntegers(numbers);
+  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
+  ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression);
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {ol.format.filter.IsNull} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @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;
-  }
-  return numbers;
+ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) {
+  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
 };
 
 
 /**
- * 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.
+ * @param {Node} node Node.
+ * @param {ol.format.filter.IsBetween} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @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);
-  }
-  return ol.format.Polyline.encodeUnsignedIntegers(numbers);
+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);
 };
 
 
 /**
- * Decode a list of signed integers from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @return {Array.<number>} A list of signed integers.
+ * @param {Node} node Node.
+ * @param {ol.format.filter.IsLike} filter Filter.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-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);
+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());
   }
-  return numbers;
+  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
+  ol.format.WFS.writeOgcLiteral_(node, '' + filter.pattern);
 };
 
 
 /**
- * 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.
+ * @param {string} tagName Tag name.
+ * @param {Node} node Node.
+ * @param {string} value Value.
+ * @private
  */
-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;
+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);
 };
 
 
 /**
- * Decode a list of unsigned integers from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @return {Array.<number>} A list of unsigned integers.
+ * @param {Node} node Node.
+ * @param {string} value PropertyName value.
+ * @private
  */
-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;
+ol.format.WFS.writeOgcPropertyName_ = function(node, value) {
+  ol.format.WFS.writeOgcExpression_('PropertyName', node, value);
 };
 
 
 /**
- * Encode one single unsigned integer and return an encoded string
- *
- * @param {number} num Unsigned integer that should be encoded.
- * @return {string} The encoded string.
+ * @param {Node} node Node.
+ * @param {string} value PropertyName value.
+ * @private
  */
-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;
+ol.format.WFS.writeOgcLiteral_ = function(node, value) {
+  ol.format.WFS.writeOgcExpression_('Literal', node, value);
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {string} time PropertyName value.
+ * @private
  */
-ol.format.Polyline.prototype.readFeature;
+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);
+};
 
 
 /**
- * @inheritDoc
+ * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @private
  */
-ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) {
-  var geometry = this.readGeometryFromText(text, opt_options);
-  return new ol.Feature(geometry);
+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_),
+    'Contains': ol.xml.makeChildAppender(ol.format.WFS.writeContainsFilter_),
+    '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_)
+  }
 };
 
 
 /**
- * Read the feature from the source. As Polyline sources contain a single
- * feature, this will return the feature in an array.
+ * Encode filter as WFS `Filter` and return the Node.
  *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
+ * @param {ol.format.filter.Filter} filter Filter.
+ * @return {Node} Result.
  * @api
  */
-ol.format.Polyline.prototype.readFeatures;
+ol.format.WFS.writeFilter = function(filter) {
+  var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter');
+  ol.format.WFS.writeFilterCondition_(child, filter, []);
+  return child;
+};
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @param {Array.<string>} featureTypes Feature types.
+ * @param {Array.<*>} objectStack Node stack.
+ * @private
  */
-ol.format.Polyline.prototype.readFeaturesFromText = function(text, opt_options) {
-  var feature = this.readFeatureFromText(text, opt_options);
-  return [feature];
+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);
 };
 
 
 /**
- * Read the geometry from the source.
+ * Encode format as WFS `GetFeature` and return the Node.
  *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
+ * @param {olx.format.WFSWriteGetFeatureOptions} options Options.
+ * @return {Node} Result.
  * @api
  */
-ol.format.Polyline.prototype.readGeometry;
+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;
+};
 
 
 /**
- * @inheritDoc
+ * 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.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.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);
+    }
+  }
+  var schemaLocation = ol.format.WFS.SCHEMA_LOCATIONS[version];
+  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
+      'xsi:schemaLocation', schemaLocation);
+  var featurePrefix = options.featurePrefix ? options.featurePrefix : ol.format.WFS.FEATURE_PREFIX;
+  if (inserts) {
+    obj = {node: node, 'featureNS': options.featureNS,
+      'featureType': options.featureType, 'featurePrefix': 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': 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': 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': featurePrefix,
+      'gmlVersion': gmlVersion, 'srsName': options.srsName},
+    ol.format.WFS.TRANSACTION_SERIALIZERS_,
+    ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements,
+    objectStack);
+  }
+  return node;
 };
 
 
 /**
- * Read the projection from a Polyline source.
+ * Read the projection from a WFS source.
  *
  * @function
  * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
+ * @return {?ol.proj.Projection} Projection.
  * @api
  */
-ol.format.Polyline.prototype.readProjection;
+ol.format.WFS.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 {
-    ol.asserts.assert(false, 40); // Expected `feature` to have a geometry
-    return '';
+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);
+    }
   }
+  return null;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.format.Polyline.prototype.writeFeaturesText = function(features, opt_options) {
-  return this.writeFeatureText(features[0], opt_options);
-};
-
-
-/**
- * Write a single geometry in Polyline format.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Geometry.
- * @api
- */
-ol.format.Polyline.prototype.writeGeometry;
-
+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);
+      }
+    }
+  }
 
-/**
- * @inheritDoc
- */
-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_);
+  return null;
 };
 
-goog.provide('ol.format.TopoJSON');
+goog.provide('ol.format.WKT');
 
 goog.require('ol');
 goog.require('ol.Feature');
 goog.require('ol.format.Feature');
-goog.require('ol.format.JSONFeature');
+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.proj');
+goog.require('ol.geom.SimpleGeometry');
 
 
 /**
  * @classdesc
- * Feature format for reading data in the TopoJSON format.
+ * Geometry format for reading and writing data in the `WellKnownText` (WKT)
+ * format.
  *
  * @constructor
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.TopoJSONOptions=} opt_options Options.
+ * @extends {ol.format.TextFeature}
+ * @param {olx.format.WKTOptions=} opt_options Options.
  * @api
  */
-ol.format.TopoJSON = function(opt_options) {
+ol.format.WKT = function(opt_options) {
 
   var options = opt_options ? opt_options : {};
 
-  ol.format.JSONFeature.call(this);
+  ol.format.TextFeature.call(this);
 
   /**
+   * Split GeometryCollection into multiple features.
+   * @type {boolean}
    * @private
-   * @type {string|undefined}
    */
-  this.layerName_ = options.layerName;
+  this.splitCollection_ = options.splitCollection !== undefined ?
+    options.splitCollection : false;
 
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.layers_ = options.layers ? options.layers : null;
+};
+ol.inherits(ol.format.WKT, ol.format.TextFeature);
 
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get(
-      options.defaultDataProjection ?
-          options.defaultDataProjection : 'EPSG:4326');
 
-};
-ol.inherits(ol.format.TopoJSON, ol.format.JSONFeature);
+/**
+ * @const
+ * @type {string}
+ */
+ol.format.WKT.EMPTY = 'EMPTY';
 
 
 /**
- * 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
+ * @const
+ * @type {string}
  */
-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;
-};
+ol.format.WKT.Z = 'Z';
 
 
 /**
- * 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
+ * @const
+ * @type {string}
  */
-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.format.WKT.M = 'M';
 
 
 /**
- * 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
+ * @const
+ * @type {string}
  */
-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.format.WKT.ZM = 'ZM';
 
 
 /**
- * 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.
+ * @param {ol.geom.Point} geom Point geometry.
+ * @return {string} Coordinates part of Point as WKT.
  * @private
  */
-ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
-  var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
-  return new ol.geom.LineString(coordinates);
+ol.format.WKT.encodePointGeometry_ = function(geom) {
+  var coordinates = geom.getCoordinates();
+  if (coordinates.length === 0) {
+    return '';
+  }
+  return coordinates.join(' ');
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
+ * @return {string} Coordinates part of MultiPoint as WKT.
  * @private
  */
-ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) {
-  var coordinates = [];
-  var i, ii;
-  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
-    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
+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 new ol.geom.MultiLineString(coordinates);
+  return array.join(',');
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
+ * @return {string} Coordinates part of GeometryCollection as WKT.
  * @private
  */
-ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) {
-  var coordinates = [];
-  var i, ii;
-  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
-    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
+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 new ol.geom.Polygon(coordinates);
+  return array.join(',');
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
+ * @return {string} Coordinates part of LineString as WKT.
  * @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.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 new ol.geom.MultiPolygon(coordinates);
+  return array.join(',');
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
+ * @return {string} Coordinates part of MultiLineString as WKT.
  * @private
  */
-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);
+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 features;
+  return array.join(',');
 };
 
 
 /**
- * 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.
+ * @param {ol.geom.Polygon} geom Polygon geometry.
+ * @return {string} Coordinates part of Polygon as WKT.
  * @private
  */
-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);
+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 feature;
+  return array.join(',');
 };
 
 
 /**
- * Read all features from a TopoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {Array.<ol.Feature>} Features.
- * @api
+ * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
+ * @return {string} Coordinates part of MultiPolygon as WKT.
+ * @private
  */
-ol.format.TopoJSON.prototype.readFeatures;
-
+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(',');
+};
 
 /**
- * @inheritDoc
+ * @param {ol.geom.SimpleGeometry} geom SimpleGeometry geometry.
+ * @return {string} Potential dimensional information for WKT type.
+ * @private
  */
-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 [];
+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;
 };
 
 
 /**
- * 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.
+ * Encode a geometry as WKT.
+ * @param {ol.geom.Geometry} geom The geometry to encode.
+ * @return {string} WKT string for the geometry.
  * @private
  */
-ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) {
-  var i, ii;
-  for (i = 0, ii = arcs.length; i < ii; ++i) {
-    ol.format.TopoJSON.transformArc_(arcs[i], scale, translate);
+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 + ')';
 };
 
 
 /**
- * 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.
+ * @const
+ * @type {Object.<string, function(ol.geom.Geometry): string>}
  * @private
  */
-ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) {
-  var x = 0;
-  var y = 0;
-  var vertex;
-  var i, ii;
-  for (i = 0, ii = arc.length; i < ii; ++i) {
-    vertex = arc[i];
-    x += vertex[0];
-    y += vertex[1];
-    vertex[0] = x;
-    vertex[1] = y;
-    ol.format.TopoJSON.transformVertex_(vertex, scale, translate);
-  }
+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_
 };
 
 
 /**
- * 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.
+ * Parse a WKT string.
+ * @param {string} wkt WKT string.
+ * @return {ol.geom.Geometry|undefined}
+ *     The geometry created.
  * @private
  */
-ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
-  vertex[0] = vertex[0] * scale[0] + translate[0];
-  vertex[1] = vertex[1] * scale[1] + translate[1];
+ol.format.WKT.prototype.parse_ = function(wkt) {
+  var lexer = new ol.format.WKT.Lexer(wkt);
+  var parser = new ol.format.WKT.Parser(lexer);
+  return parser.parse();
 };
 
 
 /**
- * Read the projection from a TopoJSON source.
+ * Read a feature from a WKT source.
  *
- * @param {Document|Node|Object|string} object Source.
- * @return {ol.proj.Projection} Projection.
- * @override
+ * @function
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Read options.
+ * @return {ol.Feature} Feature.
  * @api
  */
-ol.format.TopoJSON.prototype.readProjection;
+ol.format.WKT.prototype.readFeature;
 
 
 /**
  * @inheritDoc
  */
-ol.format.TopoJSON.prototype.readProjectionFromObject = function(object) {
-  return this.defaultDataProjection;
+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;
 };
 
 
 /**
- * @const
- * @private
- * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>}
+ * 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.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.format.WKT.prototype.readFeatures;
 
 
 /**
- * Not implemented.
  * @inheritDoc
  */
-ol.format.TopoJSON.prototype.writeFeatureObject = function(feature, opt_options) {};
+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;
+};
 
 
 /**
- * Not implemented.
- * @inheritDoc
+ * 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.TopoJSON.prototype.writeFeaturesObject = function(features, opt_options) {};
+ol.format.WKT.prototype.readGeometry;
 
 
 /**
- * Not implemented.
  * @inheritDoc
  */
-ol.format.TopoJSON.prototype.writeGeometryObject = function(geometry, opt_options) {};
+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;
+  }
+};
 
 
 /**
- * Not implemented.
- * @override
+ * 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
  */
-ol.format.TopoJSON.prototype.readGeometryFromObject = function() {};
+ol.format.WKT.prototype.writeFeature;
 
 
 /**
- * Not implemented.
- * @override
+ * @inheritDoc
  */
-ol.format.TopoJSON.prototype.readFeatureFromObject = function() {};
-
-goog.provide('ol.format.WFS');
-
-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');
+ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
+  var geometry = feature.getGeometry();
+  if (geometry) {
+    return this.writeGeometryText(geometry, opt_options);
+  }
+  return '';
+};
 
 
 /**
- * @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.
+ * Encode an array of features as a WKT string.
  *
- * @constructor
- * @param {olx.format.WFSOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.XMLFeature}
+ * @function
+ * @param {Array.<ol.Feature>} features Features.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} WKT string.
  * @api
  */
-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();
+ol.format.WKT.prototype.writeFeatures;
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.schemaLocation_ = options.schemaLocation ?
-      options.schemaLocation :
-          ol.format.WFS.SCHEMA_LOCATIONS[ol.format.WFS.DEFAULT_VERSION];
 
-  ol.format.XMLFeature.call(this);
+/**
+ * @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);
 };
-ol.inherits(ol.format.WFS, ol.format.XMLFeature);
 
 
 /**
- * @const
- * @type {string}
+ * Write a single geometry as a WKT string.
+ *
+ * @function
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @param {olx.format.WriteOptions=} opt_options Write options.
+ * @return {string} WKT string.
+ * @api
  */
-ol.format.WFS.FEATURE_PREFIX = 'feature';
+ol.format.WKT.prototype.writeGeometry;
 
 
 /**
- * @const
- * @type {string}
+ * @inheritDoc
  */
-ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';
+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)));
+};
 
 
 /**
  * @const
- * @type {string}
+ * @enum {number}
+ * @private
  */
-ol.format.WFS.OGCNS = 'http://www.opengis.net/ogc';
+ol.format.WKT.TokenType_ = {
+  TEXT: 1,
+  LEFT_PAREN: 2,
+  RIGHT_PAREN: 3,
+  NUMBER: 4,
+  COMMA: 5,
+  EOF: 6
+};
 
 
 /**
- * @const
- * @type {string}
+ * Class to tokenize a WKT string.
+ * @param {string} wkt WKT string.
+ * @constructor
+ * @protected
  */
-ol.format.WFS.WFSNS = 'http://www.opengis.net/wfs';
+ol.format.WKT.Lexer = function(wkt) {
 
+  /**
+   * @type {string}
+   */
+  this.wkt = wkt;
 
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.FESNS = 'http://www.opengis.net/fes';
+  /**
+   * @type {number}
+   * @private
+   */
+  this.index_ = -1;
+};
 
 
 /**
- * @const
- * @type {Object.<string, string>}
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is alphabetic.
+ * @private
  */
-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'
+ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) {
+  return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
 };
 
 
 /**
- * @const
- * @type {string}
+ * @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.WFS.DEFAULT_VERSION = '1.1.0';
+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;
+};
 
 
 /**
- * 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
+ * @param {string} c Character.
+ * @return {boolean} Whether the character is whitespace.
+ * @private
  */
-ol.format.WFS.prototype.readFeatures;
+ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
+  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+};
 
 
 /**
- * @inheritDoc
+ * @return {string} Next string character.
+ * @private
  */
-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;
+ol.format.WKT.Lexer.prototype.nextChar_ = function() {
+  return this.wkt.charAt(++this.index_);
 };
 
 
 /**
- * Read transaction response of the source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.WFSTransactionResponse|undefined} Transaction response.
- * @api
+ * Fetch and return the next token.
+ * @return {!ol.WKTToken} Next string token.
  */
-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);
+ol.format.WKT.Lexer.prototype.nextToken = function() {
+  var c = this.nextChar_();
+  var token = {position: this.index_, value: c};
+
+  if (c == '(') {
+    token.type = ol.format.WKT.TokenType_.LEFT_PAREN;
+  } else if (c == ',') {
+    token.type = ol.format.WKT.TokenType_.COMMA;
+  } else if (c == ')') {
+    token.type = ol.format.WKT.TokenType_.RIGHT_PAREN;
+  } else if (this.isNumeric_(c) || c == '-') {
+    token.type = ol.format.WKT.TokenType_.NUMBER;
+    token.value = this.readNumber_();
+  } else if (this.isAlpha_(c)) {
+    token.type = ol.format.WKT.TokenType_.TEXT;
+    token.value = this.readText_();
+  } else if (this.isWhiteSpace_(c)) {
+    return this.nextToken();
+  } else if (c === '') {
+    token.type = ol.format.WKT.TokenType_.EOF;
   } else {
-    return undefined;
+    throw new Error('Unexpected character: ' + c);
   }
+
+  return token;
 };
 
 
 /**
- * Read feature collection metadata of the source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.WFSFeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
- * @api
+ * @return {number} Numeric token value.
+ * @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 (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readFeatureCollectionMetadataFromDocument(doc);
-  } else {
-    return undefined;
-  }
+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_--));
 };
 
 
 /**
- * @param {Document} doc Document.
- * @return {ol.WFSFeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
+ * @return {string} String token value.
+ * @private
  */
-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;
+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();
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * Class to parse the tokens from the WKT string.
+ * @param {ol.format.WKT.Lexer} lexer The lexer.
+ * @constructor
+ * @protected
  */
-ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = {
-  'http://www.opengis.net/gml': {
-    'boundedBy': ol.xml.makeObjectPropertySetter(
-        ol.format.GMLBase.prototype.readGeometryElement, 'bounds')
-  }
+ol.format.WKT.Parser = function(lexer) {
+
+  /**
+   * @type {ol.format.WKT.Lexer}
+   * @private
+   */
+  this.lexer_ = lexer;
+
+  /**
+   * @type {ol.WKTToken}
+   * @private
+   */
+  this.token_;
+
+  /**
+   * @type {ol.geom.GeometryLayout}
+   * @private
+   */
+  this.layout_ = ol.geom.GeometryLayout.XY;
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {ol.WFSFeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
+ * Fetch the next token form the lexer and replace the active token.
+ * @private
  */
-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_);
+ol.format.WKT.Parser.prototype.consume_ = function() {
+  this.token_ = this.lexer_.nextToken();
+};
+
+/**
+ * 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.WKT.Parser.prototype.isTokenType = function(type) {
+  var isMatch = this.token_.type == type;
+  return isMatch;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * 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.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.format.WKT.Parser.prototype.match = function(type) {
+  var isMatch = this.isTokenType(type);
+  if (isMatch) {
+    this.consume_();
   }
+  return isMatch;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Transaction Summary.
- * @private
+ * Try to parse the tokens provided by the lexer.
+ * @return {ol.geom.Geometry} The geometry.
  */
-ol.format.WFS.readTransactionSummary_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
+ol.format.WKT.Parser.prototype.parse = function() {
+  this.consume_();
+  var geometry = this.parseGeometry_();
+  return geometry;
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * Try to parse the dimensional info.
+ * @return {ol.geom.GeometryLayout} The layout.
  * @private
  */
-ol.format.WFS.OGC_FID_PARSERS_ = {
-  'http://www.opengis.net/ogc': {
-    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
-      return node.getAttribute('fid');
-    })
+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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
+ * @return {!ol.geom.Geometry} The geometry.
  * @private
  */
-ol.format.WFS.fidParser_ = function(node, objectStack) {
-  ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
+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_());
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @return {!Array.<ol.geom.Geometry>} A collection of geometries.
  * @private
  */
-ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Feature': ol.format.WFS.fidParser_
+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_());
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Insert results.
+ * @return {Array.<number>} All values in a point.
  * @private
  */
-ol.format.WFS.readInsertResults_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
+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_());
 };
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @return {!Array.<!Array.<number>>} All points in a linestring.
  * @private
  */
-ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'TransactionSummary': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readTransactionSummary_, 'transactionSummary'),
-    'InsertResults': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readInsertResults_, 'insertIds')
+ol.format.WKT.Parser.prototype.parseLineStringText_ = function() {
+  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
+    var coordinates = this.parsePointList_();
+    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
+      return coordinates;
+    }
+  } else if (this.isEmptyGeometry_()) {
+    return [];
   }
+  throw new Error(this.formatErrorMessage_());
 };
 
 
 /**
- * @param {Document} doc Document.
- * @return {ol.WFSTransactionResponse|undefined} Transaction response.
+ * @return {!Array.<!Array.<number>>} All points in a polygon.
+ * @private
  */
-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);
+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 [];
   }
-  return undefined;
+  throw new Error(this.formatErrorMessage_());
 };
 
 
 /**
- * @param {Node} node Node.
- * @return {ol.WFSTransactionResponse|undefined} Transaction response.
+ * @return {!Array.<!Array.<number>>} All points in a multipoint.
+ * @private
  */
-ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) {
-  return ol.xml.pushParseAndPop(
-      /** @type {ol.WFSTransactionResponse} */({}),
-      ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []);
+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_());
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * @return {!Array.<!Array.<number>>} All linestring points
+ *                                        in a multilinestring.
  * @private
  */
-ol.format.WFS.QUERY_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
+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_());
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
+ * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon.
  * @private
  */
-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);
+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_());
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {number|string} fid Feature identifier.
- * @param {Array.<*>} objectStack Node stack.
+ * @return {!Array.<number>} A point.
  * @private
  */
-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);
+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_());
 };
 
 
 /**
- * @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.
+ * @return {!Array.<!Array.<number>>} An array of points.
  * @private
  */
-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;
+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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
+ * @return {!Array.<!Array.<number>>} An array of points.
  * @private
  */
-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);
+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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  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);
+ * @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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Object} pair Property name and value.
- * @param {Array.<*>} objectStack Node stack.
+ * @return {!Array.<!Array.<number>>} An array of points.
  * @private
  */
-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);
-    }
+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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {{vendorId: string, safeToIgnore: boolean, value: string}}
- *     nativeElement The native element.
- * @param {Array.<*>} objectStack Node stack.
+ * @return {boolean} Whether the token implies an empty geometry.
  * @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);
+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;
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
+ * Create an error message for an unexpected token error.
+ * @return {string} Error message.
  * @private
  */
-ol.format.WFS.TRANSACTION_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_),
-    'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_),
-    'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_),
-    'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_),
-    'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_)
-  }
+ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() {
+  return 'Unexpected `' + this.token_.value + '` at position ' +
+      this.token_.position + ' in `' + this.lexer_.wkt + '`';
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {string} featureType Feature type.
- * @param {Array.<*>} objectStack Node stack.
+ * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)}
  * @private
  */
-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 {
-    typeName = featureType;
-  }
-  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);
-  }
+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
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.format.filter.Filter} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
+ * @enum {(function(): Array)}
  * @private
  */
-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);
+ol.format.WKT.Parser.GeometryParser_ = {
+  'POINT': ol.format.WKT.Parser.prototype.parsePointText_,
+  'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_,
+  'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_,
+  'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_,
+  'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_,
+  'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_
 };
 
+goog.provide('ol.format.WMSCapabilities');
+
+goog.require('ol');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
+goog.require('ol.xml');
+
 
 /**
- * @param {Node} node Node.
- * @param {ol.format.filter.Bbox} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @classdesc
+ * Format for reading WMS capabilities data
+ *
+ * @constructor
+ * @extends {ol.format.XML}
+ * @api
  */
-ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  context['srsName'] = filter.srsName;
+ol.format.WMSCapabilities = function() {
 
-  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
-  ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack);
+  ol.format.XML.call(this);
+
+  /**
+   * @type {string|undefined}
+   */
+  this.version = undefined;
 };
+ol.inherits(ol.format.WMSCapabilities, ol.format.XML);
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.format.filter.Intersects} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * Read a WMS capabilities document.
+ *
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMS capabilities.
+ * @api
  */
-ol.format.WFS.writeIntersectsFilter_ = function(node, filter, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  context['srsName'] = filter.srsName;
+ol.format.WMSCapabilities.prototype.read;
 
-  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
-  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
+
+/**
+ * @inheritDoc
+ */
+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);
+    }
+  }
+  return null;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.format.filter.Within} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
+ * @inheritDoc
  */
-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);
+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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.format.filter.During} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
  */
-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);
+ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack);
+};
 
-  var timePeriod = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'TimePeriod');
 
-  node.appendChild(timePeriod);
+/**
+ * @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'))
+  ];
 
-  var begin = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'begin');
-  timePeriod.appendChild(begin);
-  ol.format.WFS.writeTimeInstant_(begin, filter.begin);
+  var resolutions = [
+    ol.format.XSD.readDecimalString(node.getAttribute('resx')),
+    ol.format.XSD.readDecimalString(node.getAttribute('resy'))
+  ];
 
-  var end = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'end');
-  timePeriod.appendChild(end);
-  ol.format.WFS.writeTimeInstant_(end, filter.end);
+  return {
+    'crs': node.getAttribute('CRS'),
+    'extent': extent,
+    'res': resolutions
+  };
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {ol.format.filter.LogicalNary} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {ol.Extent|undefined} Bounding box object.
  */
-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);
-  });
+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;
+  }
+  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 [
+    westBoundLongitude, southBoundLatitude,
+    eastBoundLongitude, northBoundLatitude
+  ];
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {ol.format.filter.Not} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} Capability object.
  */
-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);
+ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {ol.format.filter.ComparisonBinary} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} Service object.
  */
-ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) {
-  if (filter.matchCase !== undefined) {
-    node.setAttribute('matchCase', filter.matchCase.toString());
-  }
-  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
-  ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression);
+ol.format.WMSCapabilities.readService_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {ol.format.filter.IsNull} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} Contact information object.
  */
-ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) {
-  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
+ol.format.WMSCapabilities.readContactInformation_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_,
+      node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {ol.format.filter.IsBetween} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} Contact person object.
  */
-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);
+ol.format.WMSCapabilities.readContactPersonPrimary_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_,
+      node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {ol.format.filter.IsLike} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} Contact address object.
  */
-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);
+ol.format.WMSCapabilities.readContactAddress_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_,
+      node, objectStack);
 };
 
 
 /**
- * @param {string} tagName Tag name.
  * @param {Node} node Node.
- * @param {string} value Value.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Array.<string>|undefined} Format array.
  */
-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);
+ol.format.WMSCapabilities.readException_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack);
 };
 
 
 /**
  * @param {Node} node Node.
- * @param {string} value PropertyName value.
+ * @param {Array.<*>} objectStack Object stack.
  * @private
+ * @return {Object|undefined} Layer object.
  */
-ol.format.WFS.writeOgcPropertyName_ = function(node, value) {
-  ol.format.WFS.writeOgcExpression_('PropertyName', node, value);
+ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {string} value PropertyName value.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layer object.
  */
-ol.format.WFS.writeOgcLiteral_ = function(node, value) {
-  ol.format.WFS.writeOgcExpression_('Literal', node, value);
+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);
+
+  if (!layerObject) {
+    return undefined;
+  }
+  var queryable =
+        ol.format.XSD.readBooleanString(node.getAttribute('queryable'));
+  if (queryable === undefined) {
+    queryable = parentLayerObject['queryable'];
+  }
+  layerObject['queryable'] = queryable !== undefined ? queryable : false;
+
+  var cascaded = ol.format.XSD.readNonNegativeIntegerString(
+      node.getAttribute('cascaded'));
+  if (cascaded === undefined) {
+    cascaded = parentLayerObject['cascaded'];
+  }
+  layerObject['cascaded'] = cascaded;
+
+  var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
+  if (opaque === undefined) {
+    opaque = parentLayerObject['opaque'];
+  }
+  layerObject['opaque'] = opaque !== undefined ? opaque : false;
+
+  var noSubsets =
+        ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
+  if (noSubsets === undefined) {
+    noSubsets = parentLayerObject['noSubsets'];
+  }
+  layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false;
+
+  var fixedWidth =
+        ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
+  if (!fixedWidth) {
+    fixedWidth = parentLayerObject['fixedWidth'];
+  }
+  layerObject['fixedWidth'] = fixedWidth;
+
+  var fixedHeight =
+        ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
+  if (!fixedHeight) {
+    fixedHeight = parentLayerObject['fixedHeight'];
+  }
+  layerObject['fixedHeight'] = fixedHeight;
+
+  // See 7.2.4.8
+  var addKeys = ['Style', 'CRS', 'AuthorityURL'];
+  addKeys.forEach(function(key) {
+    if (key in parentLayerObject) {
+      var childValue = 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;
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {string} time PropertyName value.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object} Dimension object.
  */
-ol.format.WFS.writeTimeInstant_ = function(node, time) {
-  var timeInstant = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'TimeInstant');
-  node.appendChild(timeInstant);
+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;
+};
 
-  var timePosition = ol.xml.createElementNS(ol.format.GMLBase.GMLNS, 'timePosition');
-  timeInstant.appendChild(timePosition);
-  ol.format.XSD.writeStringTextNode(timePosition, time);
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Online resource object.
+ */
+ol.format.WMSCapabilities.readFormatOnlineresource_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_,
+      node, objectStack);
 };
 
 
 /**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Request object.
  */
-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_)
-  }
+ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} DCP type object.
+ */
+ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Encode filter as WFS `Filter` and return the Node.
- *
- * @param {ol.format.filter.Filter} filter Filter.
- * @return {Node} Result.
- * @api
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} HTTP object.
  */
-ol.format.WFS.writeFilter = function(filter) {
-  var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter');
-  ol.format.WFS.writeFilterCondition_(child, filter, []);
-  return child;
+ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<string>} featureTypes Feature types.
- * @param {Array.<*>} objectStack Node stack.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Operation type object.
  */
-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);
+ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Encode format as WFS `GetFeature` and return the Node.
- *
- * @param {olx.format.WFSWriteGetFeatureOptions} options Options.
- * @return {Node} Result.
- * @api
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Online resource object.
  */
-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.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;
   }
-  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;
+  return undefined;
 };
 
 
 /**
- * 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
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Authority URL object.
  */
-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);
-    }
-  }
-  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);
+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 node;
+  return undefined;
 };
 
 
 /**
- * Read the projection from a WFS source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {?ol.proj.Projection} Projection.
- * @api
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Metadata URL object.
  */
-ol.format.WFS.prototype.readProjection;
+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;
+};
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Style object.
  */
-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);
-    }
-  }
-  return null;
+ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<string>|undefined} Keyword list.
  */
-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);
-      }
-    }
-  }
-
-  return null;
+ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop(
+      [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack);
 };
 
-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');
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/wms'
+];
 
 
 /**
- * @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
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.WKT = function(opt_options) {
+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_)
+    });
 
-  var options = opt_options ? opt_options : {};
 
-  ol.format.TextFeature.call(this);
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @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_)
+    });
 
-  /**
-   * Split GeometryCollection into multiple features.
-   * @type {boolean}
-   * @private
-   */
-  this.splitCollection_ = options.splitCollection !== undefined ?
-      options.splitCollection : false;
 
-};
-ol.inherits(ol.format.WKT, ol.format.TextFeature);
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
+ */
+ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'KeywordList': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readKeywordList_),
+      'OnlineResource': ol.xml.makeObjectPropertySetter(
+          ol.format.XLink.readHref),
+      'ContactInformation': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readContactInformation_),
+      'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'AccessConstraints': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'LayerLimit': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'MaxWidth': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'MaxHeight': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger)
+    });
 
 
 /**
  * @const
- * @type {string}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.WKT.EMPTY = 'EMPTY';
+ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'ContactPersonPrimary': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readContactPersonPrimary_),
+      'ContactPosition': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'ContactAddress': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readContactAddress_),
+      'ContactVoiceTelephone': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'ContactFacsimileTelephone': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'ContactElectronicMailAddress': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    });
 
 
 /**
  * @const
- * @type {string}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.WKT.Z = 'Z';
+ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'ContactPerson': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'ContactOrganization': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    });
 
 
 /**
  * @const
- * @type {string}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.WKT.M = 'M';
+ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'StateOrProvince': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
+    });
 
 
 /**
  * @const
- * @type {string}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @private
  */
-ol.format.WKT.ZM = 'ZM';
+ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
+    });
 
 
 /**
- * @param {ol.geom.Point} geom Point geometry.
- * @return {string} Coordinates part of Point as WKT.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.encodePointGeometry_ = function(geom) {
-  var coordinates = geom.getCoordinates();
-  if (coordinates.length === 0) {
-    return '';
-  }
-  return coordinates.join(' ');
-};
+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_)
+    });
 
 
 /**
- * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
- * @return {string} Coordinates part of MultiPoint as WKT.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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(',');
-};
+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_)
+    });
 
 
 /**
- * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
- * @return {string} Coordinates part of GeometryCollection as WKT.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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(',');
-};
+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)
+    });
 
 
 /**
- * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
- * @return {string} Coordinates part of LineString as WKT.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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].join(' '));
-  }
-  return array.join(',');
-};
+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_)
+    });
 
 
 /**
- * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
- * @return {string} Coordinates part of MultiLineString as WKT.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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(',');
-};
+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_)
+    });
 
 
 /**
- * @param {ol.geom.Polygon} geom Polygon geometry.
- * @return {string} Coordinates part of Polygon as WKT.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'HTTP': ol.xml.makeObjectPropertySetter(
+          ol.format.WMSCapabilities.readHTTP_)
+    });
 
 
 /**
- * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
- * @return {string} Coordinates part of MultiPolygon as WKT.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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(',');
-};
+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_)
+    });
+
 
 /**
- * @param {ol.geom.SimpleGeometry} geom SimpleGeometry geometry.
- * @return {string} Potential dimensional information for WKT type.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-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;
-};
+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_)
+    });
 
 
 /**
- * Encode a geometry as WKT.
- * @param {ol.geom.Geometry} geom The geometry to encode.
- * @return {string} WKT string for the geometry.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-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 + ')';
-};
+ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ =
+    ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, {
+      'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'OnlineResource': ol.xml.makeObjectPropertySetter(
+          ol.format.XLink.readHref)
+    });
 
 
 /**
  * @const
- * @type {Object.<string, function(ol.geom.Geometry): string>}
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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_
-};
+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');
 
-/**
- * Parse a WKT string.
- * @param {string} wkt WKT string.
- * @return {ol.geom.Geometry|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();
-};
+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');
 
 
 /**
- * Read a feature from a WKT source.
+ * @classdesc
+ * Format for reading WMSGetFeatureInfo format. It uses
+ * {@link ol.format.GML2} to read features.
  *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ * @param {olx.format.WMSGetFeatureInfoOptions=} opt_options Options.
  * @api
  */
-ol.format.WKT.prototype.readFeature;
+ol.format.WMSGetFeatureInfo = function(opt_options) {
 
+  var options = opt_options ? opt_options : {};
 
-/**
- * @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;
+  /**
+   * @private
+   * @type {string}
+   */
+  this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver';
+
+
+  /**
+   * @private
+   * @type {ol.format.GML2}
+   */
+  this.gmlFormat_ = new ol.format.GML2();
+
+
+  /**
+   * @private
+   * @type {Array.<string>}
+   */
+  this.layers_ = options.layers ? options.layers : null;
+
+  ol.format.XMLFeature.call(this);
 };
+ol.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature);
 
 
 /**
- * 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
+ * @const
+ * @type {string}
+ * @private
  */
-ol.format.WKT.prototype.readFeatures;
+ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';
 
 
 /**
- * @inheritDoc
+ * @const
+ * @type {string}
+ * @private
  */
-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;
-};
+ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';
 
 
 /**
- * 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
+ * @return {Array.<string>} layers
  */
-ol.format.WKT.prototype.readGeometry;
+ol.format.WMSGetFeatureInfo.prototype.getLayers = function() {
+  return this.layers_;
+};
 
 
 /**
- * @inheritDoc
+ * @param {Array.<string>} layers Layers to parse.
  */
-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;
-  }
+ol.format.WMSGetFeatureInfo.prototype.setLayers = function(layers) {
+  this.layers_ = layers;
 };
 
 
 /**
- * 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
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Array.<ol.Feature>} Features.
+ * @private
  */
-ol.format.WKT.prototype.writeFeature;
+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];
+
+      var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_;
+      var layerName = layer.localName.replace(toRemove, '');
 
+      if (this.layers_ && !ol.array.includes(this.layers_, layerName)) {
+        continue;
+      }
 
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    return this.writeGeometryText(geometry, opt_options);
+      var featureType = layerName +
+          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.setAttribute('namespaceURI', this.featureNS_);
+      var layerFeatures = ol.xml.pushParseAndPop(
+          [], parsersNS, layer, objectStack, this.gmlFormat_);
+      if (layerFeatures) {
+        ol.array.extend(features, layerFeatures);
+      }
+    }
   }
-  return '';
+  if (localName == 'FeatureCollection') {
+    var gmlFeatures = ol.xml.pushParseAndPop([],
+        this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
+        [{}], this.gmlFormat_);
+    if (gmlFeatures) {
+      features = gmlFeatures;
+    }
+  }
+  return features;
 };
 
 
 /**
- * Encode an array of features as a WKT string.
+ * Read all features from a WMSGetFeatureInfo response.
  *
  * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} WKT string.
+ * @param {Document|Node|Object|string} source Source.
+ * @param {olx.format.ReadOptions=} opt_options Options.
+ * @return {Array.<ol.Feature>} Features.
  * @api
  */
-ol.format.WKT.prototype.writeFeatures;
+ol.format.WMSGetFeatureInfo.prototype.readFeatures;
 
 
 /**
  * @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());
+ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = function(node, opt_options) {
+  var options = {};
+  if (opt_options) {
+    ol.obj.assign(options, this.getReadOptions(node, opt_options));
   }
-  var collection = new ol.geom.GeometryCollection(geometries);
-  return this.writeGeometryText(collection, opt_options);
+  return this.readFeatures_(node, [options]);
 };
 
 
 /**
- * Write a single geometry as a WKT string.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @return {string} WKT string.
- * @api
+ * Not implemented.
+ * @inheritDoc
  */
-ol.format.WKT.prototype.writeGeometry;
+ol.format.WMSGetFeatureInfo.prototype.writeFeatureNode = function(feature, opt_options) {};
 
 
 /**
+ * Not implemented.
  * @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)));
-};
+ol.format.WMSGetFeatureInfo.prototype.writeFeaturesNode = function(features, opt_options) {};
 
 
 /**
- * @const
- * @enum {number}
- * @private
+ * Not implemented.
+ * @inheritDoc
  */
-ol.format.WKT.TokenType_ = {
-  TEXT: 1,
-  LEFT_PAREN: 2,
-  RIGHT_PAREN: 3,
-  NUMBER: 4,
-  COMMA: 5,
-  EOF: 6
-};
+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');
 
 
 /**
- * Class to tokenize a WKT string.
- * @param {string} wkt WKT string.
+ * @classdesc
+ * Format for reading WMTS capabilities data.
+ *
  * @constructor
- * @protected
+ * @extends {ol.format.XML}
+ * @api
  */
-ol.format.WKT.Lexer = function(wkt) {
-
-  /**
-   * @type {string}
-   */
-  this.wkt = wkt;
+ol.format.WMTSCapabilities = function() {
+  ol.format.XML.call(this);
 
   /**
-   * @type {number}
+   * @type {ol.format.OWS}
    * @private
    */
-  this.index_ = -1;
+  this.owsParser_ = new ol.format.OWS();
 };
+ol.inherits(ol.format.WMTSCapabilities, ol.format.XML);
 
 
 /**
- * @param {string} c Character.
- * @return {boolean} Whether the character is alphabetic.
- * @private
+ * Read a WMTS capabilities document.
+ *
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMTS capabilities.
+ * @api
  */
-ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) {
-  return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
-};
+ol.format.WMTSCapabilities.prototype.read;
 
 
 /**
- * @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
+ * @inheritDoc
  */
-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;
+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;
 };
 
 
 /**
- * @param {string} c Character.
- * @return {boolean} Whether the character is whitespace.
- * @private
+ * @inheritDoc
  */
-ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
-  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+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;
 };
 
 
 /**
- * @return {string} Next string character.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
  */
-ol.format.WKT.Lexer.prototype.nextChar_ = function() {
-  return this.wkt.charAt(++this.index_);
+ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Fetch and return the next token.
- * @return {!ol.WKTToken} Next string token.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layers object.
  */
-ol.format.WKT.Lexer.prototype.nextToken = function() {
-  var c = this.nextChar_();
-  var token = {position: this.index_, value: c};
-
-  if (c == '(') {
-    token.type = ol.format.WKT.TokenType_.LEFT_PAREN;
-  } else if (c == ',') {
-    token.type = ol.format.WKT.TokenType_.COMMA;
-  } else if (c == ')') {
-    token.type = ol.format.WKT.TokenType_.RIGHT_PAREN;
-  } else if (this.isNumeric_(c) || c == '-') {
-    token.type = ol.format.WKT.TokenType_.NUMBER;
-    token.value = this.readNumber_();
-  } else if (this.isAlpha_(c)) {
-    token.type = ol.format.WKT.TokenType_.TEXT;
-    token.value = this.readText_();
-  } else if (this.isWhiteSpace_(c)) {
-    return this.nextToken();
-  } else if (c === '') {
-    token.type = ol.format.WKT.TokenType_.EOF;
-  } else {
-    throw new Error('Unexpected character: ' + c);
-  }
-
-  return token;
+ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {number} Numeric token value.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set object.
  */
-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_--));
+ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {string} String token value.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Style object.
  */
-ol.format.WKT.Lexer.prototype.readText_ = function() {
-  var c, index = this.index_;
-  do {
-    c = this.nextChar_();
-  } while (this.isAlpha_(c));
-  return this.wkt.substring(index, this.index_--).toUpperCase();
+ol.format.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;
+
 };
 
 
 /**
- * Class to parse the tokens from the WKT string.
- * @param {ol.format.WKT.Lexer} lexer The lexer.
- * @constructor
- * @protected
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set Link object.
  */
-ol.format.WKT.Parser = function(lexer) {
-
-  /**
-   * @type {ol.format.WKT.Lexer}
-   * @private
-   */
-  this.lexer_ = lexer;
-
-  /**
-   * @type {ol.WKTToken}
-   * @private
-   */
-  this.token_;
-
-  /**
-   * @type {ol.geom.GeometryLayout}
-   * @private
-   */
-  this.layout_ = ol.geom.GeometryLayout.XY;
+ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
+    objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * Fetch the next token form the lexer and replace the active token.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Dimension object.
  */
-ol.format.WKT.Parser.prototype.consume_ = function() {
-  this.token_ = this.lexer_.nextToken();
+ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack);
 };
 
+
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Resource URL object.
  */
-ol.format.WKT.Parser.prototype.isTokenType = function(type) {
-  var isMatch = this.token_.type == type;
-  return isMatch;
+ol.format.WMTSCapabilities.readResourceUrl_ = function(node, objectStack) {
+  var format = node.getAttribute('format');
+  var template = node.getAttribute('template');
+  var resourceType = node.getAttribute('resourceType');
+  var resource = {};
+  if (format) {
+    resource['format'] = format;
+  }
+  if (template) {
+    resource['template'] = template;
+  }
+  if (resourceType) {
+    resource['resourceType'] = resourceType;
+  }
+  return resource;
 };
 
 
 /**
- * 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.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} WGS84 BBox object.
  */
-ol.format.WKT.Parser.prototype.match = function(type) {
-  var isMatch = this.isTokenType(type);
-  if (isMatch) {
-    this.consume_();
+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 isMatch;
+  return ol.extent.boundingExtent(coordinates);
 };
 
 
 /**
- * Try to parse the tokens provided by the lexer.
- * @return {ol.geom.Geometry} The geometry.
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Legend object.
  */
-ol.format.WKT.Parser.prototype.parse = function() {
-  this.consume_();
-  var geometry = this.parseGeometry_();
-  return geometry;
+ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
+  var legend = {};
+  legend['format'] = node.getAttribute('format');
+  legend['href'] = ol.format.XLink.readHref(node);
+  return legend;
 };
 
 
 /**
- * Try to parse the dimensional info.
- * @return {ol.geom.GeometryLayout} The layout.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Coordinates object.
  */
-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_();
-    }
+ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) {
+  var coordinates = ol.format.XSD.readString(node).split(' ');
+  if (!coordinates || coordinates.length != 2) {
+    return undefined;
   }
-  return layout;
+  var x = +coordinates[0];
+  var y = +coordinates[1];
+  if (isNaN(x) || isNaN(y)) {
+    return undefined;
+  }
+  return [x, y];
 };
 
 
 /**
- * @return {!ol.geom.Geometry} The geometry.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrix object.
  */
-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_());
+ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {!Array.<ol.geom.Geometry>} A collection of geometries.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrixSetLimits Object.
  */
-ol.format.WKT.Parser.prototype.parseGeometryCollectionText_ = function() {
-  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
-    var geometries = [];
-    do {
-      geometries.push(this.parseGeometry_());
-    } while (this.match(ol.format.WKT.TokenType_.COMMA));
-    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
-      return geometries;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
+ol.format.WMTSCapabilities.readTileMatrixLimitsList_ = function(node,
+    objectStack) {
+  return ol.xml.pushParseAndPop([],
+      ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_, node,
+      objectStack);
 };
 
 
 /**
- * @return {Array.<number>} All values in a point.
  * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrixLimits Array.
  */
-ol.format.WKT.Parser.prototype.parsePointText_ = function() {
-  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
-    var coordinates = this.parsePoint_();
-    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return null;
-  }
-  throw new Error(this.formatErrorMessage_());
+ol.format.WMTSCapabilities.readTileMatrixLimits_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_LIMITS_PARSERS_, node, objectStack);
 };
 
 
 /**
- * @return {!Array.<!Array.<number>>} All points in a linestring.
+ * @const
  * @private
+ * @type {Array.<string>}
  */
-ol.format.WKT.Parser.prototype.parseLineStringText_ = function() {
-  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
-    var coordinates = this.parsePointList_();
-    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
+ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/wmts/1.0'
+];
 
 
 /**
- * @return {!Array.<!Array.<number>>} All points in a polygon.
+ * @const
  * @private
+ * @type {Array.<string>}
  */
-ol.format.WKT.Parser.prototype.parsePolygonText_ = function() {
-  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
-    var coordinates = this.parseLineStringTextList_();
-    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
+ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/ows/1.1'
+];
 
 
 /**
- * @return {!Array.<!Array.<number>>} All points in a multipoint.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.prototype.parseMultiPointText_ = function() {
-  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
-    var coordinates;
-    if (this.token_.type == ol.format.WKT.TokenType_.LEFT_PAREN) {
-      coordinates = this.parsePointTextList_();
-    } else {
-      coordinates = this.parsePointList_();
-    }
-    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
+ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'Contents': ol.xml.makeObjectPropertySetter(
+          ol.format.WMTSCapabilities.readContents_)
+    });
 
 
 /**
- * @return {!Array.<!Array.<number>>} All linestring points
- *                                        in a multilinestring.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.prototype.parseMultiLineStringText_ = function() {
-  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
-    var coordinates = this.parseLineStringTextList_();
-    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
+ol.format.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 {!Array.<!Array.<number>>} All polygon points in a multipolygon.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.prototype.parseMultiPolygonText_ = function() {
-  if (this.match(ol.format.WKT.TokenType_.LEFT_PAREN)) {
-    var coordinates = this.parsePolygonTextList_();
-    if (this.match(ol.format.WKT.TokenType_.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
+ol.format.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)
+    }));
 
 
 /**
- * @return {!Array.<number>} A point.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-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_());
-};
+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 {!Array.<!Array.<number>>} An array of points.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.prototype.parsePointList_ = function() {
-  var coordinates = [this.parsePoint_()];
-  while (this.match(ol.format.WKT.TokenType_.COMMA)) {
-    coordinates.push(this.parsePoint_());
-  }
-  return coordinates;
-};
-
+ol.format.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_)
+    });
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.prototype.parsePointTextList_ = function() {
-  var coordinates = [this.parsePointText_()];
-  while (this.match(ol.format.WKT.TokenType_.COMMA)) {
-    coordinates.push(this.parsePointText_());
-  }
-  return coordinates;
-};
+ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_ = ol.xml.makeStructureNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'TileMatrixLimits': ol.xml.makeArrayPusher(
+          ol.format.WMTSCapabilities.readTileMatrixLimits_)
+    });
 
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() {
-  var coordinates = [this.parseLineStringText_()];
-  while (this.match(ol.format.WKT.TokenType_.COMMA)) {
-    coordinates.push(this.parseLineStringText_());
-  }
-  return coordinates;
-};
+ol.format.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)
+    });
 
 
 /**
- * @return {!Array.<!Array.<number>>} An array of points.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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;
-};
+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)
+    }));
 
 
 /**
- * @return {boolean} Whether the token implies an empty geometry.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-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;
-};
+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_)
+    });
 
 
 /**
- * Create an error message for an unexpected token error.
- * @return {string} Error message.
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() {
-  return 'Unexpected `' + this.token_.value + '` at position ' +
-      this.token_.position + ' in `' + this.lexer_.wkt + '`';
-};
+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)
+    }));
 
 
 /**
- * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)}
+ * @const
+ * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @private
  */
-ol.format.WKT.Parser.GeometryConstructor_ = {
-  'POINT': ol.geom.Point,
-  'LINESTRING': ol.geom.LineString,
-  'POLYGON': ol.geom.Polygon,
-  'MULTIPOINT': ol.geom.MultiPoint,
-  'MULTILINESTRING': ol.geom.MultiLineString,
-  'MULTIPOLYGON': ol.geom.MultiPolygon
-};
+ol.format.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');
 
 
 /**
- * @enum {(function(): Array)}
- * @private
+ * @enum {string}
  */
-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.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.format.WMSCapabilities');
+// FIXME handle geolocation not supported
+
+goog.provide('ol.Geolocation');
 
 goog.require('ol');
-goog.require('ol.format.XLink');
-goog.require('ol.format.XML');
-goog.require('ol.format.XSD');
-goog.require('ol.xml');
+goog.require('ol.GeolocationProperty');
+goog.require('ol.Object');
+goog.require('ol.Sphere');
+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.proj.EPSG4326');
 
 
 /**
  * @classdesc
- * Format for reading WMS capabilities data
+ * 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
- * @extends {ol.format.XML}
+ * @extends {ol.Object}
+ * @param {olx.GeolocationOptions=} opt_options Options.
  * @api
  */
-ol.format.WMSCapabilities = function() {
+ol.Geolocation = function(opt_options) {
 
-  ol.format.XML.call(this);
+  ol.Object.call(this);
+
+  var options = opt_options || {};
 
   /**
-   * @type {string|undefined}
+   * The unprojected (EPSG:4326) device position.
+   * @private
+   * @type {ol.Coordinate}
    */
-  this.version = undefined;
-};
-ol.inherits(ol.format.WMSCapabilities, ol.format.XML);
+  this.position_ = null;
 
+  /**
+   * @private
+   * @type {ol.TransformFunction}
+   */
+  this.transform_ = ol.proj.identityTransform;
 
-/**
- * Read a WMS capabilities document.
- *
- * @function
- * @param {Document|Node|string} source The XML source.
- * @return {Object} An object representing the WMS capabilities.
- * @api
- */
-ol.format.WMSCapabilities.prototype.read;
+  /**
+   * @private
+   * @type {ol.Sphere}
+   */
+  this.sphere_ = new ol.Sphere(ol.proj.EPSG4326.RADIUS);
 
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.watchId_ = undefined;
 
-/**
- * @inheritDoc
- */
-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);
-    }
+  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);
+
+  if (options.projection !== undefined) {
+    this.setProjection(options.projection);
   }
-  return null;
+  if (options.trackingOptions !== undefined) {
+    this.setTrackingOptions(options.trackingOptions);
+  }
+
+  this.setTracking(options.tracking !== undefined ? options.tracking : false);
+
 };
+ol.inherits(ol.Geolocation, ol.Object);
 
 
 /**
  * @inheritDoc
  */
-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;
+ol.Geolocation.prototype.disposeInternal = function() {
+  this.setTracking(false);
+  ol.Object.prototype.disposeInternal.call(this);
 };
 
 
 /**
  * @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);
+ol.Geolocation.prototype.handleProjectionChanged_ = function() {
+  var projection = this.getProjection();
+  if (projection) {
+    this.transform_ = ol.proj.getTransformFromProjections(
+        ol.proj.get('EPSG:4326'), projection);
+    if (this.position_) {
+      this.set(
+          ol.GeolocationProperty.POSITION, this.transform_(this.position_));
+    }
+  }
 };
 
 
 /**
  * @private
- * @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'))
-  ];
-
-  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.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;
+    }
+  }
 };
 
 
 /**
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.Extent|undefined} Bounding box object.
+ * @param {GeolocationPosition} position position event.
  */
-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;
-  }
-  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;
+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;
   }
-  return [
-    westBoundLongitude, southBoundLatitude,
-    eastBoundLongitude, northBoundLatitude
-  ];
+  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(
+      this.sphere_, this.position_, coords.accuracy);
+  geometry.applyTransform(this.transform_);
+  this.set(ol.GeolocationProperty.ACCURACY_GEOMETRY, geometry);
+  this.changed();
 };
 
+/**
+ * Triggered when the Geolocation returns an error.
+ * @event error
+ * @api
+ */
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
  * @private
- * @return {Object|undefined} Capability object.
+ * @param {GeolocationPositionError} error error object.
  */
-ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack);
+ol.Geolocation.prototype.positionError_ = function(error) {
+  error.type = ol.events.EventType.ERROR;
+  this.setTracking(false);
+  this.dispatchEvent(/** @type {{type: string, target: undefined}} */ (error));
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Service object.
+ * Get the accuracy of the position in meters.
+ * @return {number|undefined} The accuracy of the position measurement in
+ *     meters.
+ * @observable
+ * @api
  */
-ol.format.WMSCapabilities.readService_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack);
+ol.Geolocation.prototype.getAccuracy = function() {
+  return /** @type {number|undefined} */ (
+    this.get(ol.GeolocationProperty.ACCURACY));
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact information object.
+ * Get a geometry of the position accuracy.
+ * @return {?ol.geom.Polygon} A geometry of the position accuracy.
+ * @observable
+ * @api
  */
-ol.format.WMSCapabilities.readContactInformation_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_,
-        node, objectStack);
+ol.Geolocation.prototype.getAccuracyGeometry = function() {
+  return /** @type {?ol.geom.Polygon} */ (
+    this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null);
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact person object.
+ * Get the altitude associated with the position.
+ * @return {number|undefined} The altitude of the position in meters above mean
+ *     sea level.
+ * @observable
+ * @api
  */
-ol.format.WMSCapabilities.readContactPersonPrimary_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_,
-        node, objectStack);
+ol.Geolocation.prototype.getAltitude = function() {
+  return /** @type {number|undefined} */ (
+    this.get(ol.GeolocationProperty.ALTITUDE));
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact address object.
+ * Get the altitude accuracy of the position.
+ * @return {number|undefined} The accuracy of the altitude measurement in
+ *     meters.
+ * @observable
+ * @api
  */
-ol.format.WMSCapabilities.readContactAddress_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_,
-        node, objectStack);
+ol.Geolocation.prototype.getAltitudeAccuracy = function() {
+  return /** @type {number|undefined} */ (
+    this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY));
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<string>|undefined} Format array.
+ * Get the heading as radians clockwise from North.
+ * @return {number|undefined} The heading of the device in radians from north.
+ * @observable
+ * @api
  */
-ol.format.WMSCapabilities.readException_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack);
+ol.Geolocation.prototype.getHeading = function() {
+  return /** @type {number|undefined} */ (
+    this.get(ol.GeolocationProperty.HEADING));
 };
 
 
 /**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Layer object.
+ * Get the position of the device.
+ * @return {ol.Coordinate|undefined} The current position of the device reported
+ *     in the current projection.
+ * @observable
+ * @api
  */
-ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
+ol.Geolocation.prototype.getPosition = function() {
+  return /** @type {ol.Coordinate|undefined} */ (
+    this.get(ol.GeolocationProperty.POSITION));
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Layer object.
+ * Get the projection associated with the position.
+ * @return {ol.proj.Projection|undefined} The projection the position is
+ *     reported in.
+ * @observable
+ * @api
  */
-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);
-
-  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.Geolocation.prototype.getProjection = function() {
+  return /** @type {ol.proj.Projection|undefined} */ (
+    this.get(ol.GeolocationProperty.PROJECTION));
+};
 
-  var cascaded = ol.format.XSD.readNonNegativeIntegerString(
-        node.getAttribute('cascaded'));
-  if (cascaded === undefined) {
-    cascaded = parentLayerObject['cascaded'];
-  }
-  layerObject['cascaded'] = cascaded;
 
-  var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
-  if (opaque === undefined) {
-    opaque = parentLayerObject['opaque'];
-  }
-  layerObject['opaque'] = opaque !== undefined ? opaque : false;
+/**
+ * Get the speed in meters per second.
+ * @return {number|undefined} The instantaneous speed of the device in meters
+ *     per second.
+ * @observable
+ * @api
+ */
+ol.Geolocation.prototype.getSpeed = function() {
+  return /** @type {number|undefined} */ (
+    this.get(ol.GeolocationProperty.SPEED));
+};
 
-  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;
+/**
+ * Determine if the device location is being tracked.
+ * @return {boolean} The device location is being tracked.
+ * @observable
+ * @api
+ */
+ol.Geolocation.prototype.getTracking = function() {
+  return /** @type {boolean} */ (
+    this.get(ol.GeolocationProperty.TRACKING));
+};
 
-  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]);
-    }
-  });
+/**
+ * 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.Geolocation.prototype.getTrackingOptions = function() {
+  return /** @type {GeolocationPositionOptions|undefined} */ (
+    this.get(ol.GeolocationProperty.TRACKING_OPTIONS));
+};
 
-  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;
+/**
+ * Set the projection to use for transforming the coordinates.
+ * @param {ol.ProjectionLike} projection The projection the position is
+ *     reported in.
+ * @observable
+ * @api
+ */
+ol.Geolocation.prototype.setProjection = function(projection) {
+  this.set(ol.GeolocationProperty.PROJECTION, ol.proj.get(projection));
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object} Dimension object.
+ * Enable or disable tracking.
+ * @param {boolean} tracking Enable tracking.
+ * @observable
+ * @api
  */
-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;
+ol.Geolocation.prototype.setTracking = function(tracking) {
+  this.set(ol.GeolocationProperty.TRACKING, tracking);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
+ * 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.format.WMSCapabilities.readFormatOnlineresource_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_,
-        node, objectStack);
+ol.Geolocation.prototype.setTrackingOptions = function(options) {
+  this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options);
 };
 
+goog.provide('ol.geom.Circle');
+
+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');
+
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Request object.
+ * @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.format.WMSCapabilities.readRequest_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack);
+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);
 };
+ol.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} DCP type object.
+ * Make a complete copy of the geometry.
+ * @return {!ol.geom.Circle} Clone.
+ * @override
+ * @api
  */
-ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack);
+ol.geom.Circle.prototype.clone = function() {
+  var circle = new ol.geom.Circle(null);
+  circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
+  return circle;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} HTTP object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack);
+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;
+  }
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Operation type object.
+ * @inheritDoc
  */
-ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
+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_();
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
+ * Return the center of the circle as {@link ol.Coordinate coordinate}.
+ * @return {ol.Coordinate} Center.
+ * @api
  */
-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;
+ol.geom.Circle.prototype.getCenter = function() {
+  return this.flatCoordinates.slice(0, this.stride);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Authority URL object.
+ * @inheritDoc
  */
-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 undefined;
+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);
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Metadata URL object.
+ * Return the radius of the circle.
+ * @return {number} Radius.
+ * @api
  */
-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;
+ol.geom.Circle.prototype.getRadius = function() {
+  return Math.sqrt(this.getRadiusSquared_());
 };
 
 
 /**
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Style object.
+ * @return {number} Radius squared.
  */
-ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack);
+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;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Keyword list.
+ * @inheritDoc
+ * @api
  */
-ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-        [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack);
+ol.geom.Circle.prototype.getType = function() {
+  return ol.geom.GeometryType.CIRCLE;
 };
 
 
 /**
- * @const
- * @private
- * @type {Array.<string>}
+ * @inheritDoc
+ * @api
  */
-ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/wms'
-];
+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;
+    }
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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_)
-    });
+    return ol.extent.forEachCorner(extent, this.intersectsCoordinate, this);
+  }
+  return false;
+
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * Set the center of the circle as {@link ol.Coordinate coordinate}.
+ * @param {ol.Coordinate} center Center.
+ * @api
  */
-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_)
-    });
+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];
+  }
+  this.setFlatCoordinates(this.layout, flatCoordinates);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * 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.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)
-    });
+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();
+  }
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * @inheritDoc
  */
-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.geom.Circle.prototype.getCoordinates = function() {};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * @inheritDoc
  */
-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.geom.Circle.prototype.setCoordinates = function(coordinates, opt_layout) {};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * @param {ol.geom.GeometryLayout} layout Layout.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
  */
-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)
-    });
+ol.geom.Circle.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
+  this.setFlatCoordinatesInternal(layout, flatCoordinates);
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * Set the radius of the circle. The radius is in the units of the projection.
+ * @param {number} radius Radius.
+ * @api
  */
-ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
-    });
+ol.geom.Circle.prototype.setRadius = function(radius) {
+  this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
+  this.changed();
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * 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.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_)
-    });
+ol.geom.Circle.prototype.transform;
 
+goog.provide('ol.geom.flat.geodesic');
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'OnlineResource': ol.xml.makeObjectPropertySetter(
-          ol.format.XLink.readHref),
-      'LogoURL': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readSizedFormatOnlineresource_)
-    });
+goog.require('ol.math');
+goog.require('ol.proj');
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
  * @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.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.geom.flat.geodesic.line_ = function(interpolate, transform, squaredTolerance) {
+  // FIXME reduce garbage generation
+  // FIXME optimize stack operations
 
+  /** @type {Array.<number>} */
+  var flatCoordinates = [];
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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_)
-    });
+  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];
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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_)
-    });
+  /** @type {Object.<string, boolean>} */
+  var fractions = {};
 
+  var maxIterations = 1e5;
+  var geoM, m, fracA, fracB, fracM, key;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readHTTP_)
-    });
+  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);
+    }
+  }
+
+  return flatCoordinates;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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_)
-    });
+* 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);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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.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.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);
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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.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.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.geom.flat.topology');
 
+goog.require('ol.geom.flat.area');
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
+ * 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.
  */
-ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString)
-    });
+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;
+};
 
-goog.provide('ol.format.WMSGetFeatureInfo');
+goog.provide('ol.Graticule');
 
-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');
+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');
 
 
 /**
- * @classdesc
- * Format for reading WMSGetFeatureInfo format. It uses
- * {@link ol.format.GML2} to read features.
- *
+ * Render a grid for a coordinate system on a map.
  * @constructor
- * @extends {ol.format.XMLFeature}
- * @param {olx.format.WMSGetFeatureInfoOptions=} opt_options Options.
+ * @param {olx.GraticuleOptions=} opt_options Options.
  * @api
  */
-ol.format.WMSGetFeatureInfo = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
+ol.Graticule = function(opt_options) {
+  var options = opt_options || {};
 
   /**
+   * @type {ol.PluggableMap}
    * @private
-   * @type {string}
    */
-  this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver';
-
+  this.map_ = null;
 
   /**
+   * @type {ol.proj.Projection}
    * @private
-   * @type {ol.format.GML2}
    */
-  this.gmlFormat_ = new ol.format.GML2();
-
+  this.projection_ = null;
 
   /**
+   * @type {number}
    * @private
-   * @type {Array.<string>}
    */
-  this.layers_ = options.layers ? options.layers : null;
-
-  ol.format.XMLFeature.call(this);
-};
-ol.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';
-
+  this.maxLat_ = Infinity;
 
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<ol.Feature>} Features.
- * @private
- */
-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];
+  /**
+   * @type {number}
+   * @private
+   */
+  this.maxLon_ = Infinity;
 
-      var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_;
-      var layerName = layer.localName.replace(toRemove, '');
+  /**
+   * @type {number}
+   * @private
+   */
+  this.minLat_ = -Infinity;
 
-      if (this.layers_ && !ol.array.includes(this.layers_, layerName)) {
-        continue;
-      }
+  /**
+   * @type {number}
+   * @private
+   */
+  this.minLon_ = -Infinity;
 
-      var featureType = layerName +
-          ol.format.WMSGetFeatureInfo.featureIdentifier_;
+  /**
+   * @type {number}
+   * @private
+   */
+  this.maxLatP_ = Infinity;
 
-      context['featureType'] = featureType;
-      context['featureNS'] = this.featureNS_;
+  /**
+   * @type {number}
+   * @private
+   */
+  this.maxLonP_ = Infinity;
 
-      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);
-      }
-    }
-  }
-  if (localName == 'FeatureCollection') {
-    var gmlFeatures = ol.xml.pushParseAndPop([],
-        this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
-        [{}], this.gmlFormat_);
-    if (gmlFeatures) {
-      features = gmlFeatures;
-    }
-  }
-  return features;
-};
+  /**
+   * @type {number}
+   * @private
+   */
+  this.minLatP_ = -Infinity;
 
+  /**
+   * @type {number}
+   * @private
+   */
+  this.minLonP_ = -Infinity;
 
-/**
- * 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
- */
-ol.format.WMSGetFeatureInfo.prototype.readFeatures;
+  /**
+   * @type {number}
+   * @private
+   */
+  this.targetSize_ = options.targetSize !== undefined ?
+    options.targetSize : 100;
 
+  /**
+   * @type {number}
+   * @private
+   */
+  this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100;
 
-/**
- * @inheritDoc
- */
-ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = function(node, opt_options) {
-  var options = {};
-  if (opt_options) {
-    ol.obj.assign(options, this.getReadOptions(node, opt_options));
-  }
-  return this.readFeatures_(node, [options]);
-};
+  /**
+   * @type {Array.<ol.geom.LineString>}
+   * @private
+   */
+  this.meridians_ = [];
 
+  /**
+   * @type {Array.<ol.geom.LineString>}
+   * @private
+   */
+  this.parallels_ = [];
 
-/**
- * Not implemented.
- * @inheritDoc
- */
-ol.format.WMSGetFeatureInfo.prototype.writeFeatureNode = function(feature, opt_options) {};
+  /**
+   * @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;
 
-/**
- * Not implemented.
- * @inheritDoc
- */
-ol.format.WMSGetFeatureInfo.prototype.writeFeaturesNode = function(features, opt_options) {};
+  /**
+   * @type {ol.TransformFunction|undefined}
+   * @private
+   */
+  this.toLonLatTransform_ = undefined;
 
+  /**
+   * @type {ol.Coordinate}
+   * @private
+   */
+  this.projectionCenterLonLat_ = null;
 
-/**
- * Not implemented.
- * @inheritDoc
- */
-ol.format.WMSGetFeatureInfo.prototype.writeGeometryNode = function(geometry, opt_options) {};
+  /**
+   * @type {Array.<ol.GraticuleLabelDataType>}
+   * @private
+   */
+  this.meridiansLabels_ = null;
 
-goog.provide('ol.format.WMTSCapabilities');
+  /**
+   * @type {Array.<ol.GraticuleLabelDataType>}
+   * @private
+   */
+  this.parallelsLabels_ = null;
 
-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');
+  if (options.showLabels == true) {
+    var degreesToString = ol.coordinate.degreesToStringHDMS;
 
+    /**
+     * @type {null|function(number):string}
+     * @private
+     */
+    this.lonLabelFormatter_ = options.lonLabelFormatter == undefined ?
+      degreesToString.bind(this, 'EW') : options.lonLabelFormatter;
 
-/**
- * @classdesc
- * Format for reading WMTS capabilities data.
- *
- * @constructor
- * @extends {ol.format.XML}
- * @api
- */
-ol.format.WMTSCapabilities = function() {
-  ol.format.XML.call(this);
+    /**
+     * @type {function(number):string}
+     * @private
+     */
+    this.latLabelFormatter_ = options.latLabelFormatter == undefined ?
+      degreesToString.bind(this, 'NS') : options.latLabelFormatter;
 
-  /**
-   * @type {ol.format.OWS}
-   * @private
-   */
-  this.owsParser_ = new ol.format.OWS();
-};
-ol.inherits(ol.format.WMTSCapabilities, ol.format.XML);
+    /**
+     * 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;
 
-/**
- * Read a WMTS capabilities document.
- *
- * @function
- * @param {Document|Node|string} source The XML source.
- * @return {Object} An object representing the WMTS capabilities.
- * @api
- */
-ol.format.WMTSCapabilities.prototype.read;
+    /**
+     * @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
+        })
+      });
 
-/**
- * @inheritDoc
- */
-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);
-    }
+    this.meridiansLabels_ = [];
+    this.parallelsLabels_ = [];
   }
-  return null;
+
+  this.setMap(options.map !== undefined ? options.map : null);
 };
 
 
 /**
- * @inheritDoc
+ * @type {ol.style.Stroke}
+ * @private
+ * @const
  */
-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;
-};
+ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
+  color: 'rgba(0,0,0,0.2)'
+});
 
 
 /**
+ * TODO can be configurable
+ * @type {Array.<number>}
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Attribution object.
  */
-ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-        ol.format.WMTSCapabilities.CONTENTS_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 {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
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Layers object.
  */
-ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-        ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack);
+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 {ol.geom.LineString} lineString Meridian
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {ol.geom.Point} Meridian point.
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Tile Matrix Set object.
  */
-ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
+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 {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
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Style object.
  */
-ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) {
-  var style = ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack);
-  if (!style) {
-    return undefined;
+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)
+      };
+    }
+    this.parallels_[index++] = lineString;
   }
-  var isDefault = node.getAttribute('isDefault') === 'true';
-  style['isDefault'] = isDefault;
-  return style;
-
+  return index;
 };
 
 
 /**
+ * @param {ol.geom.LineString} lineString Parallels.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} index Index.
+ * @return {ol.geom.Point} Parallel point.
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Tile Matrix Set Link object.
  */
-ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
-    objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
+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 {ol.Extent} extent Extent.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} squaredTolerance Squared tolerance.
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Dimension object.
  */
-ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack);
-};
+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;
+    }
+    if (this.parallelsLabels_) {
+      this.parallelsLabels_.length = 0;
+    }
+    return;
+  }
 
-/**
- * @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;
+  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);
   }
-  if (template) {
-    resource['template'] = template;
+
+  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);
   }
-  if (resourceType) {
-    resource['resourceType'] = resourceType;
+
+  this.meridians_.length = idx;
+  if (this.meridiansLabels_) {
+    this.meridiansLabels_.length = idx;
   }
-  return resource;
-};
 
+  // Create parallels
 
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} WGS84 BBox object.
- */
-ol.format.WMTSCapabilities.readWgs84BoundingBox_ = function(node, objectStack) {
-  var coordinates = ol.xml.pushParseAndPop([],
-      ol.format.WMTSCapabilities.WGS84_BBOX_READERS_, node, objectStack);
-  if (coordinates.length != 2) {
-    return undefined;
-  }
-  return ol.extent.boundingExtent(coordinates);
-};
+  centerLat = Math.floor(centerLat / interval) * interval;
+  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);
 
+  idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0);
 
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Legend object.
- */
-ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
-  var legend = {};
-  legend['format'] = node.getAttribute('format');
-  legend['href'] = ol.format.XLink.readHref(node);
-  return legend;
-};
+  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_);
 
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Coordinates object.
- */
-ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) {
-  var coordinates = ol.format.XSD.readString(node).split(' ');
-  if (!coordinates || coordinates.length != 2) {
-    return undefined;
+  cnt = 0;
+  while (lat != this.maxLat_ && cnt++ < maxLines) {
+    lat = Math.min(lat + interval, this.maxLat_);
+    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
   }
-  var x = +coordinates[0];
-  var y = +coordinates[1];
-  if (isNaN(x) || isNaN(y)) {
-    return undefined;
+
+  this.parallels_.length = idx;
+  if (this.parallelsLabels_) {
+    this.parallelsLabels_.length = idx;
   }
-  return [x, y];
+
 };
 
 
 /**
+ * @param {number} resolution Resolution.
+ * @return {number} The interval in degrees.
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} TileMatrix object.
  */
-ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
+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;
 };
 
 
 /**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} TileMatrixSetLimits Object.
+ * Get the map associated with this graticule.
+ * @return {ol.PluggableMap} The map.
+ * @api
  */
-ol.format.WMTSCapabilities.readTileMatrixLimitsList_ = function(node,
-    objectStack) {
-  return ol.xml.pushParseAndPop([],
-      ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_, node,
-      objectStack);
+ol.Graticule.prototype.getMap = function() {
+  return this.map_;
 };
 
 
 /**
+ * @param {number} lon Longitude.
+ * @param {number} minLat Minimal latitude.
+ * @param {number} maxLat Maximal latitude.
+ * @param {number} squaredTolerance Squared tolerance.
+ * @return {ol.geom.LineString} The meridian line string.
+ * @param {number} index Index.
  * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} TileMatrixLimits Array.
  */
-ol.format.WMTSCapabilities.readTileMatrixLimits_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TMS_LIMITS_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;
 };
 
 
 /**
- * @const
- * @private
- * @type {Array.<string>}
+ * Get the list of meridians.  Meridians are lines of equal longitude.
+ * @return {Array.<ol.geom.LineString>} The meridians.
+ * @api
  */
-ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/wmts/1.0'
-];
+ol.Graticule.prototype.getMeridians = function() {
+  return this.meridians_;
+};
 
 
 /**
- * @const
+ * @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
- * @type {Array.<string>}
  */
-ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/ows/1.1'
-];
+ol.Graticule.prototype.getParallel_ = function(lat, minLon, maxLon,
+    squaredTolerance, index) {
+  var flatCoordinates = ol.geom.flat.geodesic.parallel(lat,
+      minLon, 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.XmlParser>>}
- * @private
+ * Get the list of parallels.  Parallels are lines of equal latitude.
+ * @return {Array.<ol.geom.LineString>} The parallels.
+ * @api
  */
-ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'Contents': ol.xml.makeObjectPropertySetter(
-          ol.format.WMTSCapabilities.readContents_)
-    });
+ol.Graticule.prototype.getParallels = function() {
+  return this.parallels_;
+};
 
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {ol.render.Event} e Event.
  * @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.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);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'Style': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readStyle_),
-      'Format': ol.xml.makeObjectPropertyPusher(
-          ol.format.XSD.readString),
-      'TileMatrixSetLink': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readTileMatrixSetLink_),
-      'Dimension': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readDimensions_),
-      'ResourceURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readResourceUrl_)
-    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'Abstract': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'WGS84BoundingBox': ol.xml.makeObjectPropertySetter(
-          ol.format.WMTSCapabilities.readWgs84BoundingBox_),
-      'Identifier': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    }));
+  var updateProjectionInfo = !this.projection_ ||
+      !ol.proj.equivalent(this.projection_, projection);
 
+  if (updateProjectionInfo) {
+    this.updateProjectionInfo_(projection);
+  }
 
-/**
- * @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)
-    }));
+  this.createGraticule_(extent, center, resolution, squaredTolerance);
 
+  // Draw the lines
+  vectorContext.setFillStrokeStyle(null, this.strokeStyle_);
+  var i, l, line;
+  for (i = 0, l = this.meridians_.length; i < l; ++i) {
+    line = this.meridians_[i];
+    vectorContext.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.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_)
-    });
 
 /**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
+ * @param {ol.proj.Projection} projection Projection.
  * @private
  */
-ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'TileMatrixLimits': ol.xml.makeArrayPusher(
-          ol.format.WMTSCapabilities.readTileMatrixLimits_)
-    });
-
+ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
+  var epsg4326Projection = ol.proj.get('EPSG:4326');
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-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)
-    });
+  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];
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.DIMENSION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'Default': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'Value': ol.xml.makeObjectPropertyPusher(
-          ol.format.XSD.readString)
-    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'Identifier': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    }));
+  var maxLatP = worldExtentP[3];
+  var maxLonP = worldExtentP[2];
+  var minLatP = worldExtentP[1];
+  var minLonP = worldExtentP[0];
 
+  this.maxLat_ = maxLat;
+  this.maxLon_ = maxLon;
+  this.minLat_ = minLat;
+  this.minLon_ = minLon;
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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_)
-    });
+  this.maxLatP_ = maxLatP;
+  this.maxLonP_ = maxLonP;
+  this.minLatP_ = minLatP;
+  this.minLonP_ = minLonP;
 
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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)
-    }));
+  this.fromLonLatTransform_ = ol.proj.getTransform(
+      epsg4326Projection, projection);
 
+  this.toLonLatTransform_ = ol.proj.getTransform(
+      projection, epsg4326Projection);
 
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @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)
-    }));
+  this.projectionCenterLonLat_ = this.toLonLatTransform_(
+      ol.extent.getCenter(extent));
 
-goog.provide('ol.GeolocationProperty');
+  this.projection_ = projection;
+};
 
 
 /**
- * @enum {string}
+ * Set the map for this graticule.  The graticule will be rendered on the
+ * provided map.
+ * @param {ol.PluggableMap} map Map.
+ * @api
  */
-ol.GeolocationProperty = {
-  ACCURACY: 'accuracy',
-  ACCURACY_GEOMETRY: 'accuracyGeometry',
-  ALTITUDE: 'altitude',
-  ALTITUDE_ACCURACY: 'altitudeAccuracy',
-  HEADING: 'heading',
-  POSITION: 'position',
-  PROJECTION: 'projection',
-  SPEED: 'speed',
-  TRACKING: 'tracking',
-  TRACKING_OPTIONS: 'trackingOptions'
+ol.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;
 };
 
-// FIXME handle geolocation not supported
-
-goog.provide('ol.Geolocation');
+goog.provide('ol.Image');
 
 goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.GeolocationProperty');
+goog.require('ol.ImageBase');
+goog.require('ol.ImageState');
 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');
+goog.require('ol.extent');
 
 
 /**
- * @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());
- *     });
- *
- * @fires error
  * @constructor
- * @extends {ol.Object}
- * @param {olx.GeolocationOptions=} opt_options Options.
- * @api
+ * @extends {ol.ImageBase}
+ * @param {ol.Extent} extent Extent.
+ * @param {number|undefined} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {string} src Image source URI.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
  */
-ol.Geolocation = function(opt_options) {
-
-  ol.Object.call(this);
+ol.Image = function(extent, resolution, pixelRatio, src, crossOrigin, imageLoadFunction) {
 
-  var options = opt_options || {};
+  ol.ImageBase.call(this, extent, resolution, pixelRatio, ol.ImageState.IDLE);
 
   /**
-   * The unprojected (EPSG:4326) device position.
    * @private
-   * @type {ol.Coordinate}
+   * @type {string}
    */
-  this.position_ = null;
+  this.src_ = src;
 
   /**
    * @private
-   * @type {ol.TransformFunction}
+   * @type {HTMLCanvasElement|Image|HTMLVideoElement}
    */
-  this.transform_ = ol.proj.identityTransform;
+  this.image_ = new Image();
+  if (crossOrigin !== null) {
+    this.image_.crossOrigin = crossOrigin;
+  }
 
   /**
    * @private
-   * @type {number|undefined}
+   * @type {Array.<ol.EventsKey>}
    */
-  this.watchId_ = undefined;
-
-  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);
+  this.imageListenerKeys_ = null;
 
-  if (options.projection !== undefined) {
-    this.setProjection(options.projection);
-  }
-  if (options.trackingOptions !== undefined) {
-    this.setTrackingOptions(options.trackingOptions);
-  }
+  /**
+   * @protected
+   * @type {ol.ImageState}
+   */
+  this.state = ol.ImageState.IDLE;
 
-  this.setTracking(options.tracking !== undefined ? options.tracking : false);
+  /**
+   * @private
+   * @type {ol.ImageLoadFunctionType}
+   */
+  this.imageLoadFunction_ = imageLoadFunction;
 
 };
-ol.inherits(ol.Geolocation, ol.Object);
+ol.inherits(ol.Image, ol.ImageBase);
 
 
 /**
  * @inheritDoc
+ * @api
  */
-ol.Geolocation.prototype.disposeInternal = function() {
-  this.setTracking(false);
-  ol.Object.prototype.disposeInternal.call(this);
+ol.Image.prototype.getImage = function() {
+  return this.image_;
 };
 
 
 /**
+ * Tracks loading or read errors.
+ *
  * @private
  */
-ol.Geolocation.prototype.handleProjectionChanged_ = function() {
-  var projection = this.getProjection();
-  if (projection) {
-    this.transform_ = ol.proj.getTransformFromProjections(
-        ol.proj.get('EPSG:4326'), projection);
-    if (this.position_) {
-      this.set(
-          ol.GeolocationProperty.POSITION, this.transform_(this.position_));
-    }
-  }
+ol.Image.prototype.handleImageError_ = function() {
+  this.state = ol.ImageState.ERROR;
+  this.unlistenImage_();
+  this.changed();
 };
 
 
 /**
+ * Tracks successful image load.
+ *
  * @private
  */
-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;
-    }
+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();
 };
 
 
 /**
- * @private
- * @param {GeolocationPosition} position position event.
+ * 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.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;
+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_);
   }
-  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();
 };
 
-/**
- * Triggered when the Geolocation returns an error.
- * @event error
- * @api
- */
 
 /**
- * @private
- * @param {GeolocationPositionError} error error object.
+ * @param {HTMLCanvasElement|Image|HTMLVideoElement} image Image.
  */
-ol.Geolocation.prototype.positionError_ = function(error) {
-  error.type = ol.events.EventType.ERROR;
-  this.setTracking(false);
-  this.dispatchEvent(/** @type {{type: string, target: undefined}} */ (error));
+ol.Image.prototype.setImage = function(image) {
+  this.image_ = image;
 };
 
 
 /**
- * Get the accuracy of the position in meters.
- * @return {number|undefined} The accuracy of the position measurement in
- *     meters.
- * @observable
- * @api
+ * Discards event handlers which listen for load completion or errors.
+ *
+ * @private
  */
-ol.Geolocation.prototype.getAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ACCURACY));
+ol.Image.prototype.unlistenImage_ = function() {
+  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
 };
 
+goog.provide('ol.Tile');
+
+goog.require('ol');
+goog.require('ol.TileState');
+goog.require('ol.easing');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
+
 
 /**
- * Get a geometry of the position accuracy.
- * @return {?ol.geom.Polygon} A geometry of the position accuracy.
- * @observable
- * @api
+ * @classdesc
+ * Base class for tiles.
+ *
+ * @constructor
+ * @abstract
+ * @extends {ol.events.EventTarget}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
+ * @param {olx.TileOptions=} opt_options Tile options.
  */
-ol.Geolocation.prototype.getAccuracyGeometry = function() {
-  return /** @type {?ol.geom.Polygon} */ (
-      this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null);
+ol.Tile = function(tileCoord, state, opt_options) {
+  ol.events.EventTarget.call(this);
+
+  var options = opt_options ? opt_options : {};
+
+  /**
+   * @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 = '';
+
+  /**
+   * The duration for the opacity transition.
+   * @type {number}
+   */
+  this.transition_ = options.transition === undefined ?
+    250 : options.transition;
+
+  /**
+   * Lookup of start times for rendering transitions.  If the start time is
+   * equal to -1, the transition is complete.
+   * @type {Object.<number, number>}
+   */
+  this.transitionStarts_ = {};
+
 };
+ol.inherits(ol.Tile, ol.events.EventTarget);
 
 
 /**
- * Get the altitude associated with the position.
- * @return {number|undefined} The altitude of the position in meters above mean
- *     sea level.
- * @observable
- * @api
+ * @protected
  */
-ol.Geolocation.prototype.getAltitude = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ALTITUDE));
+ol.Tile.prototype.changed = function() {
+  this.dispatchEvent(ol.events.EventType.CHANGE);
 };
 
 
 /**
- * Get the altitude accuracy of the position.
- * @return {number|undefined} The accuracy of the altitude measurement in
- *     meters.
- * @observable
- * @api
+ * @return {string} Key.
  */
-ol.Geolocation.prototype.getAltitudeAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY));
+ol.Tile.prototype.getKey = function() {
+  return this.key + '/' + this.tileCoord;
 };
 
-
 /**
- * Get the heading as radians clockwise from North.
- * @return {number|undefined} The heading of the device in radians from north.
- * @observable
- * @api
+ * 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.Geolocation.prototype.getHeading = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.HEADING));
-};
+ol.Tile.prototype.getInterimTile = function() {
+  if (!this.interimTile) {
+    //empty chain
+    return this;
+  }
+  var tile = this.interimTile;
+
+  // 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;
+    }
+    tile = tile.interimTile;
+  } while (tile);
 
+  // we can not find a better tile
+  return this;
+};
 
 /**
- * Get the position of the device.
- * @return {ol.Coordinate|undefined} The current position of the device reported
- *     in the current projection.
- * @observable
- * @api
+ * Goes through the chain of interim tiles and discards sections of the chain
+ * that are no longer relevant.
  */
-ol.Geolocation.prototype.getPosition = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.GeolocationProperty.POSITION));
-};
+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);
+};
 
 /**
- * Get the projection associated with the position.
- * @return {ol.proj.Projection|undefined} The projection the position is
- *     reported in.
- * @observable
+ * Get the tile coordinate for this tile.
+ * @return {ol.TileCoord} The tile coordinate.
  * @api
  */
-ol.Geolocation.prototype.getProjection = function() {
-  return /** @type {ol.proj.Projection|undefined} */ (
-      this.get(ol.GeolocationProperty.PROJECTION));
+ol.Tile.prototype.getTileCoord = function() {
+  return this.tileCoord;
 };
 
 
 /**
- * Get the speed in meters per second.
- * @return {number|undefined} The instantaneous speed of the device in meters
- *     per second.
- * @observable
- * @api
+ * @return {ol.TileState} State.
  */
-ol.Geolocation.prototype.getSpeed = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.GeolocationProperty.SPEED));
+ol.Tile.prototype.getState = function() {
+  return this.state;
 };
 
-
 /**
- * Determine if the device location is being tracked.
- * @return {boolean} The device location is being tracked.
- * @observable
- * @api
+ * @param {ol.TileState} state State.
  */
-ol.Geolocation.prototype.getTracking = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.GeolocationProperty.TRACKING));
+ol.Tile.prototype.setState = function(state) {
+  this.state = state;
+  this.changed();
 };
 
-
 /**
- * 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
+ * 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.Geolocation.prototype.getTrackingOptions = function() {
-  return /** @type {GeolocationPositionOptions|undefined} */ (
-      this.get(ol.GeolocationProperty.TRACKING_OPTIONS));
-};
-
+ol.Tile.prototype.load = function() {};
 
 /**
- * Set the projection to use for transforming the coordinates.
- * @param {ol.ProjectionLike} projection The projection the position is
- *     reported in.
- * @observable
- * @api
+ * Get the alpha value for rendering.
+ * @param {number} id An id for the renderer.
+ * @param {number} time The render frame time.
+ * @return {number} A number between 0 and 1.
  */
-ol.Geolocation.prototype.setProjection = function(projection) {
-  this.set(ol.GeolocationProperty.PROJECTION, ol.proj.get(projection));
-};
+ol.Tile.prototype.getAlpha = function(id, time) {
+  if (!this.transition_) {
+    return 1;
+  }
 
+  var start = this.transitionStarts_[id];
+  if (!start) {
+    start = time;
+    this.transitionStarts_[id] = start;
+  } else if (start === -1) {
+    return 1;
+  }
+
+  var delta = time - start + (1000 / 60); // avoid rendering at 0
+  if (delta >= this.transition_) {
+    return 1;
+  }
+  return ol.easing.easeIn(delta / this.transition_);
+};
 
 /**
- * Enable or disable tracking.
- * @param {boolean} tracking Enable tracking.
- * @observable
- * @api
+ * Determine if a tile is in an alpha transition.  A tile is considered in
+ * transition if tile.getAlpha() has not yet been called or has been called
+ * and returned 1.
+ * @param {number} id An id for the renderer.
+ * @return {boolean} The tile is in transition.
  */
-ol.Geolocation.prototype.setTracking = function(tracking) {
-  this.set(ol.GeolocationProperty.TRACKING, tracking);
+ol.Tile.prototype.inTransition = function(id) {
+  if (!this.transition_) {
+    return false;
+  }
+  return this.transitionStarts_[id] !== -1;
 };
 
-
 /**
- * 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
+ * Mark a transition as complete.
+ * @param {number} id An id for the renderer.
  */
-ol.Geolocation.prototype.setTrackingOptions = function(options) {
-  this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options);
+ol.Tile.prototype.endTransition = function(id) {
+  if (this.transition_) {
+    this.transitionStarts_[id] = -1;
+  }
 };
 
-goog.provide('ol.geom.Circle');
+goog.provide('ol.ImageTile');
 
 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.Tile');
+goog.require('ol.TileState');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
 
 
 /**
- * @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
+ * @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 {olx.TileOptions=} opt_options Tile options.
  */
-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);
+ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options) {
+
+  ol.Tile.call(this, tileCoord, state, opt_options);
+
+  /**
+   * @private
+   * @type {?string}
+   */
+  this.crossOrigin_ = crossOrigin;
+
+  /**
+   * Image URI
+   *
+   * @private
+   * @type {string}
+   */
+  this.src_ = src;
+
+  /**
+   * @private
+   * @type {Image|HTMLCanvasElement}
+   */
+  this.image_ = new Image();
+  if (crossOrigin !== null) {
+    this.image_.crossOrigin = crossOrigin;
+  }
+
+  /**
+   * @private
+   * @type {Array.<ol.EventsKey>}
+   */
+  this.imageListenerKeys_ = null;
+
+  /**
+   * @private
+   * @type {ol.TileLoadFunctionType}
+   */
+  this.tileLoadFunction_ = tileLoadFunction;
+
 };
-ol.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);
+ol.inherits(ol.ImageTile, ol.Tile);
 
 
 /**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Circle} Clone.
- * @override
- * @api
+ * @inheritDoc
  */
-ol.geom.Circle.prototype.clone = function() {
-  var circle = new ol.geom.Circle(null);
-  circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return circle;
+ol.ImageTile.prototype.disposeInternal = function() {
+  if (this.state == ol.TileState.LOADING) {
+    this.unlistenImage_();
+    this.image_ = ol.ImageTile.getBlankImage();
+  }
+  if (this.interimTile) {
+    this.interimTile.dispose();
+  }
+  this.state = ol.TileState.ABORT;
+  this.changed();
+  ol.Tile.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * @inheritDoc
+ * Get the HTML image element for this tile (may be a Canvas, Image, or Video).
+ * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
+ * @api
  */
-ol.geom.Circle.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  var flatCoordinates = this.flatCoordinates;
-  var dx = x - flatCoordinates[0];
-  var dy = y - flatCoordinates[1];
-  var squaredDistance = dx * dx + dy * dy;
-  if (squaredDistance < minSquaredDistance) {
-    var i;
-    if (squaredDistance === 0) {
-      for (i = 0; i < this.stride; ++i) {
-        closestPoint[i] = flatCoordinates[i];
-      }
-    } else {
-      var delta = this.getRadius() / Math.sqrt(squaredDistance);
-      closestPoint[0] = flatCoordinates[0] + delta * dx;
-      closestPoint[1] = flatCoordinates[1] + delta * dy;
-      for (i = 2; i < this.stride; ++i) {
-        closestPoint[i] = flatCoordinates[i];
-      }
-    }
-    closestPoint.length = this.stride;
-    return squaredDistance;
-  } else {
-    return minSquaredDistance;
-  }
+ol.ImageTile.prototype.getImage = function() {
+  return this.image_;
 };
 
 
 /**
  * @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_();
+ol.ImageTile.prototype.getKey = function() {
+  return this.src_;
 };
 
 
 /**
- * Return the center of the circle as {@link ol.Coordinate coordinate}.
- * @return {ol.Coordinate} Center.
- * @api
+ * Tracks loading or read errors.
+ *
+ * @private
  */
-ol.geom.Circle.prototype.getCenter = function() {
-  return this.flatCoordinates.slice(0, this.stride);
+ol.ImageTile.prototype.handleImageError_ = function() {
+  this.state = ol.TileState.ERROR;
+  this.unlistenImage_();
+  this.image_ = ol.ImageTile.getBlankImage();
+  this.changed();
 };
 
 
 /**
- * @inheritDoc
+ * Tracks successful image load.
+ *
+ * @private
  */
-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);
+ol.ImageTile.prototype.handleImageLoad_ = function() {
+  if (this.image_.naturalWidth && this.image_.naturalHeight) {
+    this.state = ol.TileState.LOADED;
+  } else {
+    this.state = ol.TileState.EMPTY;
+  }
+  this.unlistenImage_();
+  this.changed();
 };
 
 
 /**
- * Return the radius of the circle.
- * @return {number} Radius.
+ * @inheritDoc
  * @api
  */
-ol.geom.Circle.prototype.getRadius = function() {
-  return Math.sqrt(this.getRadiusSquared_());
+ol.ImageTile.prototype.load = function() {
+  if (this.state == ol.TileState.ERROR) {
+    this.state = ol.TileState.IDLE;
+    this.image_ = new Image();
+    if (this.crossOrigin_ !== null) {
+      this.image_.crossOrigin = this.crossOrigin_;
+    }
+  }
+  if (this.state == ol.TileState.IDLE) {
+    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_);
+  }
 };
 
 
 /**
+ * Discards event handlers which listen for load completion or errors.
+ *
  * @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;
+ol.ImageTile.prototype.unlistenImage_ = function() {
+  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.imageListenerKeys_ = null;
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * Get a 1-pixel blank image.
+ * @return {HTMLCanvasElement} Blank image.
  */
-ol.geom.Circle.prototype.getType = function() {
-  return ol.geom.GeometryType.CIRCLE;
+ol.ImageTile.getBlankImage = function() {
+  var ctx = ol.dom.createCanvasContext2D(1, 1);
+  ctx.fillStyle = 'rgba(0,0,0,0)';
+  ctx.fillRect(0, 0, 1, 1);
+  return ctx.canvas;
 };
 
+// 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');
+
 
 /**
- * @inheritDoc
+ * @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.geom.Circle.prototype.intersectsExtent = function(extent) {
-  var circleExtent = this.getExtent();
-  if (ol.extent.intersects(extent, circleExtent)) {
-    var center = this.getCenter();
+ol.interaction.DragAndDrop = function(opt_options) {
 
-    if (extent[0] <= center[0] && extent[2] >= center[0]) {
-      return true;
-    }
-    if (extent[1] <= center[1] && extent[3] >= center[1]) {
-      return true;
-    }
+  var options = opt_options ? opt_options : {};
 
-    return ol.extent.forEachCorner(extent, this.intersectsCoordinate, this);
-  }
-  return false;
+  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 {ol.source.Vector}
+   */
+  this.source_ = options.source || null;
+
+  /**
+   * @private
+   * @type {Element}
+   */
+  this.target = options.target ? options.target : null;
 
-/**
- * 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;
-  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);
 };
+ol.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
 
 
 /**
- * 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
+ * @param {Event} event Event.
+ * @this {ol.interaction.DragAndDrop}
+ * @private
  */
-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();
+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);
   }
 };
 
 
 /**
- * @inheritDoc
+ * @param {Event} event Event.
+ * @private
  */
-ol.geom.Circle.prototype.getCoordinates = function() {};
+ol.interaction.DragAndDrop.handleStop_ = function(event) {
+  event.stopPropagation();
+  event.preventDefault();
+  event.dataTransfer.dropEffect = 'copy';
+};
 
 
 /**
- * @inheritDoc
+ * @param {File} file File.
+ * @param {Event} event Load event.
+ * @private
  */
-ol.geom.Circle.prototype.setCoordinates = function(coordinates, opt_layout) {};
-
+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();
+  }
 
-/**
- * @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();
+  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;
+    }
+  }
+  if (this.source_) {
+    this.source_.clear();
+    this.source_.addFeatures(features);
+  }
+  this.dispatchEvent(
+      new ol.interaction.DragAndDrop.Event(
+          ol.interaction.DragAndDrop.EventType_.ADD_FEATURES, file,
+          features, projection));
 };
 
 
 /**
- * Set the radius of the circle. The radius is in the units of the projection.
- * @param {number} radius Radius.
+ * 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.geom.Circle.prototype.setRadius = function(radius) {
-  this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
-  this.changed();
-};
+ol.interaction.DragAndDrop.handleEvent = ol.functions.TRUE;
 
 
 /**
- * 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
+ * @private
  */
-ol.geom.Circle.prototype.transform;
-
-goog.provide('ol.geom.flat.geodesic');
-
-goog.require('ol.math');
-goog.require('ol.proj');
+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)
+    ];
+  }
+};
 
 
 /**
- * @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.
+ * @inheritDoc
  */
-ol.geom.flat.geodesic.line_ = function(interpolate, transform, squaredTolerance) {
-  // FIXME reduce garbage generation
-  // FIXME optimize stack operations
-
-  /** @type {Array.<number>} */
-  var flatCoordinates = [];
-
-  var geoA = interpolate(0);
-  var geoB = interpolate(1);
-
-  var a = transform(geoA);
-  var b = transform(geoB);
-
-  /** @type {Array.<ol.Coordinate>} */
-  var geoStack = [geoB, geoA];
-  /** @type {Array.<ol.Coordinate>} */
-  var stack = [b, a];
-  /** @type {Array.<number>} */
-  var fractionStack = [1, 0];
-
-  /** @type {Object.<string, boolean>} */
-  var fractions = {};
-
-  var maxIterations = 1e5;
-  var geoM, m, fracA, fracB, fracM, key;
-
-  while (--maxIterations > 0 && fractionStack.length > 0) {
-    // Pop the a coordinate off the stack
-    fracA = fractionStack.pop();
-    geoA = geoStack.pop();
-    a = stack.pop();
-    // Add the a coordinate if it has not been added yet
-    key = fracA.toString();
-    if (!(key in fractions)) {
-      flatCoordinates.push(a[0], a[1]);
-      fractions[key] = true;
-    }
-    // Pop the b coordinate off the stack
-    fracB = fractionStack.pop();
-    geoB = geoStack.pop();
-    b = stack.pop();
-    // Find the m point between the a and b coordinates
-    fracM = (fracA + fracB) / 2;
-    geoM = interpolate(fracM);
-    m = transform(geoM);
-    if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1],
-        b[0], b[1]) < squaredTolerance) {
-      // If the m point is sufficiently close to the straight line, then we
-      // discard it.  Just use the b coordinate and move on to the next line
-      // segment.
-      flatCoordinates.push(b[0], b[1]);
-      key = fracB.toString();
-      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.DragAndDrop.prototype.setActive = function(active) {
+  ol.interaction.Interaction.prototype.setActive.call(this, active);
+  if (active) {
+    this.registerListeners_();
+  } else {
+    this.unregisterListeners_();
   }
-
-  return flatCoordinates;
 };
 
 
 /**
-* Generate a great-circle arcs between two lat/lon points.
-* @param {number} lon1 Longitude 1 in degrees.
-* @param {number} lat1 Latitude 1 in degrees.
-* @param {number} lon2 Longitude 2 in degrees.
-* @param {number} lat2 Latitude 2 in degrees.
- * @param {ol.proj.Projection} projection Projection.
-* @param {number} squaredTolerance Squared tolerance.
-* @return {Array.<number>} Flat coordinates.
-*/
-ol.geom.flat.geodesic.greatCircleArc = function(
-    lon1, lat1, lon2, lat2, projection, squaredTolerance) {
-
-  var geoProjection = ol.proj.get('EPSG:4326');
-
-  var cosLat1 = Math.cos(ol.math.toRadians(lat1));
-  var sinLat1 = Math.sin(ol.math.toRadians(lat1));
-  var cosLat2 = Math.cos(ol.math.toRadians(lat2));
-  var sinLat2 = Math.sin(ol.math.toRadians(lat2));
-  var cosDeltaLon = Math.cos(ol.math.toRadians(lon2 - lon1));
-  var sinDeltaLon = Math.sin(ol.math.toRadians(lon2 - lon1));
-  var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon;
-
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
-       */
-      function(frac) {
-        if (1 <= d) {
-          return [lon2, lat2];
-        }
-        var D = frac * Math.acos(d);
-        var cosD = Math.cos(D);
-        var sinD = Math.sin(D);
-        var y = sinDeltaLon * cosLat2;
-        var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon;
-        var theta = Math.atan2(y, x);
-        var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta));
-        var lon = ol.math.toRadians(lon1) +
-            Math.atan2(Math.sin(theta) * sinD * cosLat1,
-                       cosD - sinLat1 * Math.sin(lat));
-        return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
-      }, ol.proj.getTransform(geoProjection, projection), squaredTolerance);
+ * @inheritDoc
+ */
+ol.interaction.DragAndDrop.prototype.setMap = function(map) {
+  this.unregisterListeners_();
+  ol.interaction.Interaction.prototype.setMap.call(this, map);
+  if (this.getActive()) {
+    this.registerListeners_();
+  }
 };
 
 
 /**
- * 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.
+ * @param {ol.format.Feature} format Format.
+ * @param {string} text Text.
+ * @param {olx.format.ReadOptions} options Read options.
+ * @private
+ * @return {Array.<ol.Feature>} Features.
  */
-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.DragAndDrop.prototype.tryReadFeatures_ = function(format, text, options) {
+  try {
+    return format.readFeatures(text, options);
+  } catch (e) {
+    return null;
+  }
 };
 
 
 /**
- * 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.
+ * @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.DragAndDrop.prototype.unregisterListeners_ = function() {
+  if (this.dropListenKeys_) {
+    this.dropListenKeys_.forEach(ol.events.unlistenByKey);
+    this.dropListenKeys_ = null;
+  }
 };
 
-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');
+/**
+ * @enum {string}
+ * @private
+ */
+ol.interaction.DragAndDrop.EventType_ = {
+  /**
+   * Triggered when features are added
+   * @event ol.interaction.DragAndDrop.Event#addfeatures
+   * @api
+   */
+  ADD_FEATURES: 'addfeatures'
+};
 
 
 /**
- * Render a grid for a coordinate system on a map.
+ * @classdesc
+ * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances
+ * of this type.
+ *
  * @constructor
- * @param {olx.GraticuleOptions=} opt_options Options.
- * @api
+ * @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.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;
+ol.interaction.DragAndDrop.Event = function(type, file, opt_features, opt_projection) {
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.minLat_ = -Infinity;
+  ol.events.Event.call(this, type);
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.minLon_ = -Infinity;
+  /**
+   * The features parsed from dropped data.
+   * @type {Array.<ol.Feature>|undefined}
+   * @api
+   */
+  this.features = opt_features;
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.maxLatP_ = Infinity;
+  /**
+   * The dropped file.
+   * @type {File}
+   * @api
+   */
+  this.file = file;
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.maxLonP_ = Infinity;
+  /**
+   * The feature projection.
+   * @type {ol.proj.Projection|undefined}
+   * @api
+   */
+  this.projection = opt_projection;
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.minLatP_ = -Infinity;
+};
+ol.inherits(ol.interaction.DragAndDrop.Event, ol.events.Event);
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.minLonP_ = -Infinity;
+goog.provide('ol.interaction.DragRotateAndZoom');
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.targetSize_ = options.targetSize !== undefined ?
-     options.targetSize : 100;
+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');
 
- /**
-  * @type {number}
-  * @private
-  */
-  this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100;
 
- /**
-  * @type {Array.<ol.geom.LineString>}
-  * @private
-  */
-  this.meridians_ = [];
+/**
+ * @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.interaction.DragRotateAndZoom = function(opt_options) {
 
- /**
-  * @type {Array.<ol.geom.LineString>}
-  * @private
-  */
-  this.parallels_ = [];
+  var options = opt_options ? opt_options : {};
 
-  /**
-   * @type {ol.style.Stroke}
-   * @private
-   */
-  this.strokeStyle_ = options.strokeStyle !== undefined ?
-     options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_;
+  ol.interaction.Pointer.call(this, {
+    handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_,
+    handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_,
+    handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_
+  });
 
   /**
-   * @type {ol.TransformFunction|undefined}
    * @private
+   * @type {ol.EventsConditionType}
    */
-  this.fromLonLatTransform_ = undefined;
+  this.condition_ = options.condition ?
+    options.condition : ol.events.condition.shiftKeyOnly;
 
   /**
-   * @type {ol.TransformFunction|undefined}
    * @private
+   * @type {number|undefined}
    */
-  this.toLonLatTransform_ = undefined;
+  this.lastAngle_ = undefined;
 
   /**
-   * @type {ol.Coordinate}
    * @private
+   * @type {number|undefined}
    */
-  this.projectionCenterLonLat_ = null;
+  this.lastMagnitude_ = undefined;
 
   /**
-   * @type {Array.<ol.GraticuleLabelDataType>}
    * @private
+   * @type {number}
    */
-  this.meridiansLabels_ = null;
+  this.lastScaleDelta_ = 0;
 
   /**
-   * @type {Array.<ol.GraticuleLabelDataType>}
    * @private
+   * @type {number}
    */
-  this.parallelsLabels_ = null;
-
-  if (options.showLabels == true) {
-    var degreesToString = ol.coordinate.degreesToStringHDMS;
-
-    /**
-     * @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;
-
-    /**
-     * 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;
-
-    /**
-     * @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
-          })
-        });
-
-    this.meridiansLabels_ = [];
-    this.parallelsLabels_ = [];
-  }
+  this.duration_ = options.duration !== undefined ? options.duration : 400;
 
-  this.setMap(options.map !== undefined ? options.map : null);
 };
+ol.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer);
 
 
 /**
- * @type {ol.style.Stroke}
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @this {ol.interaction.DragRotateAndZoom}
  * @private
- * @const
  */
-ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
-  color: 'rgba(0,0,0,0.2)'
-});
-
+ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return;
+  }
 
-/**
- * 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];
+  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;
+};
 
 
 /**
- * @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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.DragRotateAndZoom}
  * @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)) {
-    if (this.meridiansLabels_) {
-      var textPoint = this.getMeridianPoint_(lineString, extent, index);
-      this.meridiansLabels_[index] = {
-        geom: textPoint,
-        text: this.lonLabelFormatter_(lon)
-      };
-    }
-    this.meridians_[index++] = lineString;
+ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return true;
   }
-  return index;
-};
 
-/**
- * @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;
+  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;
 };
 
 
 /**
- * @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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.DragRotateAndZoom}
  * @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)) {
-    if (this.parallelsLabels_) {
-      var textPoint = this.getParallelPoint_(lineString, extent, index);
-      this.parallelsLabels_[index] = {
-        geom: textPoint,
-        text: this.latLabelFormatter_(lat)
-      };
-    }
-    this.parallels_[index++] = lineString;
+ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) {
+  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
+    return false;
   }
-  return index;
-};
-
 
-/**
- * @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;
+  if (this.condition_(mapBrowserEvent)) {
+    mapBrowserEvent.map.getView().setHint(ol.ViewHint.INTERACTING, 1);
+    this.lastAngle_ = undefined;
+    this.lastMagnitude_ = undefined;
+    return true;
+  } else {
+    return false;
+  }
 };
 
+goog.provide('ol.interaction.DrawEventType');
+
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} squaredTolerance Squared tolerance.
- * @private
+ * @enum {string}
  */
-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;
-    }
-    if (this.parallelsLabels_) {
-      this.parallelsLabels_.length = 0;
-    }
-    return;
-  }
+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'
+};
 
-  var centerLonLat = this.toLonLatTransform_(center);
-  var centerLon = centerLonLat[0];
-  var centerLat = centerLonLat[1];
-  var maxLines = this.maxLines_;
-  var cnt, idx, lat, lon;
+goog.provide('ol.layer.Vector');
 
-  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_)
-  ];
+goog.require('ol');
+goog.require('ol.LayerType');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.VectorRenderType');
+goog.require('ol.obj');
+goog.require('ol.style.Style');
 
-  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
+/**
+ * @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.layer.Vector = function(opt_options) {
+  var options = opt_options ?
+    opt_options : /** @type {olx.layer.VectorOptions} */ ({});
 
-  centerLon = Math.floor(centerLon / interval) * interval;
-  lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);
+  var baseOptions = ol.obj.assign({}, options);
 
-  idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);
+  delete baseOptions.style;
+  delete baseOptions.renderBuffer;
+  delete baseOptions.updateWhileAnimating;
+  delete baseOptions.updateWhileInteracting;
+  ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
 
-  cnt = 0;
-  while (lon != this.minLon_ && cnt++ < maxLines) {
-    lon = Math.max(lon - interval, this.minLon_);
-    idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
-  }
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.declutter_ = options.declutter !== undefined ? options.declutter : false;
 
-  lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);
+  /**
+   * @type {number}
+   * @private
+   */
+  this.renderBuffer_ = options.renderBuffer !== undefined ?
+    options.renderBuffer : 100;
 
-  cnt = 0;
-  while (lon != this.maxLon_ && cnt++ < maxLines) {
-    lon = Math.min(lon + interval, this.maxLon_);
-    idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
-  }
+  /**
+   * User provided style.
+   * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
+   * @private
+   */
+  this.style_ = null;
 
-  this.meridians_.length = idx;
-  if (this.meridiansLabels_) {
-    this.meridiansLabels_.length = idx;
-  }
+  /**
+   * Style function for use within the library.
+   * @type {ol.StyleFunction|undefined}
+   * @private
+   */
+  this.styleFunction_ = undefined;
 
-  // Create parallels
+  this.setStyle(options.style);
 
-  centerLat = Math.floor(centerLat / interval) * interval;
-  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.updateWhileAnimating_ = options.updateWhileAnimating !== undefined ?
+    options.updateWhileAnimating : false;
 
-  idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0);
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ?
+    options.updateWhileInteracting : false;
 
-  cnt = 0;
-  while (lat != this.minLat_ && cnt++ < maxLines) {
-    lat = Math.max(lat - interval, this.minLat_);
-    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
-  }
+  /**
+   * @private
+   * @type {ol.layer.VectorTileRenderType|string}
+   */
+  this.renderMode_ = options.renderMode || ol.layer.VectorRenderType.VECTOR;
 
-  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);
+  /**
+   * The layer type.
+   * @protected
+   * @type {ol.LayerType}
+   */
+  this.type = ol.LayerType.VECTOR;
 
-  cnt = 0;
-  while (lat != this.maxLat_ && cnt++ < maxLines) {
-    lat = Math.min(lat + interval, this.maxLat_);
-    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
-  }
+};
+ol.inherits(ol.layer.Vector, ol.layer.Layer);
 
-  this.parallels_.length = idx;
-  if (this.parallelsLabels_) {
-    this.parallelsLabels_.length = idx;
-  }
 
+/**
+ * @return {boolean} Declutter.
+ */
+ol.layer.Vector.prototype.getDeclutter = function() {
+  return this.declutter_;
 };
 
 
 /**
- * @param {number} resolution Resolution.
- * @return {number} The interval in degrees.
- * @private
+ * @param {boolean} declutter Declutter.
  */
-ol.Graticule.prototype.getInterval_ = function(resolution) {
-  var centerLon = this.projectionCenterLonLat_[0];
-  var centerLat = this.projectionCenterLonLat_[1];
-  var interval = -1;
-  var i, ii, delta, dist;
-  var target = Math.pow(this.targetSize_ * resolution, 2);
-  /** @type {Array.<number>} **/
-  var p1 = [];
-  /** @type {Array.<number>} **/
-  var p2 = [];
-  for (i = 0, ii = ol.Graticule.intervals_.length; i < ii; ++i) {
-    delta = ol.Graticule.intervals_[i] / 2;
-    p1[0] = centerLon - delta;
-    p1[1] = centerLat - delta;
-    p2[0] = centerLon + delta;
-    p2[1] = centerLat + delta;
-    this.fromLonLatTransform_(p1, p1);
-    this.fromLonLatTransform_(p2, p2);
-    dist = Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2);
-    if (dist <= target) {
-      break;
-    }
-    interval = ol.Graticule.intervals_[i];
-  }
-  return interval;
+ol.layer.Vector.prototype.setDeclutter = function(declutter) {
+  this.declutter_ = declutter;
 };
 
 
 /**
- * Get the map associated with this graticule.
- * @return {ol.Map} The map.
- * @api
+ * @return {number|undefined} Render buffer.
  */
-ol.Graticule.prototype.getMap = function() {
-  return this.map_;
+ol.layer.Vector.prototype.getRenderBuffer = function() {
+  return this.renderBuffer_;
 };
 
 
 /**
- * @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
+ * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
+ *     order.
  */
-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;
+ol.layer.Vector.prototype.getRenderOrder = function() {
+  return /** @type {ol.RenderOrderFunction|null|undefined} */ (
+    this.get(ol.layer.Vector.Property_.RENDER_ORDER));
 };
 
 
 /**
- * Get the list of meridians.  Meridians are lines of equal longitude.
- * @return {Array.<ol.geom.LineString>} The meridians.
+ * Return the associated {@link ol.source.Vector vectorsource} of the layer.
+ * @function
+ * @return {ol.source.Vector} Source.
  * @api
  */
-ol.Graticule.prototype.getMeridians = function() {
-  return this.meridians_;
-};
+ol.layer.Vector.prototype.getSource;
 
 
 /**
- * @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
+ * 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.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;
+ol.layer.Vector.prototype.getStyle = function() {
+  return this.style_;
 };
 
 
 /**
- * Get the list of parallels.  Pallels are lines of equal latitude.
- * @return {Array.<ol.geom.LineString>} The parallels.
+ * Get the style function.
+ * @return {ol.StyleFunction|undefined} Layer style function.
  * @api
  */
-ol.Graticule.prototype.getParallels = function() {
-  return this.parallels_;
+ol.layer.Vector.prototype.getStyleFunction = function() {
+  return this.styleFunction_;
 };
 
 
 /**
- * @param {ol.render.Event} e Event.
- * @private
+ * @return {boolean} Whether the rendered layer should be updated while
+ *     animating.
  */
-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);
+ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
+  return this.updateWhileAnimating_;
+};
 
-  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]
-      ];
-    }
-  }
+/**
+ * @return {boolean} Whether the rendered layer should be updated while
+ *     interacting.
+ */
+ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
+  return this.updateWhileInteracting_;
+};
 
-  this.createGraticule_(extent, center, resolution, squaredTolerance);
 
-  // Draw the lines
-  vectorContext.setFillStrokeStyle(null, this.strokeStyle_);
-  var i, l, line;
-  for (i = 0, l = this.meridians_.length; i < l; ++i) {
-    line = this.meridians_[i];
-    vectorContext.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);
-    }
-  }
+/**
+ * @param {ol.RenderOrderFunction|null|undefined} renderOrder
+ *     Render order.
+ */
+ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
+  this.set(ol.layer.Vector.Property_.RENDER_ORDER, renderOrder);
 };
 
 
 /**
- * @param {ol.proj.Projection} projection Projection.
- * @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|null|undefined}
+ *     style Layer style.
+ * @api
  */
-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);
+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();
+};
 
-  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];
+/**
+ * @return {ol.layer.VectorRenderType|string} The render mode.
+ */
+ol.layer.Vector.prototype.getRenderMode = function() {
+  return this.renderMode_;
+};
 
-  this.maxLat_ = maxLat;
-  this.maxLon_ = maxLon;
-  this.minLat_ = minLat;
-  this.minLon_ = minLon;
 
-  this.maxLatP_ = maxLatP;
-  this.maxLonP_ = maxLonP;
-  this.minLatP_ = minLatP;
-  this.minLonP_ = minLonP;
+/**
+ * @enum {string}
+ * @private
+ */
+ol.layer.Vector.Property_ = {
+  RENDER_ORDER: 'renderOrder'
+};
 
+goog.provide('ol.loadingstrategy');
 
-  this.fromLonLatTransform_ = ol.proj.getTransform(
-     epsg4326Projection, projection);
 
-  this.toLonLatTransform_ = ol.proj.getTransform(
-     projection, epsg4326Projection);
+/**
+ * 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]];
+};
 
-  this.projectionCenterLonLat_ = this.toLonLatTransform_(
-     ol.extent.getCenter(extent));
 
-  this.projection_ = projection;
+/**
+ * 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];
 };
 
 
 /**
- * Set the map for this graticule.  The graticule will be rendered on the
- * provided map.
- * @param {ol.Map} map Map.
+ * 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.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.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.ImageBase');
+goog.provide('ol.source.Source');
 
 goog.require('ol');
-goog.require('ol.events.EventTarget');
-goog.require('ol.events.EventType');
+goog.require('ol.Attribution');
+goog.require('ol.Object');
+goog.require('ol.proj');
+goog.require('ol.source.State');
 
 
 /**
+ * @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.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.
+ * @extends {ol.Object}
+ * @param {ol.SourceSourceOptions} options Source options.
+ * @api
  */
-ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) {
+ol.source.Source = function(options) {
 
-  ol.events.EventTarget.call(this);
+  ol.Object.call(this);
+
+  /**
+   * @private
+   * @type {ol.proj.Projection}
+   */
+  this.projection_ = ol.proj.get(options.projection);
 
   /**
    * @private
    * @type {Array.<ol.Attribution>}
    */
-  this.attributions_ = attributions;
+  this.attributions_ = null;
 
   /**
-   * @protected
-   * @type {ol.Extent}
+   * @private
+   * @type {?ol.Attribution2}
    */
-  this.extent = extent;
+  this.attributions2_ = this.adaptAttributions_(options.attributions);
 
   /**
    * @private
-   * @type {number}
+   * @type {string|olx.LogoOptions|undefined}
    */
-  this.pixelRatio_ = pixelRatio;
+  this.logo_ = options.logo;
 
   /**
-   * @protected
-   * @type {number|undefined}
+   * @private
+   * @type {ol.source.State}
    */
-  this.resolution = resolution;
+  this.state_ = options.state !== undefined ?
+    options.state : ol.source.State.READY;
 
   /**
-   * @protected
-   * @type {ol.ImageState}
+   * @private
+   * @type {boolean}
    */
-  this.state = state;
+  this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false;
 
 };
-ol.inherits(ol.ImageBase, ol.events.EventTarget);
-
+ol.inherits(ol.source.Source, ol.Object);
 
 /**
- * @protected
+ * Turns the attributions option into an attributions function.
+ * @suppress {deprecated}
+ * @param {ol.AttributionLike|undefined} attributionLike The attribution option.
+ * @return {?ol.Attribution2} An attribution function (or null).
  */
-ol.ImageBase.prototype.changed = function() {
-  this.dispatchEvent(ol.events.EventType.CHANGE);
-};
+ol.source.Source.prototype.adaptAttributions_ = function(attributionLike) {
+  if (!attributionLike) {
+    return null;
+  }
+  if (attributionLike instanceof ol.Attribution) {
 
+    // TODO: remove attributions_ in next major release
+    this.attributions_ = [attributionLike];
 
-/**
- * @return {Array.<ol.Attribution>} Attributions.
- */
-ol.ImageBase.prototype.getAttributions = function() {
-  return this.attributions_;
-};
+    return function(frameState) {
+      return [attributionLike.getHTML()];
+    };
+  }
+  if (Array.isArray(attributionLike)) {
+    if (attributionLike[0] instanceof ol.Attribution) {
 
+      // TODO: remove attributions_ in next major release
+      this.attributions_ = attributionLike;
+
+      var attributions = attributionLike.map(function(attribution) {
+        return attribution.getHTML();
+      });
+      return function(frameState) {
+        return attributions;
+      };
+    }
+
+    // TODO: remove attributions_ in next major release
+    this.attributions_ = attributionLike.map(function(attribution) {
+      return new ol.Attribution({html: attribution});
+    });
+
+    return function(frameState) {
+      return attributionLike;
+    };
+  }
+
+  if (typeof attributionLike === 'function') {
+    return attributionLike;
+  }
+
+  // TODO: remove attributions_ in next major release
+  this.attributions_ = [
+    new ol.Attribution({html: attributionLike})
+  ];
+
+  return function(frameState) {
+    return [attributionLike];
+  };
+};
 
 /**
- * @return {ol.Extent} Extent.
+ * @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.ImageBase.prototype.getExtent = function() {
-  return this.extent;
-};
+ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
 
 
 /**
- * @abstract
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
+ * Get the attributions of the source.
+ * @return {Array.<ol.Attribution>} Attributions.
+ * @api
  */
-ol.ImageBase.prototype.getImage = function(opt_context) {};
+ol.source.Source.prototype.getAttributions = function() {
+  return this.attributions_;
+};
 
 
 /**
- * @return {number} PixelRatio.
+ * Get the attribution function for the source.
+ * @return {?ol.Attribution2} Attribution function.
  */
-ol.ImageBase.prototype.getPixelRatio = function() {
-  return this.pixelRatio_;
+ol.source.Source.prototype.getAttributions2 = function() {
+  return this.attributions2_;
 };
 
 
 /**
- * @return {number} Resolution.
+ * Get the logo of the source.
+ * @return {string|olx.LogoOptions|undefined} Logo.
+ * @api
  */
-ol.ImageBase.prototype.getResolution = function() {
-  return /** @type {number} */ (this.resolution);
+ol.source.Source.prototype.getLogo = function() {
+  return this.logo_;
 };
 
 
 /**
- * @return {ol.ImageState} State.
+ * Get the projection of the source.
+ * @return {ol.proj.Projection} Projection.
+ * @api
  */
-ol.ImageBase.prototype.getState = function() {
-  return this.state;
+ol.source.Source.prototype.getProjection = function() {
+  return this.projection_;
 };
 
 
 /**
- * Load not yet loaded URI.
  * @abstract
+ * @return {Array.<number>|undefined} Resolutions.
  */
-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');
+ol.source.Source.prototype.getResolutions = function() {};
 
 
 /**
- * @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.
+ * Get the state of the source, see {@link ol.source.State} for possible states.
+ * @return {ol.source.State} State.
+ * @api
  */
-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;
-  }
-
-  /**
-   * @private
-   * @type {Object.<number, (HTMLCanvasElement|Image|HTMLVideoElement)>}
-   */
-  this.imageByContext_ = {};
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.imageListenerKeys_ = null;
-
-  /**
-   * @protected
-   * @type {ol.ImageState}
-   */
-  this.state = ol.ImageState.IDLE;
-
-  /**
-   * @private
-   * @type {ol.ImageLoadFunctionType}
-   */
-  this.imageLoadFunction_ = imageLoadFunction;
-
+ol.source.Source.prototype.getState = function() {
+  return this.state_;
 };
-ol.inherits(ol.Image, ol.ImageBase);
 
 
 /**
- * @inheritDoc
- * @api
+ * @return {boolean|undefined} Wrap X.
  */
-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_;
-  }
+ol.source.Source.prototype.getWrapX = function() {
+  return this.wrapX_;
 };
 
 
 /**
- * Tracks loading or read errors.
- *
- * @private
- */
-ol.Image.prototype.handleImageError_ = function() {
-  this.state = ol.ImageState.ERROR;
-  this.unlistenImage_();
+ * Refreshes the source and finally dispatches a 'change' event.
+ * @api
+ */
+ol.source.Source.prototype.refresh = function() {
   this.changed();
 };
 
 
 /**
- * Tracks successful image load.
- *
- * @private
+ * Set the attributions of the source.
+ * @param {ol.AttributionLike|undefined} attributions Attributions.
+ *     Can be passed as `string`, `Array<string>`, `{@link ol.Attribution2}`,
+ *     or `undefined`.
+ * @api
  */
-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_();
+ol.source.Source.prototype.setAttributions = function(attributions) {
+  this.attributions2_ = this.adaptAttributions_(attributions);
   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.
- * @override
- * @api
+ * Set the logo of the source.
+ * @param {string|olx.LogoOptions|undefined} logo Logo.
  */
-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_);
-  }
+ol.source.Source.prototype.setLogo = function(logo) {
+  this.logo_ = logo;
 };
 
 
 /**
- * @param {HTMLCanvasElement|Image|HTMLVideoElement} image Image.
+ * Set the state of the source.
+ * @param {ol.source.State} state State.
+ * @protected
  */
-ol.Image.prototype.setImage = function(image) {
-  this.image_ = image;
+ol.source.Source.prototype.setState = function(state) {
+  this.state_ = state;
+  this.changed();
 };
 
+goog.provide('ol.source.VectorEventType');
 
 /**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
+ * @enum {string}
  */
-ol.Image.prototype.unlistenImage_ = function() {
-  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
+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',
+
+  /**
+   * 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'
 };
 
-goog.provide('ol.ImageCanvas');
+goog.provide('ol.structs.RBush');
 
 goog.require('ol');
-goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
+goog.require('ol.ext.rbush');
+goog.require('ol.extent');
+goog.require('ol.obj');
 
 
 /**
+ * Wrapper around the RBush by Vladimir Agafonkin.
+ *
  * @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.
+ * @param {number=} opt_maxEntries Max entries.
+ * @see https://github.com/mourner/rbush
+ * @struct
+ * @template T
  */
-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);
+ol.structs.RBush = function(opt_maxEntries) {
 
   /**
    * @private
-   * @type {HTMLCanvasElement}
    */
-  this.canvas_ = canvas;
+  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 {Error}
+   * @type {Object.<number, ol.RBushEntry>}
    */
-  this.error_ = null;
+  this.items_ = {};
 
 };
-ol.inherits(ol.ImageCanvas, ol.ImageBase);
 
 
 /**
- * Get any error associated with asynchronous rendering.
- * @return {Error} Any error that occurred during rendering.
+ * Insert a value into the RBush.
+ * @param {ol.Extent} extent Extent.
+ * @param {T} value Value.
  */
-ol.ImageCanvas.prototype.getError = function() {
-  return this.error_;
+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;
 };
 
 
 /**
- * Handle async drawing complete.
- * @param {Error} err Any error during drawing.
- * @private
+ * Bulk-insert values into the RBush.
+ * @param {Array.<ol.Extent>} extents Extents.
+ * @param {Array.<T>} values Values.
  */
-ol.ImageCanvas.prototype.handleLoad_ = function(err) {
-  if (err) {
-    this.error_ = err;
-    this.state = ol.ImageState.ERROR;
-  } else {
-    this.state = ol.ImageState.LOADED;
+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.changed();
+  this.rbush_.load(items);
 };
 
 
 /**
- * @inheritDoc
+ * Remove a value from the RBush.
+ * @param {T} value Value.
+ * @return {boolean} Removed.
  */
-ol.ImageCanvas.prototype.load = function() {
-  if (this.state == ol.ImageState.IDLE) {
-    this.state = ol.ImageState.LOADING;
-    this.changed();
-    this.loader_(this.handleLoad_.bind(this));
-  }
+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;
 };
 
 
 /**
- * @inheritDoc
+ * Update the extent of a value in the RBush.
+ * @param {ol.Extent} extent Extent.
+ * @param {T} value Value.
  */
-ol.ImageCanvas.prototype.getImage = function(opt_context) {
-  return this.canvas_;
+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);
+  }
 };
 
-goog.provide('ol.Tile');
-
-goog.require('ol');
-goog.require('ol.TileState');
-goog.require('ol.events.EventTarget');
-goog.require('ol.events.EventType');
-
 
 /**
- * @classdesc
- * Base class for tiles.
- *
- * @constructor
- * @abstract
- * @extends {ol.events.EventTarget}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.TileState} state State.
+ * Return all values in the RBush.
+ * @return {Array.<T>} All.
  */
-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.structs.RBush.prototype.getAll = function() {
+  var items = this.rbush_.all();
+  return items.map(function(item) {
+    return item.value;
+  });
 };
-ol.inherits(ol.Tile, ol.events.EventTarget);
 
 
 /**
- * @protected
+ * Return all values in the given extent.
+ * @param {ol.Extent} extent Extent.
+ * @return {Array.<T>} All in extent.
  */
-ol.Tile.prototype.changed = function() {
-  this.dispatchEvent(ol.events.EventType.CHANGE);
+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;
+  });
 };
 
 
 /**
- * @return {string} Key.
+ * 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.Tile.prototype.getKey = function() {
-  return this.key + '/' + this.tileCoord;
+ol.structs.RBush.prototype.forEach = function(callback, opt_this) {
+  return this.forEach_(this.getAll(), callback, opt_this);
 };
 
+
 /**
- * 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.
+ * 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.Tile.prototype.getInterimTile = function() {
-  if (!this.interimTile) {
-    //empty chain
-    return this;
-  }
-  var tile = this.interimTile;
-
-  // 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;
-    }
-    tile = tile.interimTile;
-  } while (tile);
-
-  // we can not find a better tile
-  return this;
+ol.structs.RBush.prototype.forEachInExtent = function(extent, callback, opt_this) {
+  return this.forEach_(this.getInExtent(extent), callback, opt_this);
 };
 
+
 /**
- * Goes through the chain of interim tiles and discards sections of the chain
- * that are no longer relevant.
+ * @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.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;
+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;
     }
-    tile = prev.interimTile;
-  } while (tile);
+  }
+  return result;
 };
 
+
 /**
- * Get the tile coordinate for this tile.
- * @return {ol.TileCoord} The tile coordinate.
- * @api
+ * @return {boolean} Is empty.
  */
-ol.Tile.prototype.getTileCoord = function() {
-  return this.tileCoord;
+ol.structs.RBush.prototype.isEmpty = function() {
+  return ol.obj.isEmpty(this.items_);
 };
 
 
 /**
- * @return {ol.TileState} State.
+ * Remove all values from the RBush.
  */
-ol.Tile.prototype.getState = function() {
-  return this.state;
+ol.structs.RBush.prototype.clear = function() {
+  this.rbush_.clear();
+  this.items_ = {};
 };
 
+
 /**
- * @param {ol.TileState} state State.
+ * @param {ol.Extent=} opt_extent Extent.
+ * @return {ol.Extent} Extent.
  */
-ol.Tile.prototype.setState = function(state) {
-  this.state = state;
-  this.changed();
+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);
 };
 
+
 /**
- * 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
+ * @param {ol.structs.RBush} rbush R-Tree.
  */
-ol.Tile.prototype.load = function() {};
+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];
+  }
+};
 
-goog.provide('ol.ImageTile');
+// FIXME bulk feature upload - suppress events
+// FIXME make change-detection more refined (notably, geometry hint)
+
+goog.provide('ol.source.Vector');
 
 goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.TileState');
+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');
 
 
 /**
+ * @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.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.
+ * @extends {ol.source.Source}
+ * @fires ol.source.Vector.Event
+ * @param {olx.source.VectorOptions=} opt_options Vector source options.
+ * @api
  */
-ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) {
+ol.source.Vector = function(opt_options) {
 
-  ol.Tile.call(this, tileCoord, state);
+  var options = opt_options || {};
+
+  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
+  });
 
   /**
-   * Image URI
-   *
    * @private
-   * @type {string}
+   * @type {ol.FeatureLoader}
+   */
+  this.loader_ = ol.nullFunction;
+
+  /**
+   * @private
+   * @type {ol.format.Feature|undefined}
+   */
+  this.format_ = options.format;
+
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;
+
+  /**
+   * @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_));
+  }
+
+  /**
+   * @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.src_ = src;
+  this.idIndex_ = {};
 
   /**
+   * A lookup of features without id (keyed by ol.getUid(feature)).
    * @private
-   * @type {Image}
+   * @type {Object.<string, ol.Feature>}
    */
-  this.image_ = new Image();
-  if (crossOrigin !== null) {
-    this.image_.crossOrigin = crossOrigin;
-  }
+  this.undefIdIndex_ = {};
 
   /**
    * @private
-   * @type {Array.<ol.EventsKey>}
+   * @type {Object.<string, Array.<ol.EventsKey>>}
    */
-  this.imageListenerKeys_ = null;
+  this.featureChangeKeys_ = {};
 
   /**
    * @private
-   * @type {ol.TileLoadFunctionType}
+   * @type {ol.Collection.<ol.Feature>}
    */
-  this.tileLoadFunction_ = tileLoadFunction;
-
-};
-ol.inherits(ol.ImageTile, ol.Tile);
-
+  this.featuresCollection_ = null;
 
-/**
- * @inheritDoc
- */
-ol.ImageTile.prototype.disposeInternal = function() {
-  if (this.state == ol.TileState.LOADING) {
-    this.unlistenImage_();
+  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 (this.interimTile) {
-    this.interimTile.dispose();
+  if (!useSpatialIndex && collection === undefined) {
+    collection = new ol.Collection(features);
   }
-  this.state = ol.TileState.ABORT;
-  this.changed();
-  ol.Tile.prototype.disposeInternal.call(this);
+  if (features !== undefined) {
+    this.addFeaturesInternal(features);
+  }
+  if (collection !== undefined) {
+    this.bindFeaturesCollection_(collection);
+  }
+
 };
+ol.inherits(ol.source.Vector, ol.source.Source);
 
 
 /**
- * Get the HTML image element for this tile (may be a Canvas, Image, or Video).
- * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
+ * 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.ImageTile.prototype.getImage = function() {
-  return this.image_;
+ol.source.Vector.prototype.addFeature = function(feature) {
+  this.addFeatureInternal(feature);
+  this.changed();
 };
 
 
 /**
- * @inheritDoc
+ * Add a feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
  */
-ol.ImageTile.prototype.getKey = function() {
-  return this.src_;
-};
-
+ol.source.Vector.prototype.addFeatureInternal = function(feature) {
+  var featureKey = ol.getUid(feature).toString();
 
-/**
- * Tracks loading or read errors.
- *
- * @private
- */
-ol.ImageTile.prototype.handleImageError_ = function() {
-  this.state = ol.TileState.ERROR;
-  this.image_ = ol.ImageTile.blankImage;
-  this.unlistenImage_();
-  this.changed();
-};
+  if (!this.addToIndex_(featureKey, feature)) {
+    return;
+  }
 
+  this.setupChangeEvents_(featureKey, feature);
 
-/**
- * Tracks successful image load.
- *
- * @private
- */
-ol.ImageTile.prototype.handleImageLoad_ = function() {
-  if (this.image_.naturalWidth && this.image_.naturalHeight) {
-    this.state = ol.TileState.LOADED;
+  var geometry = feature.getGeometry();
+  if (geometry) {
+    var extent = geometry.getExtent();
+    if (this.featuresRtree_) {
+      this.featuresRtree_.insert(extent, feature);
+    }
   } else {
-    this.state = ol.TileState.EMPTY;
+    this.nullGeometryFeatures_[featureKey] = feature;
   }
-  this.unlistenImage_();
-  this.changed();
+
+  this.dispatchEvent(
+      new ol.source.Vector.Event(ol.source.VectorEventType.ADDFEATURE, feature));
 };
 
 
 /**
- * @inheritDoc
- * @api
+ * @param {string} featureKey Unique identifier for the feature.
+ * @param {ol.Feature} feature The feature.
+ * @private
  */
-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_);
-  }
+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)
+  ];
 };
 
 
 /**
- * Discards event handlers which listen for load completion or errors.
- *
+ * @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.ImageTile.prototype.unlistenImage_ = function() {
-  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
+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;
 };
 
 
 /**
- * A blank image.
- * @type {Image}
+ * Add a batch of features to the source.
+ * @param {Array.<ol.Feature>} features Features to add.
+ * @api
  */
-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');
+ol.source.Vector.prototype.addFeatures = function(features) {
+  this.addFeaturesInternal(features);
+  this.changed();
+};
 
 
 /**
- * @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
+ * Add features without firing a `change` event.
+ * @param {Array.<ol.Feature>} features Features.
+ * @protected
  */
-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;
+ol.source.Vector.prototype.addFeaturesInternal = function(features) {
+  var featureKey, i, length, feature;
 
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.dropListenKeys_ = null;
+  var extents = [];
+  var newFeatures = [];
+  var geometryFeatures = [];
 
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.target = options.target ? options.target : null;
+  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);
+    }
+  }
 
-};
-ol.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
+  for (i = 0, length = newFeatures.length; i < length; i++) {
+    feature = newFeatures[i];
+    featureKey = ol.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);
+  }
 
-/**
- * @param {Event} event Event.
- * @this {ol.interaction.DragAndDrop}
- * @private
- */
-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);
+  for (i = 0, length = newFeatures.length; i < length; i++) {
+    this.dispatchEvent(new ol.source.Vector.Event(
+        ol.source.VectorEventType.ADDFEATURE, newFeatures[i]));
   }
 };
 
 
 /**
- * @param {Event} event Event.
+ * @param {!ol.Collection.<ol.Feature>} collection Collection.
  * @private
  */
-ol.interaction.DragAndDrop.handleStop_ = function(event) {
-  event.stopPropagation();
-  event.preventDefault();
-  event.dataTransfer.dropEffect = 'copy';
+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;
 };
 
 
 /**
- * @param {File} file File.
- * @param {Event} event Load event.
- * @private
+ * Remove all features from the source.
+ * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events.
+ * @api
  */
-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();
+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();
   }
 
-  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;
-    }
+  if (this.featuresRtree_) {
+    this.featuresRtree_.clear();
   }
-  this.dispatchEvent(
-      new ol.interaction.DragAndDrop.Event(
-          ol.interaction.DragAndDrop.EventType_.ADD_FEATURES, file,
-          features, projection));
+  this.loadedExtentsRtree_.clear();
+  this.nullGeometryFeatures_ = {};
+
+  var clearEvent = new ol.source.Vector.Event(ol.source.VectorEventType.CLEAR);
+  this.dispatchEvent(clearEvent);
+  this.changed();
 };
 
 
 /**
- * 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}
+ * 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.interaction.DragAndDrop.handleEvent = ol.functions.TRUE;
+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);
+  }
+};
 
 
 /**
- * @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.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)
-    ];
-  }
+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;
+    }
+  });
 };
 
 
 /**
- * @inheritDoc
+ * 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.interaction.DragAndDrop.prototype.setActive = function(active) {
-  ol.interaction.Interaction.prototype.setActive.call(this, active);
-  if (active) {
-    this.registerListeners_();
-  } else {
-    this.unregisterListeners_();
+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);
   }
 };
 
 
 /**
- * @inheritDoc
+ * 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.interaction.DragAndDrop.prototype.setMap = function(map) {
-  this.unregisterListeners_();
-  ol.interaction.Interaction.prototype.setMap.call(this, map);
-  if (this.getActive()) {
-    this.registerListeners_();
-  }
+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;
+          }
+        }
+      });
 };
 
 
 /**
- * @param {ol.format.Feature} format Format.
- * @param {string} text Text.
- * @param {olx.format.ReadOptions} options Read options.
- * @private
- * @return {Array.<ol.Feature>} Features.
- */
-ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text, options) {
-  try {
-    return format.readFeatures(text, options);
-  } catch (e) {
-    return null;
-  }
+ * 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.source.Vector.prototype.getFeaturesCollection = function() {
+  return this.featuresCollection_;
 };
 
 
 /**
- * @private
+ * Get all features on the source in random order.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
  */
-ol.interaction.DragAndDrop.prototype.unregisterListeners_ = function() {
-  if (this.dropListenKeys_) {
-    this.dropListenKeys_.forEach(ol.events.unlistenByKey);
-    this.dropListenKeys_ = null;
+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);
 };
 
 
 /**
- * @enum {string}
- * @private
+ * Get all features whose geometry intersects the provided coordinate.
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {Array.<ol.Feature>} Features.
+ * @api
  */
-ol.interaction.DragAndDrop.EventType_ = {
-  /**
-   * Triggered when features are added
-   * @event ol.interaction.DragAndDrop.Event#addfeatures
-   * @api
-   */
-  ADD_FEATURES: 'addfeatures'
+ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
+  var features = [];
+  this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) {
+    features.push(feature);
+  });
+  return features;
 };
 
 
 /**
- * @classdesc
- * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances
- * of this type.
+ * 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).
  *
- * @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.
+ * 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.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.source.Vector.prototype.getFeaturesInExtent = function(extent) {
+  return this.featuresRtree_.getInExtent(extent);
 };
-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');
 
 
 /**
- * @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.
+ * Get the closest feature to the provided coordinate.
  *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.DragRotateAndZoomOptions=} opt_options Options.
+ * 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.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;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.lastScaleDelta_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 400;
-
+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;
 };
-ol.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer);
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
+ * 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.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;
+ol.source.Vector.prototype.getExtent = function(opt_extent) {
+  return this.featuresRtree_.getExtent(opt_extent);
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
+ * 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.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;
+ol.source.Vector.prototype.getFeatureById = function(id) {
+  var feature = this.idIndex_[id.toString()];
+  return feature !== undefined ? feature : null;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
+ * Get the format associated with this source.
+ *
+ * @return {ol.format.Feature|undefined} The feature format.
+ * @api
  */
-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;
-  }
+ol.source.Vector.prototype.getFormat = function() {
+  return this.format_;
 };
 
-goog.provide('ol.interaction.DrawEventType');
-
 
 /**
- * @enum {string}
+ * @return {boolean} The source can have overlapping geometries.
  */
-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'
+ol.source.Vector.prototype.getOverlaps = function() {
+  return this.overlaps_;
 };
 
-goog.provide('ol.render.canvas.Instruction');
 
 /**
- * @enum {number}
+ * @override
  */
-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');
-
-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');
+ol.source.Vector.prototype.getResolutions = function() {};
 
 
 /**
- * @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
+ * Get the url associated with this source.
+ *
+ * @return {string|ol.FeatureUrlFunction|undefined} The url.
+ * @api
  */
-ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) {
-  ol.render.VectorContext.call(this);
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.tolerance = tolerance;
-
-  /**
-   * @protected
-   * @const
-   * @type {ol.Extent}
-   */
-  this.maxExtent = maxExtent;
-
-  /**
-   * @protected
-   * @type {boolean}
-   */
-  this.overlaps = overlaps;
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.maxLineWidth = 0;
-
-  /**
-   * @protected
-   * @const
-   * @type {number}
-   */
-  this.resolution = resolution;
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.fillOrigin_;
-
-  /**
-   * @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 {!ol.Transform}
-   */
-  this.renderedTransform_ = ol.transform.create();
-
-  /**
-   * @protected
-   * @type {Array.<*>}
-   */
-  this.hitDetectionInstructions = [];
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.pixelCoordinates_ = null;
-
-  /**
-   * @private
-   * @type {!ol.Transform}
-   */
-  this.tmpLocalTransform_ = ol.transform.create();
-
-  /**
-   * @private
-   * @type {!ol.Transform}
-   */
-  this.resetTransform_ = ol.transform.create();
+ol.source.Vector.prototype.getUrl = function() {
+  return this.url_;
 };
-ol.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} closed Last input coordinate equals first.
- * @param {boolean} skipFirst Skip first coordinate.
- * @protected
- * @return {number} My end.
+ * @param {ol.events.Event} event Event.
+ * @private
  */
-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;
-
-  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];
+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);
       }
-      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;
+      if (this.featuresRtree_) {
+        this.featuresRtree_.update(extent, feature);
+      }
+    }
+  }
+  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;
     }
-    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;
+  this.changed();
+  this.dispatchEvent(new ol.source.Vector.Event(
+      ol.source.VectorEventType.CHANGEFEATURE, feature));
 };
 
 
 /**
- * @protected
- * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @return {boolean} Is empty.
  */
-ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) {
-  this.beginGeometryInstruction1_ =
-      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
-  this.instructions.push(this.beginGeometryInstruction1_);
-  this.beginGeometryInstruction2_ =
-      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
-  this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
+ol.source.Vector.prototype.isEmpty = function() {
+  return this.featuresRtree_.isEmpty() &&
+      ol.obj.isEmpty(this.nullGeometryFeatures_);
 };
 
 
 /**
- * @private
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} rotation Rotation.
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {ol.proj.Projection} projection Projection.
  */
-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_);
+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 {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
+ * Remove an extent from the list of loaded extents.
+ * @param {ol.Extent} extent Extent.
+ * @api
  */
-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;
-
-          context.drawImage(image, originX, originY, w, h,
-              x, y, w * pixelRatio, h * pixelRatio);
-
-          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);
-          }
-
-          // Support multiple lines separated by \n
-          var lines = text.split('\n');
-          var numLines = lines.length;
-          var fontSize, lineY;
-          if (numLines > 1) {
-            // Estimate line height using width of capital M, and add padding
-            fontSize = Math.round(context.measureText('M').width * 1.5);
-            lineY = y - (((numLines - 1) / 2) * fontSize);
-          } else {
-            // No need to calculate line height/offset for a single line
-            fontSize = 0;
-            lineY = y;
-          }
-
-          for (var lineIndex = 0; lineIndex < numLines; lineIndex++) {
-            var line = lines[lineIndex];
-            if (stroke) {
-              context.strokeText(line, x, lineY);
-            }
-            if (fill) {
-              context.fillText(line, x, lineY);
-            }
-
-            // Move next line down by fontSize px
-            lineY = lineY + fontSize;
-          }
-
-          if (scale != 1 || rotation !== 0) {
-            context.setTransform.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;
-          }
-        }
-
-        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];
-
-        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;
+ol.source.Vector.prototype.removeLoadedExtent = function(extent) {
+  var loadedExtentsRtree = this.loadedExtentsRtree_;
+  var obj;
+  loadedExtentsRtree.forEachInExtent(extent, function(object) {
+    if (ol.extent.equals(object.extent, extent)) {
+      obj = object;
+      return true;
     }
+  });
+  if (obj) {
+    loadedExtentsRtree.remove(obj);
   }
-  if (pendingFill) {
-    this.fill_(context, viewRotation);
-  }
-  if (pendingStroke) {
-    context.stroke();
-  }
-  return undefined;
 };
 
 
 /**
- * @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.
+ * 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.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);
+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);
+    }
+  }
+  this.removeFeatureInternal(feature);
+  this.changed();
 };
 
 
 /**
- * @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
+ * Remove feature without firing a `change` event.
+ * @param {ol.Feature} feature Feature.
+ * @protected
  */
-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);
+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));
 };
 
 
 /**
- * Reverse the hit detection instructions.
+ * 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.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;
+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;
 };
 
 
 /**
- * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * Set the new loader of the source. The next loadFeatures call will use the
+ * new loader.
+ * @param {ol.FeatureLoader} loader The loader to set.
+ * @api
  */
-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);
+ol.source.Vector.prototype.setLoader = function(loader) {
+  this.loader_ = loader;
 };
 
 
 /**
- * FIXME empty description for jsdoc
+ * @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.render.canvas.Replay.prototype.finish = ol.nullFunction;
+ol.source.Vector.Event = function(type, opt_feature) {
 
+  ol.events.Event.call(this, type);
+
+  /**
+   * The feature being added or removed.
+   * @type {ol.Feature|undefined}
+   * @api
+   */
+  this.feature = opt_feature;
 
-/**
- * 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;
 };
+ol.inherits(ol.source.Vector.Event, ol.events.Event);
 
-goog.provide('ol.render.canvas.ImageReplay');
+goog.provide('ol.interaction.Draw');
 
 goog.require('ol');
-goog.require('ol.render.canvas.Instruction');
-goog.require('ol.render.canvas.Replay');
+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');
 
 
 /**
+ * @classdesc
+ * Interaction for drawing feature geometries.
+ *
  * @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
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.Draw.Event
+ * @param {olx.interaction.DrawOptions} options Options.
+ * @api
  */
-ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) {
-  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
+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_
+  });
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.shouldHandle_ = false;
+
+  /**
+   * @type {ol.Pixel}
+   * @private
+   */
+  this.downPx_ = null;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.freehand_ = false;
+
+  /**
+   * Target source for drawn features.
+   * @type {ol.source.Vector}
+   * @private
+   */
+  this.source_ = options.source ? options.source : null;
+
+  /**
+   * Target collection for drawn features.
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
+   */
+  this.features_ = options.features ? options.features : null;
+
+  /**
+   * Pixel distance for snapping.
+   * @type {number}
+   * @private
+   */
+  this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12;
+
+  /**
+   * Geometry type.
+   * @type {ol.geom.GeometryType}
+   * @private
+   */
+  this.type_ = /** @type {ol.geom.GeometryType} */ (options.type);
+
+  /**
+   * Drawing mode (derived from geometry type.
+   * @type {ol.interaction.Draw.Mode_}
+   * @private
+   */
+  this.mode_ = ol.interaction.Draw.getMode_(this.type_);
+
+  /**
+   * Stop click, singleclick, and doubleclick events from firing during drawing.
+   * Default is `false`.
+   * @type {boolean}
+   * @private
+   */
+  this.stopClick_ = !!options.stopClick;
 
   /**
+   * 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
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
    */
-  this.hitDetectionImage_ = null;
+  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
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
    */
-  this.image_ = null;
+  this.maxPoints_ = options.maxPoints ? options.maxPoints : Infinity;
 
   /**
+   * A function to decide if a potential finish coordinate is permissible
    * @private
-   * @type {number|undefined}
+   * @type {ol.EventsConditionType}
    */
-  this.anchorX_ = undefined;
+  this.finishCondition_ = options.finishCondition ? options.finishCondition : ol.functions.TRUE;
+
+  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) {
+            if (coordinates[0].length) {
+              // Add a closing coordinate to match the first
+              geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]);
+            } else {
+              geometry.setCoordinates([]);
+            }
+          } else {
+            geometry.setCoordinates(coordinates);
+          }
+        } else {
+          geometry = new Constructor(coordinates);
+        }
+        return geometry;
+      };
+    }
+  }
 
   /**
+   * @type {ol.DrawGeometryFunctionType}
    * @private
-   * @type {number|undefined}
    */
-  this.anchorY_ = undefined;
+  this.geometryFunction_ = geometryFunction;
 
   /**
+   * Finish coordinate for the feature (first point for polygons, last point for
+   * linestrings).
+   * @type {ol.Coordinate}
    * @private
-   * @type {number|undefined}
    */
-  this.height_ = undefined;
+  this.finishCoordinate_ = null;
 
   /**
+   * Sketch feature.
+   * @type {ol.Feature}
    * @private
-   * @type {number|undefined}
    */
-  this.opacity_ = undefined;
+  this.sketchFeature_ = null;
 
   /**
+   * Sketch point.
+   * @type {ol.Feature}
    * @private
-   * @type {number|undefined}
    */
-  this.originX_ = undefined;
+  this.sketchPoint_ = null;
 
   /**
+   * Sketch coordinates. Used when drawing a line or polygon.
+   * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>}
    * @private
-   * @type {number|undefined}
    */
-  this.originY_ = undefined;
+  this.sketchCoords_ = null;
 
   /**
+   * Sketch line. Used when drawing polygon.
+   * @type {ol.Feature}
    * @private
-   * @type {boolean|undefined}
    */
-  this.rotateWithView_ = undefined;
+  this.sketchLine_ = null;
 
   /**
+   * Sketch line coordinates. Used when drawing a polygon or circle.
+   * @type {Array.<ol.Coordinate>}
    * @private
-   * @type {number|undefined}
    */
-  this.rotation_ = undefined;
+  this.sketchLineCoords_ = null;
 
   /**
+   * Squared tolerance for handling up events.  If the squared distance
+   * between a down and up event is greater than this tolerance, up events
+   * will not be handled.
+   * @type {number}
    * @private
-   * @type {number|undefined}
    */
-  this.scale_ = undefined;
+  this.squaredClickTolerance_ = options.clickTolerance ?
+    options.clickTolerance * options.clickTolerance : 36;
 
   /**
+   * Draw overlay where our sketch features are drawn.
+   * @type {ol.layer.Vector}
    * @private
-   * @type {boolean|undefined}
    */
-  this.snapToPixel_ = undefined;
+  this.overlay_ = new ol.layer.Vector({
+    source: new ol.source.Vector({
+      useSpatialIndex: false,
+      wrapX: options.wrapX ? options.wrapX : false
+    }),
+    style: options.style ? options.style :
+      ol.interaction.Draw.getDefaultStyleFunction()
+  });
 
   /**
+   * Name of the geometry attribute for newly created features.
+   * @type {string|undefined}
    * @private
-   * @type {number|undefined}
    */
-  this.width_ = undefined;
-
-};
-ol.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, false);
-};
-
-
-/**
- * @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);
-};
-
-
-/**
- * @inheritDoc
- */
-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);
-};
-
-
-/**
- * @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) {
-  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.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.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.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) {
-
-  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
+  this.geometryName_ = options.geometryName;
 
   /**
    * @private
-   * @type {ol.Extent}
+   * @type {ol.EventsConditionType}
    */
-  this.bufferedMaxExtent_ = null;
+  this.condition_ = options.condition ?
+    options.condition : ol.events.condition.noModifierKeys;
 
   /**
    * @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}
+   * @type {ol.EventsConditionType}
    */
-  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
-  };
-
-};
-ol.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, 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 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;
+  this.freehandCondition_;
+  if (options.freehand) {
+    this.freehandCondition_ = ol.events.condition.always;
+  } else {
+    this.freehandCondition_ = options.freehandCondition ?
+      options.freehandCondition : ol.events.condition.shiftKeyOnly;
   }
-};
 
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.interaction.Property.ACTIVE),
+      this.updateState_, this);
 
-/**
- * @inheritDoc
- */
-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);
 };
+ol.inherits(ol.interaction.Draw, ol.interaction.Pointer);
 
 
 /**
- * @inheritDoc
+ * @return {ol.StyleFunction} Styles.
  */
-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);
+ol.interaction.Draw.getDefaultStyleFunction = function() {
+  var styles = ol.style.Style.createDefaultEditing();
+  return function(feature, resolution) {
+    return styles[feature.getGeometry().getType()];
+  };
 };
 
 
 /**
  * @inheritDoc
  */
-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]);
-  }
-  this.reverseHitDetectionInstructions();
-  this.state_ = null;
+ol.interaction.Draw.prototype.setMap = function(map) {
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
+  this.updateState_();
 };
 
 
 /**
- * @inheritDoc
+ * 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.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;
+ol.interaction.Draw.handleEvent = function(event) {
+  this.freehand_ = this.mode_ !== ol.interaction.Draw.Mode_.POINT && this.freehandCondition_(event);
+  var pass = true;
+  if (this.freehand_ &&
+      event.type === ol.MapBrowserEventType.POINTERDRAG &&
+      this.sketchFeature_ !== null) {
+    this.addToDrawing_(event);
+    pass = false;
+  } else if (this.freehand_ &&
+      event.type === ol.MapBrowserEventType.POINTERDOWN) {
+    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;
 };
 
-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');
-
 
 /**
- * @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
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Draw}
+ * @private
  */
-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.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;
+  }
 };
-ol.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.
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Draw}
  * @private
- * @return {number} End.
  */
-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);
+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);
     }
-    offset = end;
-  }
-  var fillInstruction = [ol.render.canvas.Instruction.FILL];
-  this.hitDetectionInstructions.push(fillInstruction);
-  if (fill) {
-    this.instructions.push(fillInstruction);
+    pass = false;
+  } else if (this.freehand_) {
+    this.finishCoordinate_ = null;
+    this.abortDrawing_();
   }
-  if (stroke) {
-    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
-    this.instructions.push(strokeInstruction);
-    this.hitDetectionInstructions.push(strokeInstruction);
+  if (!pass && this.stopClick_) {
+    event.stopPropagation();
   }
-  return offset;
+  return pass;
 };
 
 
 /**
- * @inheritDoc
+ * Handle move events.
+ * @param {ol.MapBrowserEvent} event A move event.
+ * @return {boolean} Pass the event to other interactions.
+ * @private
  */
-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);
+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_;
   }
-  this.endGeometry(circleGeometry, feature);
-};
 
-
-/**
- * @inheritDoc
- */
-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
-    ]);
+  if (this.finishCoordinate_) {
+    this.modifyDrawing_(event);
+  } else {
+    this.createOrUpdateSketchPoint_(event);
   }
-  var ends = polygonGeometry.getEnds();
-  var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
-  var stride = polygonGeometry.getStride();
-  this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
-  this.endGeometry(polygonGeometry, feature);
+  return true;
 };
 
 
 /**
- * @inheritDoc
+ * Determine if an event is within the snapping tolerance of the start coord.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @return {boolean} The event is within the snapping tolerance of the start.
+ * @private
  */
-ol.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);
+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;
+        }
+      }
+    }
   }
-  this.endGeometry(multiPolygonGeometry, feature);
+  return at;
 };
 
 
 /**
- * @inheritDoc
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-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);
-    }
+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);
   }
 };
 
 
 /**
- * @inheritDoc
+ * Start the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-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_);
+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_;
     }
   }
-  return this.bufferedMaxExtent_;
+  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_));
 };
 
 
 /**
- * @inheritDoc
+ * Modify the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @private
  */
-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);
+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 {
-    state.fillStyle = undefined;
+    coordinates = this.sketchCoords_;
+    last = coordinates[coordinates.length - 1];
   }
-  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;
+  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));
     }
-  } else {
-    state.strokeStyle = undefined;
-    state.lineCap = undefined;
-    state.lineDash = null;
-    state.lineDashOffset = undefined;
-    state.lineJoin = undefined;
-    state.lineWidth = undefined;
-    state.miterLimit = undefined;
+    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_();
 };
 
 
 /**
+ * Add a new coordinate to the drawing.
+ * @param {ol.MapBrowserEvent} event Event.
  * @private
- * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
  */
-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]]);
+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;
+      }
     }
-    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;
+    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();
   }
 };
 
-goog.provide('ol.render.canvas.TextReplay');
-
-goog.require('ol');
-goog.require('ol.colorlike');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.Instruction');
-goog.require('ol.render.canvas.Replay');
-
 
 /**
- * @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
+ * Remove last point of the feature currently being drawn.
+ * @api
  */
-ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) {
-
-  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
-
-  /**
-   * @private
-   * @type {?ol.CanvasFillState}
-   */
-  this.replayFillState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
-   */
-  this.replayStrokeState_ = null;
+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);
+  }
 
-  /**
-   * @private
-   * @type {?ol.CanvasTextState}
-   */
-  this.replayTextState_ = null;
+  if (coordinates.length === 0) {
+    this.finishCoordinate_ = null;
+  }
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.text_ = '';
+  this.updateSketchFeatures_();
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetX_ = 0;
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetY_ = 0;
+/**
+ * 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_();
+  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();
+  }
 
-  /**
-   * @private
-   * @type {boolean|undefined}
-   */
-  this.textRotateWithView_ = undefined;
+  // cast multi-part geometries
+  if (this.type_ === ol.geom.GeometryType.MULTI_POINT) {
+    sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates]));
+  } else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING) {
+    sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates]));
+  } else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON) {
+    sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates]));
+  }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textRotation_ = 0;
+  // First dispatch event to allow full set up of feature
+  this.dispatchEvent(new ol.interaction.Draw.Event(
+      ol.interaction.DrawEventType.DRAWEND, sketchFeature));
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textScale_ = 0;
+  // Then insert feature
+  if (this.features_) {
+    this.features_.push(sketchFeature);
+  }
+  if (this.source_) {
+    this.source_.addFeature(sketchFeature);
+  }
+};
 
-  /**
-   * @private
-   * @type {?ol.CanvasFillState}
-   */
-  this.textFillState_ = null;
 
-  /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
-   */
-  this.textStrokeState_ = null;
+/**
+ * Stop drawing without adding the sketch feature to the target layer.
+ * @return {ol.Feature} The sketch feature (or null if none).
+ * @private
+ */
+ol.interaction.Draw.prototype.abortDrawing_ = function() {
+  this.finishCoordinate_ = null;
+  var sketchFeature = this.sketchFeature_;
+  if (sketchFeature) {
+    this.sketchFeature_ = null;
+    this.sketchPoint_ = null;
+    this.sketchLine_ = null;
+    this.overlay_.getSource().clear(true);
+  }
+  return sketchFeature;
+};
 
-  /**
-   * @private
-   * @type {?ol.CanvasTextState}
-   */
-  this.textState_ = null;
 
+/**
+ * Extend an existing geometry by adding additional points. This only works
+ * on features with `LineString` geometries, where the interaction will
+ * extend lines by adding points to the end of the coordinates array.
+ * @param {!ol.Feature} feature Feature to be extended.
+ * @api
+ */
+ol.interaction.Draw.prototype.extend = function(feature) {
+  var geometry = feature.getGeometry();
+  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_));
 };
-ol.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;
+ol.interaction.Draw.prototype.shouldStopEvent = ol.functions.FALSE;
+
+
+/**
+ * Redraw the sketch features.
+ * @private
+ */
+ol.interaction.Draw.prototype.updateSketchFeatures_ = function() {
+  var sketchFeatures = [];
+  if (this.sketchFeature_) {
+    sketchFeatures.push(this.sketchFeature_);
   }
-  if (this.textFillState_) {
-    this.setReplayFillState_(this.textFillState_);
+  if (this.sketchLine_) {
+    sketchFeatures.push(this.sketchLine_);
   }
-  if (this.textStrokeState_) {
-    this.setReplayStrokeState_(this.textStrokeState_);
+  if (this.sketchPoint_) {
+    sketchFeatures.push(this.sketchPoint_);
   }
-  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);
+  var overlaySource = this.overlay_.getSource();
+  overlaySource.clear(true);
+  overlaySource.addFeatures(sketchFeatures);
 };
 
 
 /**
- * @param {ol.CanvasFillState} 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;
+ol.interaction.Draw.prototype.updateState_ = function() {
+  var map = this.getMap();
+  var active = this.getActive();
+  if (!map || !active) {
+    this.abortDrawing_();
   }
+  this.overlay_.setMap(active ? map : null);
 };
 
 
 /**
- * @param {ol.CanvasStrokeState} strokeState Stroke state.
+ * 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 {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 ? /** @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;
+    }
+  );
+};
+
+
+/**
+ * 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.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;
+    }
+  );
+};
+
+
+/**
+ * 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.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;
+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);
 };
 
 
 /**
- * @param {ol.CanvasTextState} textState Text state.
+ * Draw mode.  This collapses multi-part geometry types with their single-part
+ * cousins.
+ * @enum {string}
  * @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;
-  }
+ol.interaction.Draw.Mode_ = {
+  POINT: 'Point',
+  LINE_STRING: 'LineString',
+  POLYGON: 'Polygon',
+  CIRCLE: 'Circle'
+};
+
+/**
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Draw} instances are instances of
+ * this type.
+ *
+ * @constructor
+ * @extends {ol.events.Event}
+ * @implements {oli.DrawEvent}
+ * @param {ol.interaction.DrawEventType} type Type.
+ * @param {ol.Feature} feature The feature drawn.
+ */
+ol.interaction.Draw.Event = function(type, feature) {
+
+  ol.events.Event.call(this, type);
+
+  /**
+   * The feature being drawn.
+   * @type {ol.Feature}
+   * @api
+   */
+  this.feature = feature;
+
 };
+ol.inherits(ol.interaction.Draw.Event, ol.events.Event);
+
+goog.provide('ol.interaction.ExtentEventType');
 
 
 /**
- * @inheritDoc
+ * @enum {string}
  */
-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.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;
-  }
+ol.interaction.ExtentEventType = {
+  /**
+   * Triggered after the extent is changed
+   * @event ol.interaction.Extent.Event#extentchanged
+   * @api
+   */
+  EXTENTCHANGED: 'extentchanged'
 };
 
-goog.provide('ol.render.canvas.ReplayGroup');
+goog.provide('ol.interaction.Extent');
 
 goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.dom');
+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.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');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.interaction.ExtentEventType');
+goog.require('ol.interaction.Pointer');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Style');
 
 
 /**
+ * @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.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
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.Extent.Event
+ * @param {olx.interaction.ExtentOptions=} opt_options Options.
+ * @api
  */
-ol.render.canvas.ReplayGroup = function(
-    tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) {
-  ol.render.ReplayGroup.call(this);
+ol.interaction.Extent = function(opt_options) {
+
+  var options = opt_options || {};
 
   /**
+   * Extent of the drawn box
+   * @type {ol.Extent}
    * @private
-   * @type {number}
    */
-  this.tolerance_ = tolerance;
+  this.extent_ = null;
 
   /**
+   * Handler for pointer move events
+   * @type {function (ol.Coordinate): ol.Extent|null}
    * @private
-   * @type {ol.Extent}
    */
-  this.maxExtent_ = maxExtent;
+  this.pointerHandler_ = null;
 
   /**
+   * Pixel threshold to snap to extent
+   * @type {number}
    * @private
-   * @type {boolean}
    */
-  this.overlaps_ = overlaps;
+  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
+    options.pixelTolerance : 10;
 
   /**
+   * Is the pointer snapped to an extent vertex
+   * @type {boolean}
    * @private
-   * @type {number}
    */
-  this.resolution_ = resolution;
+  this.snappedToVertex_ = false;
 
   /**
+   * Feature for displaying the visible extent
+   * @type {ol.Feature}
    * @private
-   * @type {number|undefined}
    */
-  this.renderBuffer_ = opt_renderBuffer;
+  this.extentFeature_ = null;
 
   /**
+   * Feature for displaying the visible pointer
+   * @type {ol.Feature}
    * @private
-   * @type {!Object.<string,
-   *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
    */
-  this.replaysByZIndex_ = {};
+  this.vertexFeature_ = null;
+
+  if (!opt_options) {
+    opt_options = {};
+  }
+
+  /* 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_
+  });
 
   /**
+   * Layer for the extentFeature
+   * @type {ol.layer.Vector}
    * @private
-   * @type {CanvasRenderingContext2D}
    */
-  this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1);
+  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
+  });
 
   /**
+   * Layer for the vertexFeature
+   * @type {ol.layer.Vector}
    * @private
-   * @type {ol.Transform}
    */
-  this.hitDetectionTransform_ = ol.transform.create();
-};
-ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);
-
+  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
+  });
 
-/**
- * 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.render.canvas.ReplayGroup.circleArrayCache_ = {
-  0: [[true]]
+  if (opt_options.extent) {
+    this.setExtent(opt_options.extent);
+  }
 };
 
+ol.inherits(ol.interaction.Extent, ol.interaction.Pointer);
 
 /**
- * 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.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Event.
+ * @return {boolean} Propagate event?
+ * @this {ol.interaction.Extent}
  * @private
  */
-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;
-    }
+ol.interaction.Extent.handleEvent_ = function(mapBrowserEvent) {
+  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
+    return true;
+  }
+  //display pointer (if not dragging)
+  if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERMOVE && !this.handlingDownUpSequence) {
+    this.handlePointerMove_(mapBrowserEvent);
   }
+  //call pointer to determine up/down/drag
+  ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent);
+  //return false to stop propagation
+  return false;
 };
 
-
 /**
- * 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.
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Event handled?
+ * @this {ol.interaction.Extent}
  * @private
  */
-ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) {
-  if (ol.render.canvas.ReplayGroup.circleArrayCache_[radius] !== undefined) {
-    return ol.render.canvas.ReplayGroup.circleArrayCache_[radius];
-  }
-
-  var arraySize = radius * 2 + 1;
-  var arr = new Array(arraySize);
-  for (var i = 0; i < arraySize; i++) {
-    arr[i] = new Array(arraySize);
-  }
+ol.interaction.Extent.handleDownEvent_ = function(mapBrowserEvent) {
+  var pixel = mapBrowserEvent.pixel;
+  var map = mapBrowserEvent.map;
 
-  var x = radius;
-  var y = 0;
-  var error = 0;
+  var extent = this.getExtent();
+  var vertex = this.snapToVertex_(pixel, map);
 
-  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);
+  //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];
+    }
+    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;
 
-    y++;
-    error += 1 + 2 * y;
-    if (2 * (error - x) + 1 > 0) {
-      x -= 1;
-      error += 1 - 2 * x;
+    //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);
   }
-
-  ol.render.canvas.ReplayGroup.circleArrayCache_[radius] = arr;
-  return arr;
+  return true; //event handled; start downup sequence
 };
 
 /**
- * FIXME empty description for jsdoc
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Event handled?
+ * @this {ol.interaction.Extent}
+ * @private
  */
-ol.render.canvas.ReplayGroup.prototype.finish = function() {
-  var zKey;
-  for (zKey in this.replaysByZIndex_) {
-    var replays = this.replaysByZIndex_[zKey];
-    var replayKey;
-    for (replayKey in replays) {
-      replays[replayKey].finish();
-    }
+ol.interaction.Extent.handleDragEvent_ = function(mapBrowserEvent) {
+  if (this.pointerHandler_) {
+    var pixelCoordinate = mapBrowserEvent.coordinate;
+    this.setExtent(this.pointerHandler_(pixelCoordinate));
+    this.createOrUpdatePointerFeature_(pixelCoordinate);
   }
+  return true;
 };
 
-
 /**
- * @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
+ * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Extent}
+ * @private
  */
-ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
-    coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback) {
-
-  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);
-  }
-
-  /**
-   * @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);
+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);
   }
-
-  var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance);
-
-  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);
+  return false; //Stop handling downup sequence
 };
 
+/**
+ * Returns the default style for the drawn bbox
+ *
+ * @return {ol.StyleFunction} Default Extent style
+ * @private
+ */
+ol.interaction.Extent.getDefaultExtentStyleFunction_ = function() {
+  var style = ol.style.Style.createDefaultEditing();
+  return function(feature, resolution) {
+    return style[ol.geom.GeometryType.POLYGON];
+  };
+};
 
 /**
- * @param {ol.Transform} transform Transform.
- * @return {Array.<number>} Clip coordinates.
+ * Returns the default style for the pointer
+ *
+ * @return {ol.StyleFunction} Default pointer style
+ * @private
  */
-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;
+ol.interaction.Extent.getDefaultPointerStyleFunction_ = function() {
+  var style = ol.style.Style.createDefaultEditing();
+  return function(feature, resolution) {
+    return style[ol.geom.GeometryType.POINT];
+  };
 };
 
+/**
+ * @param {ol.Coordinate} fixedPoint corner that will be unchanged in the new extent
+ * @returns {function (ol.Coordinate): ol.Extent} event handler
+ * @private
+ */
+ol.interaction.Extent.getPointHandler_ = function(fixedPoint) {
+  return function(point) {
+    return ol.extent.boundingExtent([fixedPoint, point]);
+  };
+};
 
 /**
- * @inheritDoc
+ * @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.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;
+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;
   }
-  return replay;
 };
 
-
 /**
- * @inheritDoc
+ * @param {ol.Extent} extent extent
+ * @returns {Array<Array<ol.Coordinate>>} extent line segments
+ * @private
  */
-ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
-  return ol.obj.isEmpty(this.replaysByZIndex_);
+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]]]
+  ];
 };
 
-
 /**
- * @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}
+ * @param {ol.Pixel} pixel cursor location
+ * @param {ol.PluggableMap} map map
+ * @returns {ol.Coordinate|null} snapped vertex on extent
+ * @private
  */
-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);
+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];
 
-  // 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();
+    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
+        closestSegment));
+    var vertexPixel = map.getPixelFromCoordinate(vertex);
 
-  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);
+    //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;
     }
   }
-
-  context.restore();
+  return null;
 };
 
-
 /**
+ * @param {ol.MapBrowserEvent} mapBrowserEvent pointer move event
  * @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.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;
-  });
+ol.interaction.Extent.prototype.handlePointerMove_ = function(mapBrowserEvent) {
+  var pixel = mapBrowserEvent.pixel;
+  var map = mapBrowserEvent.map;
 
-  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;
-        }
-      }
-    }
+  var vertex = this.snapToVertex_(pixel, map);
+  if (!vertex) {
+    vertex = map.getCoordinateFromPixel(pixel);
   }
-  return undefined;
+  this.createOrUpdatePointerFeature_(vertex);
 };
 
-
 /**
- * @const
+ * @param {ol.Extent} extent extent
+ * @returns {ol.Feature} extent as featrue
  * @private
- * @type {Object.<ol.render.ReplayType,
- *                function(new: ol.render.canvas.Replay, number, ol.Extent,
- *                number, boolean)>}
  */
-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');
+ol.interaction.Extent.prototype.createOrUpdateExtentFeature_ = function(extent) {
+  var extentFeature = this.extentFeature_;
 
-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');
+  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;
+};
 
 
 /**
- * @constructor
- * @extends {ol.Observable}
- * @param {ol.layer.Layer} layer Layer.
- * @struct
+ * @param {ol.Coordinate} vertex location of feature
+ * @returns {ol.Feature} vertex as feature
+ * @private
  */
-ol.renderer.Layer = function(layer) {
-
-  ol.Observable.call(this);
-
-  /**
-   * @private
-   * @type {ol.layer.Layer}
-   */
-  this.layer_ = layer;
-
-
+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;
 };
-ol.inherits(ol.renderer.Layer, ol.Observable);
 
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
-
+ol.interaction.Extent.prototype.setMap = function(map) {
+  this.extentOverlay_.setMap(map);
+  this.vertexOverlay_.setMap(map);
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
+};
 
 /**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {olx.FrameState} frameState Frame state.
- * @return {boolean} Is there a feature at the given coordinate?
+ * Returns the current drawn extent in the view projection
+ *
+ * @return {ol.Extent} Drawn extent in the view projection.
+ * @api
  */
-ol.renderer.Layer.prototype.hasFeatureAtCoordinate = ol.functions.FALSE;
-
+ol.interaction.Extent.prototype.getExtent = function() {
+  return this.extent_;
+};
 
 /**
- * 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
+ * Manually sets the drawn extent, using the view projection.
+ *
+ * @param {ol.Extent} extent Extent
+ * @api
  */
-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);
-      });
+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_));
 };
 
 
 /**
- * @return {ol.layer.Layer} Layer.
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Extent} instances are instances of
+ * this type.
+ *
+ * @constructor
+ * @implements {oli.ExtentEvent}
+ * @param {ol.Extent} extent the new extent
+ * @extends {ol.events.Event}
  */
-ol.renderer.Layer.prototype.getLayer = function() {
-  return this.layer_;
+ol.interaction.Extent.Event = function(extent) {
+  ol.events.Event.call(this, ol.interaction.ExtentEventType.EXTENTCHANGED);
+
+  /**
+   * The current extent.
+   * @type {ol.Extent}
+   * @api
+   */
+  this.extent = extent;
+
 };
+ol.inherits(ol.interaction.Extent.Event, ol.events.Event);
+
+goog.provide('ol.interaction.ModifyEventType');
 
 
 /**
- * Handle changes in image state.
- * @param {ol.events.Event} event Image change event.
- * @private
+ * @enum {string}
  */
-ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
-  var image = /** @type {ol.Image} */ (event.target);
-  if (image.getState() === ol.ImageState.LOADED) {
-    this.renderIfReadyAndVisible();
-  }
+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.interaction.Modify');
+
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Feature');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapBrowserPointerEvent');
+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.source.VectorEventType');
+goog.require('ol.structs.RBush');
+goog.require('ol.style.Style');
 
 /**
- * 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
+ * @classdesc
+ * Interaction for modifying feature geometries.  To modify features that have
+ * been added to an existing source, construct the modify interaction with the
+ * `source` option.  If you want to modify features in a collection (for example,
+ * the collection used by a select interaction), construct the interaction with
+ * the `features` option.  The interaction must be constructed with either a
+ * `source` or `features` option.
+ *
+ * By default, the interaction will allow deletion of vertices when the `alt`
+ * key is pressed.  To configure the interaction with a different condition
+ * for deletion, use the `deleteCondition` option.
+ *
+ * @constructor
+ * @extends {ol.interaction.Pointer}
+ * @param {olx.interaction.ModifyOptions} options Options.
+ * @fires ol.interaction.Modify.Event
+ * @api
  */
-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;
-};
+ol.interaction.Modify = function(options) {
+
+  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 {ol.EventsConditionType}
+   */
+  this.condition_ = options.condition ?
+    options.condition : ol.events.condition.primaryAction;
 
 
-/**
- * @protected
- */
-ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
-  var layer = this.getLayer();
-  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
-    this.changed();
-  }
-};
+  /**
+   * @private
+   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
+   * @return {boolean} Combined condition result.
+   */
+  this.defaultDeleteCondition_ = function(mapBrowserEvent) {
+    return ol.events.condition.altKeyOnly(mapBrowserEvent) &&
+      ol.events.condition.singleClick(mapBrowserEvent);
+  };
 
+  /**
+   * @type {ol.EventsConditionType}
+   * @private
+   */
+  this.deleteCondition_ = options.deleteCondition ?
+    options.deleteCondition : this.defaultDeleteCondition_;
 
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Tile} tileSource Tile source.
- * @protected
- */
-ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) {
-  if (tileSource.canExpireCache()) {
-    /**
-     * @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);
+  /**
+   * @type {ol.EventsConditionType}
+   * @private
+   */
+  this.insertVertexCondition_ = options.insertVertexCondition ?
+    options.insertVertexCondition : ol.events.condition.always;
 
-    frameState.postRenderFunctions.push(
-      /** @type {ol.PostRenderFunction} */ (postRenderFunction)
-    );
-  }
-};
+  /**
+   * Editing vertex.
+   * @type {ol.Feature}
+   * @private
+   */
+  this.vertexFeature_ = null;
 
+  /**
+   * Segments intersecting {@link this.vertexFeature_} by segment uid.
+   * @type {Object.<string, boolean>}
+   * @private
+   */
+  this.vertexSegments_ = null;
 
-/**
- * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
- *     set (target).
- * @param {Array.<ol.Attribution>} attributions Attributions (source).
- * @protected
- */
-ol.renderer.Layer.prototype.updateAttributions = function(attributionsSet, attributions) {
-  if (attributions) {
-    var attribution, i, ii;
-    for (i = 0, ii = attributions.length; i < ii; ++i) {
-      attribution = attributions[i];
-      attributionsSet[ol.getUid(attribution).toString()] = attribution;
-    }
-  }
-};
+  /**
+   * @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;
 
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Source} source Source.
- * @protected
- */
-ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
-  var logo = source.getLogo();
-  if (logo !== undefined) {
-    if (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;
-    }
-  }
-};
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.modified_ = false;
 
+  /**
+   * Segment RTree for each layer
+   * @type {ol.structs.RBush.<ol.ModifySegmentDataType>}
+   * @private
+   */
+  this.rBush_ = new ol.structs.RBush();
 
-/**
- * @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 = 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;
-  }
-};
+  /**
+   * @type {number}
+   * @private
+   */
+  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
+    options.pixelTolerance : 10;
 
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.snappedToVertex_ = false;
 
-/**
- * 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 = 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);
-        }
-      }
-    }
-  }
-};
+  /**
+   * Indicate whether the interaction is currently changing a feature's
+   * coordinates.
+   * @type {boolean}
+   * @private
+   */
+  this.changingFeature_ = false;
 
-goog.provide('ol.renderer.canvas.Layer');
+  /**
+   * @type {Array}
+   * @private
+   */
+  this.dragSegments_ = [];
 
-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');
+  /**
+   * 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_
+  };
 
 
-/**
- * @constructor
- * @abstract
- * @extends {ol.renderer.Layer}
- * @param {ol.layer.Layer} layer Layer.
- */
-ol.renderer.canvas.Layer = function(layer) {
+  /**
+   * @type {ol.source.Vector}
+   * @private
+   */
+  this.source_ = null;
 
-  ol.renderer.Layer.call(this, layer);
+  var features;
+  if (options.source) {
+    this.source_ = options.source;
+    features = new ol.Collection(this.source_.getFeatures());
+    ol.events.listen(this.source_, ol.source.VectorEventType.ADDFEATURE,
+        this.handleSourceAdd_, this);
+    ol.events.listen(this.source_, ol.source.VectorEventType.REMOVEFEATURE,
+        this.handleSourceRemove_, this);
+  } else {
+    features = options.features;
+  }
+  if (!features) {
+    throw new Error('The modify interaction requires features or a source');
+  }
 
   /**
-   * @protected
-   * @type {number}
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
    */
-  this.renderedResolution;
+  this.features_ = 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
-   * @type {ol.Transform}
    */
-  this.transform_ = ol.transform.create();
+  this.lastPointerEvent_ = null;
 
 };
-ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);
+ol.inherits(ol.interaction.Modify, ol.interaction.Pointer);
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Extent} extent Clip extent.
- * @protected
+ * @define {number} The segment index assigned to a circle's center when
+ * breaking up a cicrle into ModifySegmentDataType segments.
  */
-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);
+ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX = 0;
 
-  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);
-};
+/**
+ * @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.render.EventType} type Event type.
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Transform=} opt_transform Transform.
+ * @param {ol.Feature} feature Feature.
  * @private
  */
-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);
+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.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
+ * @param {ol.MapBrowserPointerEvent} evt Map browser event
+ * @private
  */
-ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) {
-  var hasFeature = this.forEachFeatureAtCoordinate(
-      coordinate, frameState, 0, ol.functions.TRUE, this);
-
-  if (hasFeature) {
-    return callback.call(thisArg, this.getLayer(), null);
-  } else {
-    return undefined;
+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));
   }
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @param {ol.Transform=} opt_transform Transform.
- * @protected
+ * @param {ol.Feature} feature Feature.
+ * @private
  */
-ol.renderer.canvas.Layer.prototype.postCompose = function(context, frameState, layerState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context,
-      frameState, opt_transform);
+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);
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Transform=} opt_transform Transform.
- * @protected
+ * @param {ol.Feature} feature Feature.
+ * @private
  */
-ol.renderer.canvas.Layer.prototype.preCompose = function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context,
-      frameState, opt_transform);
+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]);
+  }
 };
 
 
 /**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Transform=} opt_transform Transform.
- * @protected
+ * @inheritDoc
  */
-ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.EventType.RENDER, context,
-      frameState, opt_transform);
+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);
 };
 
 
 /**
- * @param {olx.FrameState} frameState Frame state.
- * @param {number} offsetX Offset on the x-axis in view coordinates.
- * @protected
- * @return {!ol.Transform} Transform.
+ * @inheritDoc
  */
-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);
+ol.interaction.Modify.prototype.setMap = function(map) {
+  this.overlay_.setMap(map);
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
 };
 
 
 /**
- * @abstract
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @param {CanvasRenderingContext2D} context Context.
- */
-ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {};
-
-/**
- * @abstract
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @return {boolean} whether composeFrame should be called.
- */
-ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {};
-
-goog.provide('ol.renderer.vector');
-
-goog.require('ol');
-goog.require('ol.ImageState');
-goog.require('ol.render.ReplayType');
-
-
-/**
- * @param {ol.Feature|ol.render.Feature} feature1 Feature 1.
- * @param {ol.Feature|ol.render.Feature} feature2 Feature 2.
- * @return {number} Order.
+ * @param {ol.source.Vector.Event} event Event.
+ * @private
  */
-ol.renderer.vector.defaultOrder = function(feature1, feature2) {
-  return ol.getUid(feature1) - ol.getUid(feature2);
+ol.interaction.Modify.prototype.handleSourceAdd_ = function(event) {
+  if (event.feature) {
+    this.features_.push(event.feature);
+  }
 };
 
 
 /**
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @return {number} Squared pixel tolerance.
+ * @param {ol.source.Vector.Event} event Event.
+ * @private
  */
-ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) {
-  var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
-  return tolerance * tolerance;
+ol.interaction.Modify.prototype.handleSourceRemove_ = function(event) {
+  if (event.feature) {
+    this.features_.remove(event.feature);
+  }
 };
 
 
 /**
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @return {number} Pixel tolerance.
+ * @param {ol.Collection.Event} evt Event.
+ * @private
  */
-ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
-  return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
+ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) {
+  this.addFeature_(/** @type {ol.Feature} */ (evt.element));
 };
 
 
 /**
- * @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.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 {ol.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.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
+ * @param {ol.Collection.Event} evt Event.
+ * @private
  */
-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;
-    }
-  }
-  ol.renderer.vector.renderFeature_(replayGroup, feature, style,
-      squaredTolerance);
-  return loading;
+ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
+  var feature = /** @type {ol.Feature} */ (evt.element);
+  this.removeFeature_(feature);
 };
 
 
 /**
- * @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 {ol.Feature} feature Feature
+ * @param {ol.geom.Point} geometry Geometry.
  * @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()];
-  geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
+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);
 };
 
 
 /**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPoint} geometry Geometry.
  * @private
  */
-ol.renderer.vector.renderGeometryCollectionGeometry_ = function(replayGroup, geometry, style, feature) {
-  var geometries = geometry.getGeometriesArray();
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    var geometryRenderer =
-        ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()];
-    geometryRenderer(replayGroup, geometries[i], style, feature);
+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);
   }
 };
 
 
 /**
- * @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.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.LineString} geometry Geometry.
  * @private
  */
-ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
-  var strokeStyle = style.getStroke();
-  if (strokeStyle) {
-    var lineStringReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
-    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
-    lineStringReplay.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);
+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);
   }
 };
 
 
 /**
- * @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.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiLineString} geometry Geometry.
  * @private
  */
-ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
-  var strokeStyle = style.getStroke();
-  if (strokeStyle) {
-    var lineStringReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
-    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
-    lineStringReplay.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);
+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);
+    }
   }
 };
 
 
 /**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Polygon} geometry Geometry.
  * @private
  */
-ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (strokeStyle || fillStyle) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.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);
+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);
+    }
   }
 };
 
 
 /**
- * @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.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
  * @private
  */
-ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) {
-  var imageStyle = style.getImage();
-  if (imageStyle) {
-    if (imageStyle.getImageState() != ol.ImageState.LOADED) {
-      return;
+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);
+      }
     }
-    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);
   }
 };
 
 
 /**
- * @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.
+ * 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.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);
-  }
+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);
 };
 
 
 /**
- * @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.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
  * @private
  */
-ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (fillStyle || strokeStyle) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.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);
+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]);
   }
 };
 
 
 /**
- * @const
+ * @param {ol.Coordinate} coordinates Coordinates.
+ * @return {ol.Feature} Vertex feature.
  * @private
- * @type {Object.<ol.geom.GeometryType,
- *                function(ol.render.ReplayGroup, 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_
+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;
 };
 
-goog.provide('ol.renderer.canvas.VectorLayer');
 
-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 {ol.ModifySegmentDataType} a The first segment data.
+ * @param {ol.ModifySegmentDataType} b The second segment data.
+ * @return {number} The difference in indexes.
+ * @private
+ */
+ol.interaction.Modify.compareIndexes_ = function(a, b) {
+  return a.index - b.index;
+};
 
 
 /**
- * @constructor
- * @extends {ol.renderer.canvas.Layer}
- * @param {ol.layer.Vector} vectorLayer Vector layer.
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Modify}
+ * @private
  */
-ol.renderer.canvas.VectorLayer = function(vectorLayer) {
+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) {
 
-  ol.renderer.canvas.Layer.call(this, vectorLayer);
+        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]) {
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.dirty_ = false;
+        // 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;
+        }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = -1;
+        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_;
+};
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedResolution_ = NaN;
 
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.renderedExtent_ = ol.extent.createEmpty();
+/**
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @this {ol.interaction.Modify}
+ * @private
+ */
+ol.interaction.Modify.handleDragEvent_ = function(evt) {
+  this.ignoreNextSingleClick_ = false;
+  this.willModifyFeatures_(evt);
 
-  /**
-   * @private
-   * @type {function(ol.Feature, ol.Feature): number|null}
-   */
-  this.renderedRenderOrder_ = null;
+  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];
 
-  /**
-   * @private
-   * @type {ol.render.canvas.ReplayGroup}
-   */
-  this.replayGroup_ = null;
+    while (vertex.length < geometry.getStride()) {
+      vertex.push(segment[index][vertex.length]);
+    }
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D();
+    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);
 };
-ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
 
 
 /**
- * @inheritDoc
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Modify}
+ * @private
  */
-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());
+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;
+};
 
-  var transform = this.getTransform(frameState, 0);
 
-  this.preCompose(context, frameState, transform);
+/**
+ * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the
+ * geometry.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.Modify}
+ * @api
+ */
+ol.interaction.Modify.handleEvent = function(mapBrowserEvent) {
+  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
+    return true;
+  }
+  this.lastPointerEvent_ = mapBrowserEvent;
 
-  // 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 handled;
+  if (!mapBrowserEvent.map.getView().getInteracting() &&
+      mapBrowserEvent.type == ol.MapBrowserEventType.POINTERMOVE &&
+      !this.handlingDownUpSequence) {
+    this.handlePointerMove_(mapBrowserEvent);
   }
-  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_;
+  if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) {
+    if (mapBrowserEvent.type != ol.MapBrowserEventType.SINGLECLICK ||
+        !this.ignoreNextSingleClick_) {
+      handled = this.removePoint();
     } 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.render.canvas.rotateAtOffset(replayContext, rotation,
-        width / 2, height / 2);
-
-    if (replayContext != context) {
-      this.dispatchRenderEvent(replayContext, frameState, transform);
-      context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
-      replayContext.translate(-drawOffsetX, -drawOffsetY);
+      handled = true;
     }
-    replayContext.globalAlpha = alpha;
   }
 
-  if (clipped) {
-    context.restore();
+  if (mapBrowserEvent.type == ol.MapBrowserEventType.SINGLECLICK) {
+    this.ignoreNextSingleClick_ = false;
   }
-  this.postCompose(context, frameState, layerState, transform);
 
+  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
+      !handled;
 };
 
 
 /**
- * @inheritDoc
+ * @param {ol.MapBrowserEvent} evt Event.
+ * @private
  */
-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);
-          }
-        });
-  }
+ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
+  this.lastPixel_ = evt.pixel;
+  this.handlePointerAtPixel_(evt.pixel, evt.map);
 };
 
 
 /**
- * Handle changes in image style state.
- * @param {ol.events.Event} event Image style change event.
+ * @param {ol.Pixel} pixel Pixel
+ * @param {ol.PluggableMap} map Map.
  * @private
  */
-ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
-  this.renderIfReadyAndVisible();
+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 = {};
+
+      if (node.geometry.getType() === ol.geom.GeometryType.CIRCLE &&
+      node.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
+
+        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;
+  }
 };
 
 
 /**
- * @inheritDoc
+ * Returns the distance from a point to a line segment.
+ *
+ * @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.
  */
-ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) {
+ol.interaction.Modify.pointDistanceToSegmentDataSquared_ = function(pointCoordinates, segmentData) {
+  var geometry = segmentData.geometry;
 
-  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
-  var vectorSource = vectorLayer.getSource();
+  if (geometry.getType() === ol.geom.GeometryType.CIRCLE) {
+    var circleGeometry = /** @type {ol.geom.Circle} */ (geometry);
 
-  this.updateAttributions(
-      frameState.attributions, vectorSource.getAttributions());
-  this.updateLogos(frameState, vectorSource);
+    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;
+    }
+  }
+  return ol.coordinate.squaredDistanceToSegment(pointCoordinates, segmentData.segment);
+};
 
-  var animating = frameState.viewHints[ol.ViewHint.ANIMATING];
-  var interacting = frameState.viewHints[ol.ViewHint.INTERACTING];
-  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
-  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();
+/**
+ * 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.
+ */
+ol.interaction.Modify.closestOnSegmentData_ = function(pointCoordinates, segmentData) {
+  var geometry = segmentData.geometry;
 
-  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
-      (!updateWhileInteracting && interacting)) {
-    return true;
+  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);
+};
 
-  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;
-  }
+/**
+ * @param {ol.ModifySegmentDataType} 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 = /** @type {number} */ (segmentData.index);
+  var coordinates;
 
-  var extent = ol.extent.buffer(frameStateExtent,
-      vectorLayerRenderBuffer * resolution);
-  var projectionExtent = viewState.projection.getExtent();
+  while (vertex.length < geometry.getStride()) {
+    vertex.push(0);
+  }
 
-  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;
+  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;
   }
 
-  if (!this.dirty_ &&
-      this.renderedResolution_ == resolution &&
-      this.renderedRevision_ == vectorLayerRevision &&
-      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
-      ol.extent.containsExtent(this.renderedExtent_, extent)) {
+  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]);
+
+  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;
+};
+
+/**
+ * Removes the vertex currently being pointed.
+ * @return {boolean} True when a vertex was removed.
+ * @api
+ */
+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;
+};
 
-  this.replayGroup_ = null;
-
-  this.dirty_ = false;
+/**
+ * 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 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;
+    }
 
-  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);
+  }
+  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 {
-      styleFunction = vectorLayer.getStyleFunction();
-      if (styleFunction) {
-        styles = styleFunction(feature, resolution);
+      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;
     }
-    if (styles) {
-      var dirty = this.renderFeature(
-          feature, resolution, pixelRatio, styles, replayGroup);
-      this.dirty_ = this.dirty_ || dirty;
-    }
-  };
-  if (vectorLayerRenderOrder) {
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    vectorSource.forEachFeatureInExtent(extent,
-        /**
-         * @param {ol.Feature} feature Feature.
-         */
-        function(feature) {
-          features.push(feature);
-        }, this);
-    features.sort(vectorLayerRenderOrder);
-    features.forEach(renderFeature, this);
-  } else {
-    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
+
   }
-  replayGroup.finish();
+  return deleted;
+};
 
-  this.renderedResolution_ = resolution;
-  this.renderedRevision_ = vectorLayerRevision;
-  this.renderedRenderOrder_ = vectorLayerRenderOrder;
-  this.renderedExtent_ = extent;
-  this.replayGroup_ = replayGroup;
 
-  return true;
+/**
+ * @param {ol.geom.SimpleGeometry} geometry Geometry.
+ * @param {Array} coordinates Coordinates.
+ * @private
+ */
+ol.interaction.Modify.prototype.setGeometryCoordinates_ = function(geometry, coordinates) {
+  this.changingFeature_ = true;
+  geometry.setCoordinates(coordinates);
+  this.changingFeature_ = false;
 };
 
 
 /**
- * @param {ol.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.
+ * @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.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
-  if (!styles) {
-    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],
-          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-          this.handleStyleImageChange_, this) || loading;
+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;
     }
-  } else {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles,
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleStyleImageChange_, this) || loading;
-  }
-  return loading;
+  });
 };
 
-// This file is automatically generated, do not edit
-/* eslint openlayers-internal/no-missing-requires: 0 */
-goog.provide('ol.renderer.webgl.defaultmapshader');
 
-goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
+/**
+ * @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];
+  };
+};
 
-if (ol.ENABLE_WEBGL) {
 
-  /**
-   * @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);
+/**
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Modify} instances are instances of
+ * this type.
+ *
+ * @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);
 
   /**
-   * @const
-   * @type {string}
+   * The features being modified.
+   * @type {ol.Collection.<ol.Feature>}
+   * @api
    */
-  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';
-
+  this.features = features;
 
   /**
-   * @const
-   * @type {string}
+   * Associated {@link ol.MapBrowserEvent}.
+   * @type {ol.MapBrowserEvent}
+   * @api
    */
-  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;}';
+  this.mapBrowserEvent = mapBrowserPointerEvent;
+};
+ol.inherits(ol.interaction.Modify.Event, ol.events.Event);
 
+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');
 
-  /**
-   * @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;
 
+/**
+ * @classdesc
+ * Interaction for selecting vector features. By default, selected features are
+ * styled differently, so this interaction can be used for visual highlighting,
+ * as well as selecting features for other actions, such as modification or
+ * output. There are three ways of controlling which features are selected:
+ * using the browser event as defined by the `condition` and optionally the
+ * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a
+ * further feature filter using the `filter` option.
+ *
+ * Selected features are added to an internal unmanaged layer.
+ *
+ * @constructor
+ * @extends {ol.interaction.Interaction}
+ * @param {olx.interaction.SelectOptions=} opt_options Options.
+ * @fires ol.interaction.Select.Event
+ * @api
+ */
+ol.interaction.Select = function(opt_options) {
 
-  ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment();
+  ol.interaction.Interaction.call(this, {
+    handleEvent: ol.interaction.Select.handleEvent
+  });
 
+  var options = opt_options ? opt_options : {};
 
   /**
-   * @constructor
-   * @extends {ol.webgl.Vertex}
-   * @struct
+   * @private
+   * @type {ol.EventsConditionType}
    */
-  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);
-
+  this.condition_ = options.condition ?
+    options.condition : ol.events.condition.singleClick;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {ol.EventsConditionType}
    */
-  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';
-
+  this.addCondition_ = options.addCondition ?
+    options.addCondition : ol.events.condition.never;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {ol.EventsConditionType}
    */
-  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;}';
+  this.removeCondition_ = options.removeCondition ?
+    options.removeCondition : ol.events.condition.never;
 
+  /**
+   * @private
+   * @type {ol.EventsConditionType}
+   */
+  this.toggleCondition_ = options.toggleCondition ?
+    options.toggleCondition : ol.events.condition.shiftKeyOnly;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {boolean}
    */
-  ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
-      ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE :
-      ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE;
+  this.multi_ = options.multi ? options.multi : false;
 
+  /**
+   * @private
+   * @type {ol.SelectFilterFunction}
+   */
+  this.filter_ = options.filter ? options.filter :
+    ol.functions.TRUE;
 
-  ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex();
+  /**
+   * @private
+   * @type {number}
+   */
+  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
+  });
 
   /**
-   * @constructor
-   * @param {WebGLRenderingContext} gl GL.
-   * @param {WebGLProgram} program Program.
-   * @struct
+   * @private
+   * @type {ol.layer.Vector}
    */
-  ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) {
-
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_opacity = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f');
+  this.featureOverlay_ = featureOverlay;
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_projectionMatrix = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'e');
+  /** @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;
+  }
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_texCoordMatrix = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_texCoordMatrix' : 'd');
+  /**
+   * @private
+   * @type {function(ol.layer.Layer): boolean}
+   */
+  this.layerFilter_ = layerFilter;
 
-    /**
-     * @type {WebGLUniformLocation}
-     */
-    this.u_texture = gl.getUniformLocation(
-        program, ol.DEBUG_WEBGL ? 'u_texture' : 'g');
+  /**
+   * An association between selected feature (key)
+   * and layer (value)
+   * @private
+   * @type {Object.<number, ol.layer.Layer>}
+   */
+  this.featureLayerAssociation_ = {};
 
-    /**
-     * @type {number}
-     */
-    this.a_position = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_position' : 'b');
+  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);
 
-    /**
-     * @type {number}
-     */
-    this.a_texCoord = gl.getAttribLocation(
-        program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c');
-  };
+};
+ol.inherits(ol.interaction.Select, ol.interaction.Interaction);
 
-}
 
-goog.provide('ol.renderer.webgl.Layer');
+/**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {ol.layer.Layer} layer Layer.
+ * @private
+ */
+ol.interaction.Select.prototype.addFeatureLayerAssociation_ = function(feature, layer) {
+  var key = ol.getUid(feature);
+  this.featureLayerAssociation_[key] = 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');
 
+/**
+ * Get the selected features.
+ * @return {ol.Collection.<ol.Feature>} Features collection.
+ * @api
+ */
+ol.interaction.Select.prototype.getFeatures = function() {
+  return this.featureOverlay_.getSource().getFeaturesCollection();
+};
 
-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) {
+/**
+ * Returns the Hit-detection tolerance.
+ * @returns {number} Hit tolerance in pixels.
+ * @api
+ */
+ol.interaction.Select.prototype.getHitTolerance = function() {
+  return this.hitTolerance_;
+};
 
-    ol.renderer.Layer.call(this, layer);
 
-    /**
-     * @protected
-     * @type {ol.renderer.webgl.Map}
-     */
-    this.mapRenderer = mapRenderer;
+/**
+ * Returns the associated {@link ol.layer.Vector vectorlayer} of
+ * the (last) selected feature. Note that this will not work with any
+ * programmatic method like pushing features to
+ * {@link ol.interaction.Select#getFeatures collection}.
+ * @param {ol.Feature|ol.render.Feature} feature Feature
+ * @return {ol.layer.Vector} Layer.
+ * @api
+ */
+ol.interaction.Select.prototype.getLayer = function(feature) {
+  var key = ol.getUid(feature);
+  return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]);
+};
 
-    /**
-     * @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;
+/**
+ * Handles the {@link ol.MapBrowserEvent map browser event} and may change the
+ * selected state of features.
+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
+ * @return {boolean} `false` to stop event propagation.
+ * @this {ol.interaction.Select}
+ * @api
+ */
+ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
+  if (!this.condition_(mapBrowserEvent)) {
+    return true;
+  }
+  var add = this.addCondition_(mapBrowserEvent);
+  var remove = this.removeCondition_(mapBrowserEvent);
+  var toggle = this.toggleCondition_(mapBrowserEvent);
+  var set = !add && !remove && !toggle;
+  var map = mapBrowserEvent.map;
+  var features = this.featureOverlay_.getSource().getFeaturesCollection();
+  var 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);
+      }
+    }
+    if (selected.length !== 0) {
+      features.extend(selected);
+    }
+  } 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);
+};
 
-    /**
-     * @protected
-     * @type {WebGLFramebuffer}
-     */
-    this.framebuffer = null;
 
-    /**
-     * @protected
-     * @type {number|undefined}
-     */
-    this.framebufferDimension = undefined;
+/**
+ * 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
+ */
+ol.interaction.Select.prototype.setHitTolerance = function(hitTolerance) {
+  this.hitTolerance_ = hitTolerance;
+};
 
-    /**
-     * @protected
-     * @type {ol.Transform}
-     */
-    this.texCoordMatrix = ol.transform.create();
 
-    /**
-     * @protected
-     * @type {ol.Transform}
-     */
-    this.projectionMatrix = ol.transform.create();
+/**
+ * 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.PluggableMap} map Map.
+ * @override
+ * @api
+ */
+ol.interaction.Select.prototype.setMap = function(map) {
+  var currentMap = this.getMap();
+  var selectedFeatures =
+      this.featureOverlay_.getSource().getFeaturesCollection();
+  if (currentMap) {
+    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
+  }
+  ol.interaction.Interaction.prototype.setMap.call(this, map);
+  this.featureOverlay_.setMap(map);
+  if (map) {
+    selectedFeatures.forEach(map.skipFeature, map);
+  }
+};
 
-    /**
-     * @type {Array.<number>}
-     * @private
-     */
-    this.tmpMat4_ = ol.vec.Mat4.create();
 
-    /**
-     * @private
-     * @type {ol.renderer.webgl.defaultmapshader.Locations}
-     */
-    this.defaultLocations_ = null;
+/**
+ * @return {ol.StyleFunction} Styles.
+ */
+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()];
   };
-  ol.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) {
-      /**
-       * @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)
-      );
+/**
+ * @param {ol.Collection.Event} evt Event.
+ * @private
+ */
+ol.interaction.Select.prototype.addFeature_ = function(evt) {
+  var map = this.getMap();
+  if (map) {
+    map.skipFeature(/** @type {ol.Feature} */ (evt.element));
+  }
+};
 
-      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);
+/**
+ * @param {ol.Collection.Event} evt Event.
+ * @private
+ */
+ol.interaction.Select.prototype.removeFeature_ = function(evt) {
+  var map = this.getMap();
+  if (map) {
+    map.unskipFeature(/** @type {ol.Feature} */ (evt.element));
+  }
+};
 
-      this.texture = texture;
-      this.framebuffer = framebuffer;
-      this.framebufferDimension = framebufferDimension;
 
-    } else {
-      gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer);
-    }
+/**
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = function(feature) {
+  var key = ol.getUid(feature);
+  delete this.featureLayerAssociation_[key];
+};
 
-  };
 
+/**
+ * @classdesc
+ * Events emitted by {@link ol.interaction.Select} instances are instances of
+ * this type.
+ *
+ * @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
+ */
+ol.interaction.Select.Event = function(type, selected, deselected, mapBrowserEvent) {
+  ol.events.Event.call(this, type);
 
   /**
-   * @param {olx.FrameState} frameState Frame state.
-   * @param {ol.LayerState} layerState Layer state.
-   * @param {ol.webgl.Context} context Context.
+   * Selected features array.
+   * @type {Array.<ol.Feature>}
+   * @api
    */
-  ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) {
-
-    this.dispatchComposeEvent_(
-        ol.render.EventType.PRECOMPOSE, context, frameState);
+  this.selected = selected;
 
-    context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_);
+  /**
+   * Deselected features array.
+   * @type {Array.<ol.Feature>}
+   * @api
+   */
+  this.deselected = deselected;
 
-    var gl = context.getGL();
+  /**
+   * Associated {@link ol.MapBrowserEvent}.
+   * @type {ol.MapBrowserEvent}
+   * @api
+   */
+  this.mapBrowserEvent = mapBrowserEvent;
+};
+ol.inherits(ol.interaction.Select.Event, ol.events.Event);
 
-    var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment;
-    var vertexShader = ol.renderer.webgl.defaultmapshader.vertex;
 
-    var program = context.getProgram(fragmentShader, vertexShader);
+/**
+ * @enum {string}
+ * @private
+ */
+ol.interaction.Select.EventType_ = {
+  /**
+   * Triggered when feature(s) has been (de)selected.
+   * @event ol.interaction.Select.Event#select
+   * @api
+   */
+  SELECT: 'select'
+};
 
-    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_;
-    }
+goog.provide('ol.interaction.Snap');
 
-    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);
-    }
+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');
 
-    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);
+/**
+ * @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) {
 
-  };
+  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 : {};
 
   /**
-   * @param {ol.render.EventType} type Event type.
-   * @param {ol.webgl.Context} context WebGL context.
-   * @param {olx.FrameState} frameState Frame state.
+   * @type {ol.source.Vector}
    * @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);
-    }
-  };
-
+  this.source_ = options.source ? options.source : null;
 
   /**
-   * @return {!ol.Transform} Matrix.
+   * @private
+   * @type {boolean}
    */
-  ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
-    return this.texCoordMatrix;
-  };
-
+  this.vertex_ = options.vertex !== undefined ? options.vertex : true;
 
   /**
-   * @return {WebGLTexture} Texture.
+   * @private
+   * @type {boolean}
    */
-  ol.renderer.webgl.Layer.prototype.getTexture = function() {
-    return this.texture;
-  };
-
+  this.edge_ = options.edge !== undefined ? options.edge : true;
 
   /**
-   * @return {!ol.Transform} Matrix.
+   * @type {ol.Collection.<ol.Feature>}
+   * @private
    */
-  ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
-    return this.projectionMatrix;
-  };
-
+  this.features_ = options.features ? options.features : null;
 
   /**
-   * Handle webglcontextlost.
+   * @type {Array.<ol.EventsKey>}
+   * @private
    */
-  ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
-    this.texture = null;
-    this.framebuffer = null;
-    this.framebufferDimension = undefined;
-  };
-
+  this.featuresListenerKeys_ = [];
 
   /**
-   * @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.
+   * @type {Object.<number, ol.EventsKey>}
+   * @private
    */
-  ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {};
-
+  this.featureChangeListenerKeys_ = {};
 
   /**
-   * @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
+   * Extents are preserved so indexed segment can be quickly removed
+   * when its feature geometry changes
+   * @type {Object.<number, ol.Extent>}
+   * @private
    */
-  ol.renderer.webgl.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {};
-
-}
-
-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');
-
-
-if (ol.ENABLE_WEBGL) {
+  this.indexedFeaturesExtents_ = {};
 
   /**
-   * @constructor
-   * @extends {ol.renderer.webgl.Layer}
-   * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
-   * @param {ol.layer.Vector} vectorLayer Vector layer.
+   * 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
    */
-  ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
-
-    ol.renderer.webgl.Layer.call(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.LayerState}
-     */
-    this.layerState_ = null;
-
-  };
-  ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
-
+  this.pendingFeatures_ = {};
 
   /**
-   * @inheritDoc
+   * Used for distance sorting in sortByDistance_
+   * @type {ol.Coordinate}
+   * @private
    */
-  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);
-    }
-
-  };
-
+  this.pixelCoordinate_ = null;
 
   /**
-   * @inheritDoc
+   * @type {number}
+   * @private
    */
-  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);
-  };
-
+  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
+    options.pixelTolerance : 10;
 
   /**
-   * @inheritDoc
+   * @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number}
+   * @private
    */
-  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);
-            }
-          });
-    }
-  };
+  this.sortByDistance_ = ol.interaction.Snap.sortByDistance.bind(this);
 
 
   /**
-   * @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);
-    }
-  };
+  * Segment RTree for each layer
+  * @type {ol.structs.RBush.<ol.SnapSegmentDataType>}
+  * @private
+  */
+  this.rBush_ = new ol.structs.RBush();
 
 
   /**
-   * @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);
-
-    if (hasFeature) {
-      return callback.call(thisArg, this.getLayer(), null);
-    } else {
-      return undefined;
-    }
+  * @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);
 
 
-  /**
-   * 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();
-  };
+/**
+ * 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
+ */
+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);
+    }
+  }
 
+  if (listen) {
+    this.featureChangeListenerKeys_[feature_uid] = ol.events.listen(
+        feature,
+        ol.events.EventType.CHANGE,
+        this.handleFeatureChange_, this);
+  }
+};
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) {
 
-    var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
-    var vectorSource = vectorLayer.getSource();
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
+  this.addFeature(feature);
+};
 
-    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();
+/**
+ * @param {ol.Feature} feature Feature.
+ * @private
+ */
+ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
+  this.removeFeature(feature);
+};
 
-    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();
+/**
+ * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} Features.
+ * @private
+ */
+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);
+};
 
-    if (vectorLayerRenderOrder === undefined) {
-      vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
-    }
 
-    var extent = ol.extent.buffer(frameStateExtent,
-        vectorLayerRenderBuffer * resolution);
+/**
+ * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
+ * @private
+ */
+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));
+};
 
-    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));
-    }
+/**
+ * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
+ * @private
+ */
+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));
+};
 
-    this.dirty_ = false;
 
-    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);
+/**
+ * @param {ol.events.Event} evt Event.
+ * @private
+ */
+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;
     }
-    replayGroup.finish(context);
-
-    this.renderedResolution_ = resolution;
-    this.renderedRevision_ = vectorLayerRevision;
-    this.renderedRenderOrder_ = vectorLayerRenderOrder;
-    this.renderedExtent_ = extent;
-    this.replayGroup_ = replayGroup;
-
-    return true;
-  };
+  } else {
+    this.updateFeature_(feature);
+  }
+};
 
 
-  /**
-   * @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;
+/**
+ * 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);
       }
-    } else {
-      loading = ol.renderer.vector.renderFeature(
-          replayGroup, feature, styles,
-          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-          this.handleStyleImageChange_, this) || loading;
+    });
+    for (i = nodesToRemove.length - 1; i >= 0; --i) {
+      rBush.remove(nodesToRemove[i]);
     }
-    return loading;
-  };
-
-}
-
-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');
+  if (unlisten) {
+    ol.events.unlistenByKey(this.featureChangeListenerKeys_[feature_uid]);
+    delete this.featureChangeListenerKeys_[feature_uid];
+  }
+};
 
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.layer.Vector = function(opt_options) {
-  var options = opt_options ?
-     opt_options : /** @type {olx.layer.VectorOptions} */ ({});
-
-  var baseOptions = ol.obj.assign({}, options);
-
-  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;
-
- /**
-  * User provided style.
-  * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
-  * @private
-  */
-  this.style_ = null;
+ol.interaction.Snap.prototype.setMap = function(map) {
+  var currentMap = this.getMap();
+  var keys = this.featuresListenerKeys_;
+  var features = this.getFeatures_();
 
- /**
-  * Style function for use within the library.
-  * @type {ol.StyleFunction|undefined}
-  * @private
-  */
-  this.styleFunction_ = undefined;
+  if (currentMap) {
+    keys.forEach(ol.events.unlistenByKey);
+    keys.length = 0;
+    features.forEach(this.forEachFeatureRemove_, this);
+  }
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
 
-  this.setStyle(options.style);
+  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);
+  }
+};
 
- /**
-  * @type {boolean}
-  * @private
-  */
-  this.updateWhileAnimating_ = options.updateWhileAnimating !== undefined ?
-     options.updateWhileAnimating : false;
 
- /**
-  * @type {boolean}
-  * @private
-  */
-  this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ?
-     options.updateWhileInteracting : false;
-};
-ol.inherits(ol.layer.Vector, ol.layer.Layer);
+/**
+ * @inheritDoc
+ */
+ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE;
 
 
 /**
- * @inheritDoc
+ * @param {ol.Pixel} pixel Pixel
+ * @param {ol.Coordinate} pixelCoordinate Coordinate
+ * @param {ol.PluggableMap} map Map.
+ * @return {ol.SnapResultType} Snap result
  */
-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);
+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;
+    });
   }
-  return renderer;
+
+  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
+  });
 };
 
 
 /**
- * @return {number|undefined} Render buffer.
+ * @param {ol.Feature} feature Feature
+ * @private
  */
-ol.layer.Vector.prototype.getRenderBuffer = function() {
-  return this.renderBuffer_;
+ol.interaction.Snap.prototype.updateFeature_ = function(feature) {
+  this.removeFeature(feature, false);
+  this.addFeature(feature, false);
 };
 
 
 /**
- * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
- *     order.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Circle} geometry Geometry.
+ * @private
  */
-ol.layer.Vector.prototype.getRenderOrder = function() {
-  return /** @type {ol.RenderOrderFunction|null|undefined} */ (
-      this.get(ol.layer.Vector.Property_.RENDER_ORDER));
+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);
+  }
 };
 
 
 /**
- * Return the associated {@link ol.source.Vector vectorsource} of the layer.
- * @function
- * @return {ol.source.Vector} Source.
- * @api
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.GeometryCollection} geometry Geometry.
+ * @private
  */
-ol.layer.Vector.prototype.getSource;
+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]);
+    }
+  }
+};
 
 
 /**
- * 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
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.LineString} geometry Geometry.
+ * @private
  */
-ol.layer.Vector.prototype.getStyle = function() {
-  return this.style_;
+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);
+  }
 };
 
 
 /**
- * Get the style function.
- * @return {ol.StyleFunction|undefined} Layer style function.
- * @api
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiLineString} geometry Geometry.
+ * @private
  */
-ol.layer.Vector.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
+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);
+    }
+  }
 };
 
 
 /**
- * @return {boolean} Whether the rendered layer should be updated while
- *     animating.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPoint} geometry Geometry.
+ * @private
  */
-ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
-  return this.updateWhileAnimating_;
+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);
+  }
 };
 
 
 /**
- * @return {boolean} Whether the rendered layer should be updated while
- *     interacting.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.MultiPolygon} geometry Geometry.
+ * @private
  */
-ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
-  return this.updateWhileInteracting_;
+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);
+      }
+    }
+  }
 };
 
 
 /**
- * @param {ol.RenderOrderFunction|null|undefined} renderOrder
- *     Render order.
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Point} geometry Geometry.
+ * @private
  */
-ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
-  this.set(ol.layer.Vector.Property_.RENDER_ORDER, renderOrder);
+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);
 };
 
 
 /**
- * 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
+ * @param {ol.Feature} feature Feature
+ * @param {ol.geom.Polygon} geometry Geometry.
+ * @private
  */
-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();
+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);
+    }
+  }
 };
 
 
 /**
- * @enum {string}
+ * Handle all pointer events events.
+ * @param {ol.MapBrowserEvent} evt A move event.
+ * @return {boolean} Pass the event to other interactions.
+ * @this {ol.interaction.Snap}
  * @private
  */
-ol.layer.Vector.Property_ = {
-  RENDER_ORDER: 'renderOrder'
+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);
 };
 
-goog.provide('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
+ * @param {ol.MapBrowserPointerEvent} evt Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Snap}
+ * @private
  */
-ol.loadingstrategy.all = function(extent, resolution) {
-  return [[-Infinity, -Infinity, Infinity, Infinity]];
+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;
 };
 
 
 /**
- * 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
+ * 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.loadingstrategy.bbox = function(extent, resolution) {
-  return [extent];
+ol.interaction.Snap.sortByDistance = function(a, b) {
+  return ol.coordinate.squaredDistanceToSegment(
+      this.pixelCoordinate_, a.segment) -
+      ol.coordinate.squaredDistanceToSegment(
+          this.pixelCoordinate_, b.segment);
 };
 
+goog.provide('ol.interaction.TranslateEventType');
+
 
 /**
- * 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
+ * @enum {string}
  */
-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;
-      });
+ol.interaction.TranslateEventType = {
+  /**
+   * Triggered upon feature translation start.
+   * @event ol.interaction.Translate.Event#translatestart
+   * @api
+   */
+  TRANSLATESTART: 'translatestart',
+  /**
+   * Triggered upon feature translation.
+   * @event ol.interaction.Translate.Event#translating
+   * @api
+   */
+  TRANSLATING: 'translating',
+  /**
+   * Triggered upon feature translation end.
+   * @event ol.interaction.Translate.Event#translateend
+   * @api
+   */
+  TRANSLATEEND: 'translateend'
 };
 
-goog.provide('ol.source.Source');
+goog.provide('ol.interaction.Translate');
 
 goog.require('ol');
-goog.require('ol.Attribution');
+goog.require('ol.Collection');
 goog.require('ol.Object');
-goog.require('ol.proj');
-goog.require('ol.source.State');
+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');
 
 
 /**
  * @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.
+ * Interaction for translating (moving) features.
  *
  * @constructor
- * @abstract
- * @extends {ol.Object}
- * @param {ol.SourceSourceOptions} options Source options.
+ * @extends {ol.interaction.Pointer}
+ * @fires ol.interaction.Translate.Event
+ * @param {olx.interaction.TranslateOptions=} opt_options Options.
  * @api
  */
-ol.source.Source = function(options) {
+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_
+  });
 
-  ol.Object.call(this);
+  var options = opt_options ? opt_options : {};
 
   /**
+   * The last position we translated to.
+   * @type {ol.Coordinate}
    * @private
-   * @type {ol.proj.Projection}
    */
-  this.projection_ = ol.proj.get(options.projection);
+  this.lastCoordinate_ = null;
+
 
   /**
+   * @type {ol.Collection.<ol.Feature>}
    * @private
-   * @type {Array.<ol.Attribution>}
    */
-  this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions);
+  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;
+  }
 
   /**
    * @private
-   * @type {string|olx.LogoOptions|undefined}
+   * @type {function(ol.layer.Layer): boolean}
    */
-  this.logo_ = options.logo;
+  this.layerFilter_ = layerFilter;
 
   /**
    * @private
-   * @type {ol.source.State}
+   * @type {number}
    */
-  this.state_ = options.state !== undefined ?
-      options.state : ol.source.State.READY;
+  this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0;
 
   /**
+   * @type {ol.Feature}
    * @private
-   * @type {boolean}
    */
-  this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false;
+  this.lastFeature_ = null;
+
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.interaction.Property.ACTIVE),
+      this.handleActiveChanged_, this);
 
 };
-ol.inherits(ol.source.Source, ol.Object);
+ol.inherits(ol.interaction.Translate, ol.interaction.Pointer);
+
 
 /**
- * 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.
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Start drag sequence?
+ * @this {ol.interaction.Translate}
+ * @private
  */
-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 attributions;
-  } else {
-    return null;
+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_]);
+
+    this.dispatchEvent(
+        new ol.interaction.Translate.Event(
+            ol.interaction.TranslateEventType.TRANSLATESTART, features,
+            event.coordinate));
+    return true;
   }
+  return false;
 };
 
 
 /**
- * @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
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @return {boolean} Stop drag sequence?
+ * @this {ol.interaction.Translate}
+ * @private
  */
-ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
+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_]);
 
-/**
- * Get the attributions of the source.
- * @return {Array.<ol.Attribution>} Attributions.
- * @api
- */
-ol.source.Source.prototype.getAttributions = function() {
-  return this.attributions_;
+    this.dispatchEvent(
+        new ol.interaction.Translate.Event(
+            ol.interaction.TranslateEventType.TRANSLATEEND, features,
+            event.coordinate));
+    return true;
+  }
+  return false;
 };
 
 
 /**
- * Get the logo of the source.
- * @return {string|olx.LogoOptions|undefined} Logo.
- * @api
+ * @param {ol.MapBrowserPointerEvent} event Event.
+ * @this {ol.interaction.Translate}
+ * @private
  */
-ol.source.Source.prototype.getLogo = function() {
-  return this.logo_;
-};
+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_]);
 
-/**
- * Get the projection of the source.
- * @return {ol.proj.Projection} Projection.
- * @api
- */
-ol.source.Source.prototype.getProjection = function() {
-  return this.projection_;
+    features.forEach(function(feature) {
+      var geom = feature.getGeometry();
+      geom.translate(deltaX, deltaY);
+      feature.setGeometry(geom);
+    });
+
+    this.lastCoordinate_ = newCoordinate;
+    this.dispatchEvent(
+        new ol.interaction.Translate.Event(
+            ol.interaction.TranslateEventType.TRANSLATING, features,
+            newCoordinate));
+  }
 };
 
 
 /**
- * @abstract
- * @return {Array.<number>|undefined} Resolutions.
+ * @param {ol.MapBrowserEvent} event Event.
+ * @this {ol.interaction.Translate}
+ * @private
  */
-ol.source.Source.prototype.getResolutions = function() {};
+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');
+  }
+};
 
 
 /**
- * Get the state of the source, see {@link ol.source.State} for possible states.
- * @return {ol.source.State} State.
- * @api
+ * 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.PluggableMap} map Map to test the intersection on.
+ * @return {ol.Feature} Returns the feature found at the specified pixel
+ * coordinates.
+ * @private
  */
-ol.source.Source.prototype.getState = function() {
-  return this.state_;
+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_
+      });
 };
 
 
 /**
- * @return {boolean|undefined} Wrap X.
+ * Returns the Hit-detection tolerance.
+ * @returns {number} Hit tolerance in pixels.
+ * @api
  */
-ol.source.Source.prototype.getWrapX = function() {
-  return this.wrapX_;
+ol.interaction.Translate.prototype.getHitTolerance = function() {
+  return this.hitTolerance_;
 };
 
 
 /**
- * Refreshes the source and finally dispatches a 'change' event.
+ * 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
  */
-ol.source.Source.prototype.refresh = function() {
-  this.changed();
+ol.interaction.Translate.prototype.setHitTolerance = function(hitTolerance) {
+  this.hitTolerance_ = hitTolerance;
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.source.Source.prototype.setAttributions = function(attributions) {
-  this.attributions_ = ol.source.Source.toAttributionsArray_(attributions);
-  this.changed();
+ol.interaction.Translate.prototype.setMap = function(map) {
+  var oldMap = this.getMap();
+  ol.interaction.Pointer.prototype.setMap.call(this, map);
+  this.updateState_(oldMap);
 };
 
 
 /**
- * Set the logo of the source.
- * @param {string|olx.LogoOptions|undefined} logo Logo.
+ * @private
  */
-ol.source.Source.prototype.setLogo = function(logo) {
-  this.logo_ = logo;
+ol.interaction.Translate.prototype.handleActiveChanged_ = function() {
+  this.updateState_(null);
 };
 
 
 /**
- * Set the state of the source.
- * @param {ol.source.State} state State.
- * @protected
+ * @param {ol.PluggableMap} oldMap Old map.
+ * @private
  */
-ol.source.Source.prototype.setState = function(state) {
-  this.state_ = state;
-  this.changed();
+ol.interaction.Translate.prototype.updateState_ = function(oldMap) {
+  var map = this.getMap();
+  var active = this.getActive();
+  if (!map || !active) {
+    map = map || oldMap;
+    if (map) {
+      var elem = map.getViewport();
+      elem.classList.remove('ol-grab', 'ol-grabbing');
+    }
+  }
 };
 
-goog.provide('ol.source.VectorEventType');
 
 /**
- * @enum {string}
+ * @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.
  */
-ol.source.VectorEventType = {
-  /**
-   * Triggered when a feature is added to the source.
-   * @event ol.source.Vector.Event#addfeature
-   * @api
-   */
-  ADDFEATURE: 'addfeature',
+ol.interaction.Translate.Event = function(type, features, coordinate) {
 
-  /**
-   * Triggered when a feature is updated.
-   * @event ol.source.Vector.Event#changefeature
-   * @api
-   */
-  CHANGEFEATURE: 'changefeature',
+  ol.events.Event.call(this, type);
 
   /**
-   * Triggered when the clear method is called on the source.
-   * @event ol.source.Vector.Event#clear
+   * The features being translated.
+   * @type {ol.Collection.<ol.Feature>}
    * @api
    */
-  CLEAR: 'clear',
+  this.features = features;
 
   /**
-   * 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
+   * The coordinate of the drag event.
+   * @const
+   * @type {ol.Coordinate}
    * @api
    */
-  REMOVEFEATURE: 'removefeature'
+  this.coordinate = coordinate;
 };
+ol.inherits(ol.interaction.Translate.Event, ol.events.Event);
 
-// FIXME bulk feature upload - suppress events
-// FIXME make change-detection more refined (notably, geometry hint)
-
-goog.provide('ol.source.Vector');
+goog.provide('ol.layer.Heatmap');
 
-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');
+goog.require('ol.Object');
+goog.require('ol.dom');
+goog.require('ol.layer.Vector');
+goog.require('ol.math');
 goog.require('ol.obj');
-goog.require('ol.source.Source');
-goog.require('ol.source.State');
-goog.require('ol.source.VectorEventType');
-goog.require('ol.structs.RBush');
+goog.require('ol.render.EventType');
+goog.require('ol.style.Icon');
+goog.require('ol.style.Style');
 
 
 /**
  * @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.
+ * 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.source.Source}
- * @fires ol.source.Vector.Event
- * @param {olx.source.VectorOptions=} opt_options Vector source options.
+ * @extends {ol.layer.Vector}
+ * @fires ol.render.Event
+ * @param {olx.layer.HeatmapOptions=} opt_options Options.
  * @api
  */
-ol.source.Vector = function(opt_options) {
-
-  var options = opt_options || {};
-
-  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;
-
-  /**
-   * @private
-   * @type {ol.format.Feature|undefined}
-   */
-  this.format_ = options.format;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;
-
-  /**
-   * @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_));
-  }
-
-  /**
-   * @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;
+ol.layer.Heatmap = function(opt_options) {
+  var options = opt_options ? opt_options : {};
 
-  /**
-   * @private
-   * @type {ol.structs.RBush.<{extent: ol.Extent}>}
-   */
-  this.loadedExtentsRtree_ = new ol.structs.RBush();
+  var baseOptions = ol.obj.assign({}, options);
 
-  /**
-   * @private
-   * @type {Object.<string, ol.Feature>}
-   */
-  this.nullGeometryFeatures_ = {};
+  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));
 
   /**
-   * A lookup of features by id (the return from feature.getId()).
    * @private
-   * @type {Object.<string, ol.Feature>}
+   * @type {Uint8ClampedArray}
    */
-  this.idIndex_ = {};
+  this.gradient_ = null;
 
   /**
-   * A lookup of features without id (keyed by ol.getUid(feature)).
    * @private
-   * @type {Object.<string, ol.Feature>}
+   * @type {number}
    */
-  this.undefIdIndex_ = {};
+  this.shadow_ = options.shadow !== undefined ? options.shadow : 250;
 
   /**
    * @private
-   * @type {Object.<string, Array.<ol.EventsKey>>}
+   * @type {string|undefined}
    */
-  this.featureChangeKeys_ = {};
+  this.circleImage_ = undefined;
 
   /**
    * @private
-   * @type {ol.Collection.<ol.Feature>}
+   * @type {Array.<Array.<ol.style.Style>>}
    */
-  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);
+  this.styleCache_ = null;
 
+  ol.events.listen(this,
+      ol.Object.getChangeEventType(ol.layer.Heatmap.Property_.GRADIENT),
+      this.handleGradientChanged_, this);
 
-/**
- * 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.source.Vector.prototype.addFeature = function(feature) {
-  this.addFeatureInternal(feature);
-  this.changed();
-};
+  this.setGradient(options.gradient ?
+    options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
 
+  this.setBlur(options.blur !== undefined ? options.blur : 15);
 
-/**
- * Add a feature without firing a `change` event.
- * @param {ol.Feature} feature Feature.
- * @protected
- */
-ol.source.Vector.prototype.addFeatureInternal = function(feature) {
-  var featureKey = ol.getUid(feature).toString();
+  this.setRadius(options.radius !== undefined ? options.radius : 8);
 
-  if (!this.addToIndex_(featureKey, feature)) {
-    return;
-  }
+  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.setupChangeEvents_(featureKey, feature);
+  this.handleStyleChanged_();
 
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    var extent = geometry.getExtent();
-    if (this.featuresRtree_) {
-      this.featuresRtree_.insert(extent, feature);
-    }
+  var weight = options.weight ? options.weight : 'weight';
+  var weightFunction;
+  if (typeof weight === 'string') {
+    weightFunction = function(feature) {
+      return feature.get(weight);
+    };
   } else {
-    this.nullGeometryFeatures_[featureKey] = feature;
+    weightFunction = weight;
   }
 
-  this.dispatchEvent(
-      new ol.source.Vector.Event(ol.source.VectorEventType.ADDFEATURE, feature));
-};
-
-
-/**
- * @param {string} featureKey Unique identifier for the feature.
- * @param {ol.Feature} feature The feature.
- * @private
- */
-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)
-  ];
-};
+  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 {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.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;
+  ol.events.listen(this, ol.render.EventType.RENDER, this.handleRender_, this);
 };
+ol.inherits(ol.layer.Heatmap, ol.layer.Vector);
 
 
 /**
- * Add a batch of features to the source.
- * @param {Array.<ol.Feature>} features Features to add.
- * @api
+ * @const
+ * @type {Array.<string>}
  */
-ol.source.Vector.prototype.addFeatures = function(features) {
-  this.addFeaturesInternal(features);
-  this.changed();
-};
+ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
 
 
 /**
- * Add features without firing a `change` event.
- * @param {Array.<ol.Feature>} features Features.
- * @protected
+ * @param {Array.<string>} colors A list of colored.
+ * @return {Uint8ClampedArray} An array.
+ * @private
  */
-ol.source.Vector.prototype.addFeaturesInternal = function(features) {
-  var featureKey, i, length, feature;
-
-  var extents = [];
-  var newFeatures = [];
-  var geometryFeatures = [];
+ol.layer.Heatmap.createGradient_ = function(colors) {
+  var width = 1;
+  var height = 256;
+  var context = ol.dom.createCanvasContext2D(width, height);
 
-  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);
-    }
+  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]);
   }
 
-  for (i = 0, length = newFeatures.length; i < length; i++) {
-    feature = newFeatures[i];
-    featureKey = ol.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);
-  }
+  context.fillStyle = gradient;
+  context.fillRect(0, 0, width, height);
 
-  for (i = 0, length = newFeatures.length; i < length; i++) {
-    this.dispatchEvent(new ol.source.Vector.Event(
-        ol.source.VectorEventType.ADDFEATURE, newFeatures[i]));
-  }
+  return context.getImageData(0, 0, width, height).data;
 };
 
 
 /**
- * @param {!ol.Collection.<ol.Feature>} collection Collection.
+ * @return {string} Data URL for a circle.
  * @private
  */
-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;
+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();
 };
 
 
 /**
- * Remove all features from the source.
- * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events.
+ * Return the blur size in pixels.
+ * @return {number} Blur size in pixels.
  * @api
+ * @observable
  */
-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();
-  }
+ol.layer.Heatmap.prototype.getBlur = function() {
+  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property_.BLUR));
+};
 
-  if (this.featuresRtree_) {
-    this.featuresRtree_.clear();
-  }
-  this.loadedExtentsRtree_.clear();
-  this.nullGeometryFeatures_ = {};
 
-  var clearEvent = new ol.source.Vector.Event(ol.source.VectorEventType.CLEAR);
-  this.dispatchEvent(clearEvent);
-  this.changed();
+/**
+ * Return the gradient colors as array of strings.
+ * @return {Array.<string>} Colors.
+ * @api
+ * @observable
+ */
+ol.layer.Heatmap.prototype.getGradient = function() {
+  return /** @type {Array.<string>} */ (
+    this.get(ol.layer.Heatmap.Property_.GRADIENT));
 };
 
 
 /**
- * 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
+ * Return the size of the radius in pixels.
+ * @return {number} Radius size in pixel.
  * @api
+ * @observable
  */
-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);
-  }
+ol.layer.Heatmap.prototype.getRadius = function() {
+  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property_.RADIUS));
 };
 
 
 /**
- * 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
+ * @private
  */
-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;
-    }
-  });
+ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
+  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
 };
 
 
 /**
- * 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
+ * @private
  */
-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);
+ol.layer.Heatmap.prototype.handleStyleChanged_ = function() {
+  this.circleImage_ = this.createCircle_();
+  this.styleCache_ = new Array(256);
+  this.changed();
+};
+
+
+/**
+ * @param {ol.render.Event} event Post compose event
+ * @private
+ */
+ol.layer.Heatmap.prototype.handleRender_ = function(event) {
+  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);
 };
 
 
 /**
- * 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
+ * Set the blur size in pixels.
+ * @param {number} blur Blur size in pixels.
  * @api
+ * @observable
  */
-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;
-          }
-        }
-      });
+ol.layer.Heatmap.prototype.setBlur = function(blur) {
+  this.set(ol.layer.Heatmap.Property_.BLUR, blur);
 };
 
 
 /**
- * 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.
+ * Set the gradient colors as array of strings.
+ * @param {Array.<string>} colors Gradient.
  * @api
+ * @observable
  */
-ol.source.Vector.prototype.getFeaturesCollection = function() {
-  return this.featuresCollection_;
+ol.layer.Heatmap.prototype.setGradient = function(colors) {
+  this.set(ol.layer.Heatmap.Property_.GRADIENT, colors);
 };
 
 
 /**
- * Get all features on the source in random order.
- * @return {Array.<ol.Feature>} Features.
+ * Set the size of the radius in pixels.
+ * @param {number} radius Radius size in pixel.
  * @api
+ * @observable
  */
-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);
+ol.layer.Heatmap.prototype.setRadius = function(radius) {
+  this.set(ol.layer.Heatmap.Property_.RADIUS, radius);
 };
 
 
 /**
- * Get all features whose geometry intersects the provided coordinate.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {Array.<ol.Feature>} Features.
- * @api
+ * @enum {string}
+ * @private
  */
-ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
-  var features = [];
-  this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) {
-    features.push(feature);
-  });
-  return features;
+ol.layer.Heatmap.Property_ = {
+  BLUR: 'blur',
+  GRADIENT: 'gradient',
+  RADIUS: 'radius'
 };
 
+goog.provide('ol.layer.Image');
+
+goog.require('ol');
+goog.require('ol.LayerType');
+goog.require('ol.layer.Layer');
+
 
 /**
- * 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).
+ * @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.
  *
- * 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.
+ * @constructor
+ * @extends {ol.layer.Layer}
+ * @fires ol.render.Event
+ * @param {olx.layer.ImageOptions=} opt_options Layer options.
  * @api
  */
-ol.source.Vector.prototype.getFeaturesInExtent = function(extent) {
-  return this.featuresRtree_.getInExtent(extent);
+ol.layer.Image = function(opt_options) {
+  var options = opt_options ? opt_options : {};
+  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (options));
+
+  /**
+   * The layer type.
+   * @protected
+   * @type {ol.LayerType}
+   */
+  this.type = ol.LayerType.IMAGE;
+
 };
+ol.inherits(ol.layer.Image, ol.layer.Layer);
 
 
 /**
- * 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.
+ * Return the associated {@link ol.source.Image source} of the image layer.
+ * @function
+ * @return {ol.source.Image} Source.
  * @api
  */
-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;
+ol.layer.Image.prototype.getSource;
+
+goog.provide('ol.layer.TileProperty');
+
+/**
+ * @enum {string}
+ */
+ol.layer.TileProperty = {
+  PRELOAD: 'preload',
+  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
 };
 
+goog.provide('ol.layer.Tile');
+
+goog.require('ol');
+goog.require('ol.LayerType');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.TileProperty');
+goog.require('ol.obj');
+
 
 /**
- * Get the extent of the features currently in the source.
+ * @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.
  *
- * 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.
+ * @constructor
+ * @extends {ol.layer.Layer}
+ * @fires ol.render.Event
+ * @param {olx.layer.TileOptions=} opt_options Tile layer options.
  * @api
  */
-ol.source.Vector.prototype.getExtent = function(opt_extent) {
-  return this.featuresRtree_.getExtent(opt_extent);
+ol.layer.Tile = function(opt_options) {
+  var options = opt_options ? opt_options : {};
+
+  var baseOptions = ol.obj.assign({}, options);
+
+  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);
+
+  /**
+   * The layer type.
+   * @protected
+   * @type {ol.LayerType}
+   */
+  this.type = ol.LayerType.TILE;
+
 };
+ol.inherits(ol.layer.Tile, ol.layer.Layer);
 
 
 /**
- * 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).
+ * 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.source.Vector.prototype.getFeatureById = function(id) {
-  var feature = this.idIndex_[id.toString()];
-  return feature !== undefined ? feature : null;
+ol.layer.Tile.prototype.getPreload = function() {
+  return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
 };
 
 
 /**
- * Get the format associated with this source.
- *
- * @return {ol.format.Feature|undefined} The feature format.
+ * Return the associated {@link ol.source.Tile tilesource} of the layer.
+ * @function
+ * @return {ol.source.Tile} Source.
  * @api
  */
-ol.source.Vector.prototype.getFormat = function() {
-  return this.format_;
+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);
 };
 
 
 /**
- * @return {boolean} The source can have overlapping geometries.
+ * Whether we use interim tiles on error.
+ * @return {boolean} Use interim tiles on error.
+ * @observable
+ * @api
  */
-ol.source.Vector.prototype.getOverlaps = function() {
-  return this.overlaps_;
+ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
+  return /** @type {boolean} */ (
+    this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
 };
 
 
 /**
- * @override
+ * Set whether we use interim tiles on error.
+ * @param {boolean} useInterimTilesOnError Use interim tiles on error.
+ * @observable
+ * @api
  */
-ol.source.Vector.prototype.getResolutions = function() {};
+ol.layer.Tile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
+  this.set(
+      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
+};
+
+goog.provide('ol.layer.VectorTile');
+
+goog.require('ol');
+goog.require('ol.LayerType');
+goog.require('ol.asserts');
+goog.require('ol.layer.TileProperty');
+goog.require('ol.layer.Vector');
+goog.require('ol.layer.VectorTileRenderType');
+goog.require('ol.obj');
 
 
 /**
- * Get the url associated with this source.
+ * @classdesc
+ * Layer for vector tile data that is rendered client-side.
+ * Note that any property set in the options is set as a {@link ol.Object}
+ * property on the layer object; for example, setting `title: 'My Title'` in the
+ * options means that `title` is observable, and has get/set accessors.
  *
- * @return {string|ol.FeatureUrlFunction|undefined} The url.
+ * @constructor
+ * @extends {ol.layer.Vector}
+ * @param {olx.layer.VectorTileOptions=} opt_options Options.
  * @api
  */
-ol.source.Vector.prototype.getUrl = function() {
-  return this.url_;
+ol.layer.VectorTile = function(opt_options) {
+  var options = opt_options ? opt_options : {};
+
+  var renderMode = options.renderMode || ol.layer.VectorTileRenderType.HYBRID;
+  ol.asserts.assert(renderMode == undefined ||
+      renderMode == ol.layer.VectorTileRenderType.IMAGE ||
+      renderMode == ol.layer.VectorTileRenderType.HYBRID ||
+      renderMode == ol.layer.VectorTileRenderType.VECTOR,
+  28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`
+  if (options.declutter && renderMode == ol.layer.VectorTileRenderType.IMAGE) {
+    renderMode = ol.layer.VectorTileRenderType.HYBRID;
+  }
+  options.renderMode = renderMode;
+
+  var baseOptions = ol.obj.assign({}, options);
+
+  delete baseOptions.preload;
+  delete baseOptions.useInterimTilesOnError;
+  ol.layer.Vector.call(this,  /** @type {olx.layer.VectorOptions} */ (baseOptions));
+
+  this.setPreload(options.preload ? options.preload : 0);
+  this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
+    options.useInterimTilesOnError : true);
+
+  /**
+   * The layer type.
+   * @protected
+   * @type {ol.LayerType}
+   */
+  this.type = ol.LayerType.VECTOR_TILE;
+
 };
+ol.inherits(ol.layer.VectorTile, ol.layer.Vector);
 
 
 /**
- * @param {ol.events.Event} event 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.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);
-      }
-    }
-  }
-  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;
-    }
-  }
-  this.changed();
-  this.dispatchEvent(new ol.source.Vector.Event(
-      ol.source.VectorEventType.CHANGEFEATURE, feature));
+ol.layer.VectorTile.prototype.getPreload = function() {
+  return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
+};
+
+
+/**
+ * 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));
 };
 
 
 /**
- * @return {boolean} Is empty.
+ * 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.source.Vector.prototype.isEmpty = function() {
-  return this.featuresRtree_.isEmpty() &&
-      ol.obj.isEmpty(this.nullGeometryFeatures_);
+ol.layer.VectorTile.prototype.setPreload = function(preload) {
+  this.set(ol.layer.TileProperty.PRELOAD, preload);
 };
 
 
 /**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {ol.proj.Projection} projection Projection.
+ * Set whether we use interim tiles on error.
+ * @param {boolean} useInterimTilesOnError Use interim tiles on error.
+ * @observable
+ * @api
  */
-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()});
-    }
-  }
+ol.layer.VectorTile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
+  this.set(
+      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
 };
 
 
 /**
- * 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.
+ * Return the associated {@link ol.source.VectorTile vectortilesource} of the layer.
+ * @function
+ * @return {ol.source.VectorTile} Source.
  * @api
  */
-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);
-    }
-  }
-  this.removeFeatureInternal(feature);
-  this.changed();
-};
+ol.layer.VectorTile.prototype.getSource;
+
+goog.provide('ol.webgl.Shader');
+
+goog.require('ol.functions');
 
 
 /**
- * Remove feature without firing a `change` event.
- * @param {ol.Feature} feature Feature.
- * @protected
+ * @constructor
+ * @abstract
+ * @param {string} source Source.
+ * @struct
  */
-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));
+ol.webgl.Shader = function(source) {
+
+  /**
+   * @private
+   * @type {string}
+   */
+  this.source_ = source;
+
 };
 
 
 /**
- * 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
+ * @abstract
+ * @return {number} Type.
  */
-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;
+ol.webgl.Shader.prototype.getType = function() {};
+
+
+/**
+ * @return {string} Source.
+ */
+ol.webgl.Shader.prototype.getSource = function() {
+  return this.source_;
 };
 
 
 /**
- * @classdesc
- * Events emitted by {@link ol.source.Vector} instances are instances of this
- * type.
- *
+ * @return {boolean} Is animated?
+ */
+ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE;
+
+goog.provide('ol.webgl.Fragment');
+
+goog.require('ol');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Shader');
+
+
+/**
  * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.source.Vector.Event}
- * @param {string} type Type.
- * @param {ol.Feature=} opt_feature Feature.
+ * @extends {ol.webgl.Shader}
+ * @param {string} source Source.
+ * @struct
  */
-ol.source.Vector.Event = function(type, opt_feature) {
+ol.webgl.Fragment = function(source) {
+  ol.webgl.Shader.call(this, source);
+};
+ol.inherits(ol.webgl.Fragment, ol.webgl.Shader);
 
-  ol.events.Event.call(this, type);
 
-  /**
-   * The feature being added or removed.
-   * @type {ol.Feature|undefined}
-   * @api
-   */
-  this.feature = opt_feature;
+/**
+ * @inheritDoc
+ */
+ol.webgl.Fragment.prototype.getType = function() {
+  return ol.webgl.FRAGMENT_SHADER;
+};
+
+goog.provide('ol.webgl.Vertex');
+
+goog.require('ol');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Shader');
 
+
+/**
+ * @constructor
+ * @extends {ol.webgl.Shader}
+ * @param {string} source Source.
+ * @struct
+ */
+ol.webgl.Vertex = function(source) {
+  ol.webgl.Shader.call(this, source);
 };
-ol.inherits(ol.source.Vector.Event, ol.events.Event);
+ol.inherits(ol.webgl.Vertex, ol.webgl.Shader);
 
-goog.provide('ol.interaction.Draw');
+
+/**
+ * @inheritDoc
+ */
+ol.webgl.Vertex.prototype.getType = function() {
+  return ol.webgl.VERTEX_SHADER;
+};
+
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.circlereplay.defaultshader');
+
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
+
+
+ol.render.webgl.circlereplay.defaultshader.fragment = new ol.webgl.Fragment(ol.DEBUG_WEBGL ?
+  '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' :
+  '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;}}');
+
+ol.render.webgl.circlereplay.defaultshader.vertex = new ol.webgl.Vertex(ol.DEBUG_WEBGL ?
+  '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' :
+  '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;//Until we get gl_VertexID in WebGL,we store an instruction.if(f==0.0){//Offsetting the edges of the triangle by lineWidth/2 is necessary,however//we should also leave some space for the antialiasing,thus we offset by lineWidth.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);}}');
+
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.circlereplay.defaultshader.Locations');
 
 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');
 
 
 /**
- * @classdesc
- * Interaction for drawing feature geometries.
- *
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.interaction.Draw.Event
- * @param {olx.interaction.DrawOptions} options Options.
- * @api
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-ol.interaction.Draw = function(options) {
+ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) {
 
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.Draw.handleDownEvent_,
-    handleEvent: ol.interaction.Draw.handleEvent,
-    handleUpEvent: ol.interaction.Draw.handleUpEvent_
-  });
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h');
 
   /**
-   * @type {boolean}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.shouldHandle_ = false;
+  this.u_offsetScaleMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i');
 
   /**
-   * @type {ol.Pixel}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.downPx_ = null;
+  this.u_offsetRotateMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
 
   /**
-   * @type {boolean}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.freehand_ = false;
+  this.u_lineWidth = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k');
 
   /**
-   * Target source for drawn features.
-   * @type {ol.source.Vector}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.source_ = options.source ? options.source : null;
+  this.u_pixelRatio = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'l');
 
   /**
-   * Target collection for drawn features.
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.features_ = options.features ? options.features : null;
+  this.u_opacity = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm');
 
   /**
-   * Pixel distance for snapping.
-   * @type {number}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12;
+  this.u_fillColor = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_fillColor' : 'n');
 
   /**
-   * Geometry type.
-   * @type {ol.geom.GeometryType}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.type_ = options.type;
+  this.u_strokeColor = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_strokeColor' : 'o');
 
   /**
-   * Drawing mode (derived from geometry type.
-   * @type {ol.interaction.Draw.Mode_}
-   * @private
+   * @type {WebGLUniformLocation}
    */
-  this.mode_ = ol.interaction.Draw.getMode_(this.type_);
+  this.u_size = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_size' : 'p');
 
   /**
-   * 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);
+  this.a_position = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_position' : 'e');
 
   /**
-   * 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;
+  this.a_instruction = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_instruction' : 'f');
 
   /**
-   * A function to decide if a potential finish coordinate is permissible
-   * @private
-   * @type {ol.EventsConditionType}
+   * @type {number}
    */
-  this.finishCondition_ = options.finishCondition ? options.finishCondition : ol.functions.TRUE;
+  this.a_radius = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_radius' : 'g');
+};
 
-  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.vec.Mat4');
+
+
+/**
+ * @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];
+};
+
+
+/**
+ * @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');
+
+
+/**
+ * @constructor
+ * @abstract
+ * @extends {ol.render.VectorContext}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @struct
+ */
+ol.render.webgl.Replay = function(tolerance, maxExtent) {
+  ol.render.VectorContext.call(this);
 
   /**
-   * @type {ol.DrawGeometryFunctionType}
-   * @private
+   * @protected
+   * @type {number}
    */
-  this.geometryFunction_ = geometryFunction;
+  this.tolerance = tolerance;
 
   /**
-   * Finish coordinate for the feature (first point for polygons, last point for
-   * linestrings).
+   * @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}
-   * @private
    */
-  this.finishCoordinate_ = null;
+  this.origin = ol.extent.getCenter(maxExtent);
 
   /**
-   * Sketch feature.
-   * @type {ol.Feature}
    * @private
+   * @type {ol.Transform}
    */
-  this.sketchFeature_ = null;
+  this.projectionMatrix_ = ol.transform.create();
 
   /**
-   * Sketch point.
-   * @type {ol.Feature}
    * @private
+   * @type {ol.Transform}
    */
-  this.sketchPoint_ = null;
+  this.offsetRotateMatrix_ = ol.transform.create();
 
   /**
-   * Sketch coordinates. Used when drawing a line or polygon.
-   * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>}
    * @private
+   * @type {ol.Transform}
    */
-  this.sketchCoords_ = null;
+  this.offsetScaleMatrix_ = ol.transform.create();
 
   /**
-   * Sketch line. Used when drawing polygon.
-   * @type {ol.Feature}
    * @private
+   * @type {Array.<number>}
    */
-  this.sketchLine_ = null;
+  this.tmpMat4_ = ol.vec.Mat4.create();
 
   /**
-   * Sketch line coordinates. Used when drawing a polygon or circle.
-   * @type {Array.<ol.Coordinate>}
-   * @private
+   * @protected
+   * @type {Array.<number>}
    */
-  this.sketchLineCoords_ = null;
+  this.indices = [];
 
   /**
-   * 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
+   * @protected
+   * @type {?ol.webgl.Buffer}
    */
-  this.squaredClickTolerance_ = options.clickTolerance ?
-      options.clickTolerance * options.clickTolerance : 36;
+  this.indicesBuffer = null;
 
   /**
-   * Draw overlay where our sketch features are drawn.
-   * @type {ol.layer.Vector}
-   * @private
+   * Start index per feature (the index).
+   * @protected
+   * @type {Array.<number>}
    */
-  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.startIndices = [];
 
   /**
-   * Name of the geometry attribute for newly created features.
-   * @type {string|undefined}
-   * @private
+   * Start index per feature (the feature).
+   * @protected
+   * @type {Array.<ol.Feature|ol.render.Feature>}
    */
-  this.geometryName_ = options.geometryName;
+  this.startIndicesFeature = [];
 
   /**
-   * @private
-   * @type {ol.EventsConditionType}
+   * @protected
+   * @type {Array.<number>}
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.noModifierKeys;
+  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);
+
+
+/**
+ * @abstract
+ * @param {ol.webgl.Context} context WebGL context.
+ * @return {function()} Delete resources function.
+ */
+ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = function(context) {};
+
+
+/**
+ * @abstract
+ * @param {ol.webgl.Context} context Context.
+ */
+ol.render.webgl.Replay.prototype.finish = function(context) {};
+
+
+/**
+ * @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.linestringreplay.defaultshader.Locations|
+            ol.render.webgl.polygonreplay.defaultshader.Locations|
+            ol.render.webgl.texturereplay.defaultshader.Locations} Locations.
+ */
+ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {};
+
+
+/**
+ * @abstract
+ * @protected
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.render.webgl.circlereplay.defaultshader.Locations|
+           ol.render.webgl.linestringreplay.defaultshader.Locations|
+           ol.render.webgl.polygonreplay.defaultshader.Locations|
+           ol.render.webgl.texturereplay.defaultshader.Locations} locations Locations.
+ */
+ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {};
+
+
+/**
+ * @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) {};
+
+
+/**
+ * @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) {};
+
+
+/**
+ * @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);
+  }
+};
+
+
+/**
+ * @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;
+  }
+};
+
+
+/**
+ * @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);
+
+    gl.stencilMask(0);
+    gl.stencilFunc(gl.NOTEQUAL, 1, 255);
+  }
+
+  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer);
+
+  context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
+
+  var locations = this.setUpProgram(gl, context, size, pixelRatio);
+
+  // 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]);
+
+  var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_);
+  if (rotation !== 0) {
+    ol.transform.rotate(offsetRotateMatrix, -rotation);
+  }
 
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.freehandCondition_;
-  if (options.freehand) {
-    this.freehandCondition_ = ol.events.condition.always;
+  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);
+
+  // draw!
+  var result;
+  if (featureCallback === undefined) {
+    this.drawReplay(gl, context, skippedFeaturesHash, false);
   } else {
-    this.freehandCondition_ = options.freehandCondition ?
-        options.freehandCondition : ol.events.condition.shiftKeyOnly;
+    // draw feature by feature for the hit-detection
+    result = this.drawHitDetectionReplay(gl, context, skippedFeaturesHash,
+        featureCallback, oneByOne, opt_hitExtent);
   }
 
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.interaction.Property.ACTIVE),
-      this.updateState_, this);
+  // disable the vertex attrib arrays
+  this.shutDownProgram(gl, locations);
 
-};
-ol.inherits(ol.interaction.Draw, ol.interaction.Pointer);
+  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;
+};
 
 /**
- * @return {ol.StyleFunction} Styles.
+ * @protected
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {number} start Start index.
+ * @param {number} end End index.
  */
-ol.interaction.Draw.getDefaultStyleFunction = function() {
-  var styles = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return styles[feature.getGeometry().getType()];
-  };
+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);
 };
 
+goog.provide('ol.render.webgl');
+
 
 /**
- * @inheritDoc
+ * @const
+ * @type {string}
  */
-ol.interaction.Draw.prototype.setMap = function(map) {
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
-  this.updateState_();
-};
+ol.render.webgl.defaultFont = '10px sans-serif';
 
 
 /**
- * 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
+ * @const
+ * @type {ol.Color}
  */
-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;
-};
+ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0];
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Draw}
- * @private
+ * @const
+ * @type {string}
  */
-ol.interaction.Draw.handleDownEvent_ = function(event) {
-  this.shouldHandle_ = !this.freehand_;
+ol.render.webgl.defaultLineCap = 'round';
 
-  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;
-  }
-};
+
+/**
+ * @const
+ * @type {Array.<number>}
+ */
+ol.render.webgl.defaultLineDash = [];
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Draw}
- * @private
+ * @const
+ * @type {number}
  */
-ol.interaction.Draw.handleUpEvent_ = function(event) {
-  var pass = true;
+ol.render.webgl.defaultLineDashOffset = 0;
 
-  this.handlePointerMove_(event);
 
-  var circleMode = this.mode_ === ol.interaction.Draw.Mode_.CIRCLE;
+/**
+ * @const
+ * @type {string}
+ */
+ol.render.webgl.defaultLineJoin = 'round';
 
-  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_();
-  }
-  return pass;
-};
 
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.webgl.defaultMiterLimit = 10;
 
 /**
- * Handle move events.
- * @param {ol.MapBrowserEvent} event A move event.
- * @return {boolean} Pass the event to other interactions.
- * @private
+ * @const
+ * @type {ol.Color}
  */
-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_;
-  }
+ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0];
 
-  if (this.finishCoordinate_) {
-    this.modifyDrawing_(event);
-  } else {
-    this.createOrUpdateSketchPoint_(event);
-  }
-  return true;
-};
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.webgl.defaultTextAlign = 0.5;
 
 
 /**
- * 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
+ * @const
+ * @type {number}
  */
-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;
+ol.render.webgl.defaultTextBaseline = 0.5;
+
+
+/**
+ * @const
+ * @type {number}
+ */
+ol.render.webgl.defaultLineWidth = 1;
+
+/**
+ * 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;
+
+goog.provide('ol.webgl.Buffer');
+
+goog.require('ol.webgl');
+
 
 /**
- * @param {ol.MapBrowserEvent} event Event.
- * @private
+ * @constructor
+ * @param {Array.<number>=} opt_arr Array.
+ * @param {number=} opt_usage Usage.
+ * @struct
  */
-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);
-  }
+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.Buffer.Usage_.STATIC_DRAW;
+
 };
 
 
 /**
- * Start the drawing.
- * @param {ol.MapBrowserEvent} event Event.
- * @private
+ * @return {Array.<number>} Array.
  */
-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_));
+ol.webgl.Buffer.prototype.getArray = function() {
+  return this.arr_;
 };
 
 
 /**
- * Modify the drawing.
- * @param {ol.MapBrowserEvent} event Event.
- * @private
+ * @return {number} Usage.
  */
-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_();
+ol.webgl.Buffer.prototype.getUsage = function() {
+  return this.usage_;
 };
 
 
 /**
- * Add a new coordinate to the drawing.
- * @param {ol.MapBrowserEvent} event Event.
+ * @enum {number}
  * @private
  */
-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();
-  }
+ol.webgl.Buffer.Usage_ = {
+  STATIC_DRAW: ol.webgl.STATIC_DRAW,
+  STREAM_DRAW: ol.webgl.STREAM_DRAW,
+  DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW
 };
 
+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.circlereplay.defaultshader.Locations');
+goog.require('ol.render.webgl.Replay');
+goog.require('ol.render.webgl');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
+
 
 /**
- * Remove last point of the feature currently being drawn.
- * @api
+ * @constructor
+ * @extends {ol.render.webgl.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @struct
  */
-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);
-  }
+ol.render.webgl.CircleReplay = function(tolerance, maxExtent) {
+  ol.render.webgl.Replay.call(this, tolerance, maxExtent);
 
-  if (coordinates.length === 0) {
-    this.finishCoordinate_ = null;
-  }
+  /**
+   * @private
+   * @type {ol.render.webgl.circlereplay.defaultshader.Locations}
+   */
+  this.defaultLocations_ = null;
+
+  /**
+   * @private
+   * @type {Array.<Array.<Array.<number>|number>>}
+   */
+  this.styles_ = [];
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.styleIndices_ = [];
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.radius_ = 0;
+
+  /**
+   * @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
+  };
 
-  this.updateSketchFeatures_();
 };
+ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay);
 
 
 /**
- * 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
+ * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  */
-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();
-  }
+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_;
 
-  // 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]));
-  }
+    this.vertices[numVertices++] = flatCoordinates[i];
+    this.vertices[numVertices++] = flatCoordinates[i + 1];
+    this.vertices[numVertices++] = 1;
+    this.vertices[numVertices++] = this.radius_;
 
-  // First dispatch event to allow full set up of feature
-  this.dispatchEvent(new ol.interaction.Draw.Event(
-      ol.interaction.DrawEventType.DRAWEND, sketchFeature));
+    this.vertices[numVertices++] = flatCoordinates[i];
+    this.vertices[numVertices++] = flatCoordinates[i + 1];
+    this.vertices[numVertices++] = 2;
+    this.vertices[numVertices++] = this.radius_;
 
-  // Then insert feature
-  if (this.features_) {
-    this.features_.push(sketchFeature);
-  }
-  if (this.source_) {
-    this.source_.addFeature(sketchFeature);
+    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;
+
+    this.indices[numIndices++] = n + 2;
+    this.indices[numIndices++] = n + 3;
+    this.indices[numIndices++] = n;
+
+    n += 4;
   }
 };
 
 
 /**
- * Stop drawing without adding the sketch feature to the target layer.
- * @return {ol.Feature} The sketch feature (or null if none).
- * @private
+ * @inheritDoc
  */
-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);
+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;
+    }
+
+    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;
+      }
+    }
   }
-  return sketchFeature;
 };
 
 
 /**
- * Extend an existing geometry by adding additional points. This only works
- * on features with `LineString` geometries, where the interaction will
- * extend lines by adding points to the end of the coordinates array.
- * @param {!ol.Feature} feature Feature to be extended.
- * @api
- */
-ol.interaction.Draw.prototype.extend = function(feature) {
-  var geometry = feature.getGeometry();
-  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_));
+ * @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);
+
+  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_ = [];
+  }
+
+  this.vertices = null;
+  this.indices = null;
+};
+
+
+/**
+ * @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);
+  };
 };
 
 
 /**
  * @inheritDoc
  */
-ol.interaction.Draw.prototype.shouldStopEvent = ol.functions.FALSE;
+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_) {
+    locations = new ol.render.webgl.circlereplay.defaultshader.Locations(gl, program);
+    this.defaultLocations_ = locations;
+  } else {
+    locations = this.defaultLocations_;
+  }
+
+  context.useProgram(program);
+
+  // 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);
+
+  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);
+
+  return locations;
+};
 
 
 /**
- * Redraw the sketch features.
- * @private
+ * @inheritDoc
  */
-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.render.webgl.CircleReplay.prototype.shutDownProgram = function(gl, locations) {
+  gl.disableVertexAttribArray(locations.a_position);
+  gl.disableVertexAttribArray(locations.a_instruction);
+  gl.disableVertexAttribArray(locations.a_radius);
 };
 
 
 /**
- * @private
+ * @inheritDoc
  */
-ol.interaction.Draw.prototype.updateState_ = function() {
-  var map = this.getMap();
-  var active = this.getActive();
-  if (!map || !active) {
-    this.abortDrawing_();
+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;
+    }
   }
-  this.overlay_.setMap(active ? map : null);
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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 ? /** @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;
+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);
+
+        if (result) {
+          return result;
+        }
+
       }
-  );
+      featureIndex--;
+      end = start;
+    }
+  }
+  return undefined;
 };
 
 
 /**
- * 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
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
  */
-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;
+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];
+
+    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;
+  }
 };
 
 
 /**
- * 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
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {Array.<number>} color Color.
  */
-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);
+ol.render.webgl.CircleReplay.prototype.setFillStyle_ = function(gl, color) {
+  gl.uniform4fv(this.defaultLocations_.u_fillColor, color);
 };
 
 
 /**
- * Draw mode.  This collapses multi-part geometry types with their single-part
- * cousins.
- * @enum {string}
  * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {Array.<number>} color Color.
+ * @param {number} lineWidth Line width.
  */
-ol.interaction.Draw.Mode_ = {
-  POINT: 'Point',
-  LINE_STRING: 'LineString',
-  POLYGON: 'Polygon',
-  CIRCLE: 'Circle'
+ol.render.webgl.CircleReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth) {
+  gl.uniform4fv(this.defaultLocations_.u_strokeColor, color);
+  gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth);
 };
 
+
+/**
+ * @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]);
+  }
+};
+
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.texturereplay.defaultshader');
+
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
+
+
+ol.render.webgl.texturereplay.defaultshader.fragment = new ol.webgl.Fragment(ol.DEBUG_WEBGL ?
+  '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' :
+  '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;}');
+
+ol.render.webgl.texturereplay.defaultshader.vertex = new ol.webgl.Vertex(ol.DEBUG_WEBGL ?
+  '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' :
+  '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 file is automatically generated, do not edit
+goog.provide('ol.render.webgl.texturereplay.defaultshader.Locations');
+
+goog.require('ol');
+
+
 /**
- * @classdesc
- * Events emitted by {@link ol.interaction.Draw} instances are instances of
- * this type.
- *
  * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.DrawEvent}
- * @param {ol.interaction.DrawEventType} type Type.
- * @param {ol.Feature} feature The feature drawn.
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-ol.interaction.Draw.Event = function(type, feature) {
+ol.render.webgl.texturereplay.defaultshader.Locations = function(gl, program) {
 
-  ol.events.Event.call(this, type);
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h');
 
   /**
-   * The feature being drawn.
-   * @type {ol.Feature}
-   * @api
+   * @type {WebGLUniformLocation}
    */
-  this.feature = feature;
+  this.u_offsetScaleMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_offsetRotateMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_opacity = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_opacity' : 'k');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_image = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_image' : 'l');
+
+  /**
+   * @type {number}
+   */
+  this.a_position = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_position' : 'c');
+
+  /**
+   * @type {number}
+   */
+  this.a_texCoord = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'd');
 
+  /**
+   * @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');
+
+  /**
+   * @type {number}
+   */
+  this.a_rotateWithView = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'g');
 };
-ol.inherits(ol.interaction.Draw.Event, ol.events.Event);
 
-goog.provide('ol.interaction.Extent');
+goog.provide('ol.webgl.ContextEventType');
+
+
+/**
+ * @enum {string}
+ */
+ol.webgl.ContextEventType = {
+  LOST: 'webglcontextlost',
+  RESTORED: 'webglcontextrestored'
+};
+
+goog.provide('ol.webgl.Context');
 
 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');
+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');
 
 
 /**
  * @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.
+ * A WebGL context for accessing low-level WebGL capabilities.
  *
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.interaction.Extent.Event
- * @param {olx.interaction.ExtentOptions=} opt_options Options.
- * @api
+ * @extends {ol.Disposable}
+ * @param {HTMLCanvasElement} canvas Canvas.
+ * @param {WebGLRenderingContext} gl GL.
  */
-ol.interaction.Extent = function(opt_options) {
+ol.webgl.Context = function(canvas, gl) {
 
   /**
-   * Extent of the drawn box
-   * @type {ol.Extent}
    * @private
+   * @type {HTMLCanvasElement}
    */
-  this.extent_ = null;
+  this.canvas_ = canvas;
 
   /**
-   * Handler for pointer move events
-   * @type {function (ol.Coordinate): ol.Extent|null}
    * @private
+   * @type {WebGLRenderingContext}
    */
-  this.pointerHandler_ = null;
+  this.gl_ = gl;
 
   /**
-   * Pixel threshold to snap to extent
-   * @type {number}
    * @private
+   * @type {Object.<string, ol.WebglBufferCacheEntry>}
    */
-  this.pixelTolerance_ = 10;
+  this.bufferCache_ = {};
 
   /**
-   * Is the pointer snapped to an extent vertex
-   * @type {boolean}
    * @private
+   * @type {Object.<string, WebGLShader>}
    */
-  this.snappedToVertex_ = false;
+  this.shaderCache_ = {};
 
   /**
-   * Feature for displaying the visible extent
-   * @type {ol.Feature}
    * @private
+   * @type {Object.<string, WebGLProgram>}
    */
-  this.extentFeature_ = null;
+  this.programCache_ = {};
 
   /**
-   * Feature for displaying the visible pointer
-   * @type {ol.Feature}
    * @private
+   * @type {WebGLProgram}
    */
-  this.vertexFeature_ = null;
-
-  if (!opt_options) {
-    opt_options = {};
-  }
-
-  if (opt_options.extent) {
-    this.setExtent(opt_options.extent);
-  }
+  this.currentProgram_ = null;
 
-  /* 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_
-  });
+  /**
+   * @private
+   * @type {WebGLFramebuffer}
+   */
+  this.hitDetectionFramebuffer_ = null;
 
   /**
-   * Layer for the extentFeature
-   * @type {ol.layer.Vector}
    * @private
+   * @type {WebGLTexture}
    */
-  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
-  });
+  this.hitDetectionTexture_ = null;
 
   /**
-   * Layer for the vertexFeature
-   * @type {ol.layer.Vector}
    * @private
+   * @type {WebGLRenderbuffer}
    */
-  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
-  });
-};
+  this.hitDetectionRenderbuffer_ = null;
 
-ol.inherits(ol.interaction.Extent, ol.interaction.Pointer);
+  /**
+   * @type {boolean}
+   */
+  this.hasOESElementIndexUint = ol.array.includes(
+      ol.WEBGL_EXTENSIONS, 'OES_element_index_uint');
 
-/**
- * @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;
-  }
-  //display pointer (if not dragging)
-  if (mapBrowserEvent.type == ol.MapBrowserEventType.POINTERMOVE && !this.handlingDownUpSequence) {
-    this.handlePointerMove_(mapBrowserEvent);
+  // use the OES_element_index_uint extension if available
+  if (this.hasOESElementIndexUint) {
+    gl.getExtension('OES_element_index_uint');
   }
-  //call pointer to determine up/down/drag
-  ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent);
-  //return false to stop propagation
-  return false;
-};
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Event handled?
- * @this {ol.interaction.Extent}
- * @private
- */
-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];
-    }
-    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;
+  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
+      this.handleWebGLContextLost, this);
+  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
+      this.handleWebGLContextRestored, this);
 
-    //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 true; //event handled; start downup sequence
 };
+ol.inherits(ol.webgl.Context, ol.Disposable);
 
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Event handled?
- * @this {ol.interaction.Extent}
- * @private
- */
-ol.interaction.Extent.handleDragEvent_ = function(mapBrowserEvent) {
-  if (this.pointerHandler_) {
-    var pixelCoordinate = mapBrowserEvent.coordinate;
-    this.setExtent(this.pointerHandler_(pixelCoordinate));
-    this.createOrUpdatePointerFeature_(pixelCoordinate);
-  }
-  return true;
-};
 
 /**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Extent}
- * @private
+ * 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.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);
+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
+    };
   }
-  return false; //Stop handling downup sequence
-};
-
-/**
- * Returns the default style for the drawn bbox
- *
- * @return {ol.StyleFunction} Default Extent style
- * @private
- */
-ol.interaction.Extent.getDefaultExtentStyleFunction_ = function() {
-  var style = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POLYGON];
-  };
-};
-
-/**
- * Returns the default style for the pointer
- *
- * @return {ol.StyleFunction} Default pointer style
- * @private
- */
-ol.interaction.Extent.getDefaultPointerStyleFunction_ = function() {
-  var style = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POINT];
-  };
 };
 
-/**
- * @param {ol.Coordinate} fixedPoint corner that will be unchanged in the new extent
- * @returns {function (ol.Coordinate): ol.Extent} event handler
- * @private
- */
-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
+ * @param {ol.webgl.Buffer} buf Buffer.
  */
-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;
+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];
 };
 
-/**
- * @param {ol.Extent} extent extent
- * @returns {Array<Array<ol.Coordinate>>} extent line segments
- * @private
- */
-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]]]
-  ];
-};
-
-/**
- * @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];
-
-    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
-        closestSegment));
-    var vertexPixel = map.getPixelFromCoordinate(vertex);
 
-    //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;
+/**
+ * @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_);
   }
-  return null;
 };
 
+
 /**
- * @param {ol.MapBrowserEvent} mapBrowserEvent pointer move event
- * @private
+ * @return {HTMLCanvasElement} Canvas.
  */
-ol.interaction.Extent.prototype.handlePointerMove_ = function(mapBrowserEvent) {
-  var pixel = mapBrowserEvent.pixel;
-  var map = mapBrowserEvent.map;
+ol.webgl.Context.prototype.getCanvas = function() {
+  return this.canvas_;
+};
 
-  var vertex = this.snapToVertex_(pixel, map);
-  if (!vertex) {
-    vertex = map.getCoordinateFromPixel(pixel);
-  }
-  this.createOrUpdatePointerFeature_(vertex);
+
+/**
+ * Get the WebGL rendering context
+ * @return {WebGLRenderingContext} The rendering context.
+ * @api
+ */
+ol.webgl.Context.prototype.getGL = function() {
+  return this.gl_;
 };
 
+
 /**
- * @param {ol.Extent} extent extent
- * @returns {ol.Feature} extent as featrue
- * @private
+ * Get the frame buffer for hit detection.
+ * @return {WebGLFramebuffer} The hit detection frame buffer.
  */
-ol.interaction.Extent.prototype.createOrUpdateExtentFeature_ = function(extent) {
-  var extentFeature = this.extentFeature_;
+ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() {
+  if (!this.hitDetectionFramebuffer_) {
+    this.initHitDetectionFramebuffer_();
+  }
+  return this.hitDetectionFramebuffer_;
+};
 
-  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);
+
+/**
+ * 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 {
-    if (!extent) {
-      extentFeature.setGeometry(undefined);
-    } else {
-      extentFeature.setGeometry(ol.geom.Polygon.fromExtent(extent));
-    }
+    var gl = this.getGL();
+    var shader = gl.createShader(shaderObject.getType());
+    gl.shaderSource(shader, shaderObject.getSource());
+    gl.compileShader(shader);
+    this.shaderCache_[shaderKey] = shader;
+    return shader;
   }
-  return extentFeature;
 };
 
 
 /**
- * @param {ol.Coordinate} vertex location of feature
- * @returns {ol.Feature} vertex as feature
- * @private
+ * Get the program from the cache if it's in the cache. Otherwise create
+ * the WebGL program, attach the shaders to it, and add an entry to the
+ * cache.
+ * @param {ol.webgl.Fragment} fragmentShaderObject Fragment shader.
+ * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader.
+ * @return {WebGLProgram} Program.
  */
-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);
+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 geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
-    geometry.setCoordinates(vertex);
+    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;
   }
-  return vertexFeature;
 };
 
 
 /**
- * @inheritDoc
+ * FIXME empy description for jsdoc
  */
-ol.interaction.Extent.prototype.setMap = function(map) {
-  this.extentOverlay_.setMap(map);
-  this.vertexOverlay_.setMap(map);
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
+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;
 };
 
+
 /**
- * Returns the current drawn extent in the view projection
- *
- * @return {ol.Extent} Drawn extent in the view projection.
- * @api
+ * FIXME empy description for jsdoc
  */
-ol.interaction.Extent.prototype.getExtent = function() {
-  return this.extent_;
+ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
 };
 
+
 /**
- * Manually sets the drawn extent, using the view projection.
- *
- * @param {ol.Extent} extent Extent
- * @api
+ * Creates a 1x1 pixel framebuffer for the hit-detection.
+ * @private
  */
-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_));
+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;
 };
 
 
 /**
- * @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}
+ * Use a program.  If the program is already in use, this will return `false`.
+ * @param {WebGLProgram} program Program.
+ * @return {boolean} Changed.
+ * @api
  */
-ol.interaction.Extent.Event = function(extent) {
-  ol.events.Event.call(this, ol.interaction.Extent.EventType_.EXTENTCHANGED);
-
-  /**
-   * The current extent.
-   * @type {ol.Extent}
-   * @api
-   */
-  this.extent_ = extent;
+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;
+  }
 };
-ol.inherits(ol.interaction.Extent.Event, ol.events.Event);
 
 
 /**
- * @enum {string}
+ * @param {WebGLRenderingContext} gl WebGL rendering context.
+ * @param {number=} opt_wrapS wrapS.
+ * @param {number=} opt_wrapT wrapT.
+ * @return {WebGLTexture} The texture.
  * @private
  */
-ol.interaction.Extent.EventType_ = {
-  /**
-   * Triggered after the extent is changed
-   * @event ol.interaction.Extent.Event
-   * @api
-   */
-  EXTENTCHANGED: 'extentchanged'
+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(
+        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;
 };
 
-goog.provide('ol.interaction.ModifyEventType');
+
+/**
+ * @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.
+ */
+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;
+};
 
 
 /**
- * @enum {string}
+ * @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.
  */
-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'
+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.interaction.Modify');
+goog.provide('ol.render.webgl.TextureReplay');
 
 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');
-
+goog.require('ol.obj');
+goog.require('ol.render.webgl.texturereplay.defaultshader');
+goog.require('ol.render.webgl.texturereplay.defaultshader.Locations');
+goog.require('ol.render.webgl.Replay');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Context');
 
 /**
- * @classdesc
- * Interaction for modifying feature geometries.
- *
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.ModifyOptions} options Options.
- * @fires ol.interaction.Modify.Event
- * @api
+ * @abstract
+ * @extends {ol.render.webgl.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @struct
  */
-ol.interaction.Modify = function(options) {
-
-  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_
-  });
+ol.render.webgl.TextureReplay = function(tolerance, maxExtent) {
+  ol.render.webgl.Replay.call(this, tolerance, maxExtent);
 
   /**
-   * @private
-   * @type {ol.EventsConditionType}
+   * @type {number|undefined}
+   * @protected
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.primaryAction;
-
+  this.anchorX = undefined;
 
   /**
-   * @private
-   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
-   * @return {boolean} Combined condition result.
+   * @type {number|undefined}
+   * @protected
    */
-  this.defaultDeleteCondition_ = function(mapBrowserEvent) {
-    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
-      ol.events.condition.singleClick(mapBrowserEvent);
-  };
+  this.anchorY = undefined;
 
   /**
-   * @type {ol.EventsConditionType}
-   * @private
+   * @type {Array.<number>}
+   * @protected
    */
-  this.deleteCondition_ = options.deleteCondition ?
-      options.deleteCondition : this.defaultDeleteCondition_;
+  this.groupIndices = [];
 
   /**
-   * @type {ol.EventsConditionType}
-   * @private
+   * @type {Array.<number>}
+   * @protected
    */
-  this.insertVertexCondition_ = options.insertVertexCondition ?
-      options.insertVertexCondition : ol.events.condition.always;
+  this.hitDetectionGroupIndices = [];
 
   /**
-   * Editing vertex.
-   * @type {ol.Feature}
-   * @private
+   * @type {number|undefined}
+   * @protected
    */
-  this.vertexFeature_ = null;
+  this.height = undefined;
 
   /**
-   * Segments intersecting {@link this.vertexFeature_} by segment uid.
-   * @type {Object.<string, boolean>}
-   * @private
+   * @type {number|undefined}
+   * @protected
    */
-  this.vertexSegments_ = null;
+  this.imageHeight = undefined;
 
   /**
-   * @type {ol.Pixel}
-   * @private
+   * @type {number|undefined}
+   * @protected
    */
-  this.lastPixel_ = [0, 0];
+  this.imageWidth = undefined;
 
   /**
-   * Tracks if the next `singleclick` event should be ignored to prevent
-   * accidental deletion right after vertex creation.
-   * @type {boolean}
-   * @private
+   * @protected
+   * @type {ol.render.webgl.texturereplay.defaultshader.Locations}
    */
-  this.ignoreNextSingleClick_ = false;
+  this.defaultLocations = null;
 
   /**
-   * @type {boolean}
-   * @private
+   * @protected
+   * @type {number|undefined}
    */
-  this.modified_ = false;
+  this.opacity = undefined;
 
   /**
-   * Segment RTree for each layer
-   * @type {ol.structs.RBush.<ol.ModifySegmentDataType>}
-   * @private
+   * @type {number|undefined}
+   * @protected
    */
-  this.rBush_ = new ol.structs.RBush();
+  this.originX = undefined;
 
   /**
-   * @type {number}
-   * @private
+   * @type {number|undefined}
+   * @protected
    */
-  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
-      options.pixelTolerance : 10;
+  this.originY = undefined;
 
   /**
-   * @type {boolean}
-   * @private
+   * @protected
+   * @type {boolean|undefined}
    */
-  this.snappedToVertex_ = false;
+  this.rotateWithView = undefined;
 
   /**
-   * Indicate whether the interaction is currently changing a feature's
-   * coordinates.
-   * @type {boolean}
-   * @private
+   * @protected
+   * @type {number|undefined}
    */
-  this.changingFeature_ = false;
+  this.rotation = undefined;
 
   /**
-   * @type {Array}
-   * @private
+   * @protected
+   * @type {number|undefined}
    */
-  this.dragSegments_ = [];
+  this.scale = undefined;
 
   /**
-   * Draw overlay where sketch features are drawn.
-   * @type {ol.layer.Vector}
-   * @private
+   * @type {number|undefined}
+   * @protected
    */
-  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.width = undefined;
+};
+ol.inherits(ol.render.webgl.TextureReplay, ol.render.webgl.Replay);
 
-  /**
-  * @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_
+
+/**
+ * @inheritDoc
+ */
+ol.render.webgl.TextureReplay.prototype.getDeleteResourcesFunction = function(context) {
+  var verticesBuffer = this.verticesBuffer;
+  var indicesBuffer = this.indicesBuffer;
+  var textures = this.getTextures(true);
+  var gl = context.getGL();
+  return function() {
+    if (!gl.isContextLost()) {
+      var i, ii;
+      for (i = 0, ii = textures.length; i < ii; ++i) {
+        gl.deleteTexture(textures[i]);
+      }
+    }
+    context.deleteBuffer(verticesBuffer);
+    context.deleteBuffer(indicesBuffer);
   };
+};
 
-  /**
-   * @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);
+/**
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
+ * @return {number} My end.
+ * @protected
+ */
+ol.render.webgl.TextureReplay.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;
 
-  /**
-   * @type {ol.MapBrowserPointerEvent}
-   * @private
-   */
-  this.lastPointerEvent_ = null;
+    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;
 };
-ol.inherits(ol.interaction.Modify, ol.interaction.Pointer);
 
 
 /**
- * @define {number} The segment index assigned to a circle's center when
- * breaking up a cicrle into ModifySegmentDataType segments.
- */
-ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX = 0;
+ * @protected
+ * @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.TextureReplay.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];
 
-/**
- * @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;
+    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;
+  }
+};
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @private
+ * @inheritDoc
  */
-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.render.webgl.TextureReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {
+  // get the program
+  var fragmentShader = ol.render.webgl.texturereplay.defaultshader.fragment;
+  var vertexShader = ol.render.webgl.texturereplay.defaultshader.vertex;
+  var program = context.getProgram(fragmentShader, vertexShader);
+
+  // get the locations
+  var locations;
+  if (!this.defaultLocations) {
+    locations = new ol.render.webgl.texturereplay.defaultshader.Locations(gl, program);
+    this.defaultLocations = locations;
+  } else {
+    locations = this.defaultLocations;
   }
-  ol.events.listen(feature, ol.events.EventType.CHANGE,
-      this.handleFeatureChange_, this);
+
+  // use the program (FIXME: use the return value)
+  context.useProgram(program);
+
+  // enable the vertex attrib arrays
+  gl.enableVertexAttribArray(locations.a_position);
+  gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
+      false, 32, 0);
+
+  gl.enableVertexAttribArray(locations.a_offsets);
+  gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT,
+      false, 32, 8);
+
+  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);
+
+  gl.enableVertexAttribArray(locations.a_rotateWithView);
+  gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT,
+      false, 32, 28);
+
+  return locations;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Map browser event
- * @private
+ * @inheritDoc
  */
-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));
-  }
+ol.render.webgl.TextureReplay.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);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @private
+ * @inheritDoc
  */
-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.render.webgl.TextureReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {
+  var textures = hitDetection ? this.getHitDetectionTextures() : this.getTextures();
+  var groupIndices = hitDetection ? this.hitDetectionGroupIndices : this.groupIndices;
+
+  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;
+    }
   }
-  ol.events.unlisten(feature, ol.events.EventType.CHANGE,
-      this.handleFeatureChange_, this);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
- * @private
+ * 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
+ *
+ * @protected
+ * @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.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);
+ol.render.webgl.TextureReplay.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);
         }
-      });
-  for (var i = nodesToRemove.length - 1; i >= 0; --i) {
-    rBush.remove(nodesToRemove[i]);
+        // 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, context, start, end);
+    }
   }
 };
 
@@ -65692,6239 +67280,7064 @@ ol.interaction.Modify.prototype.removeFeatureSegmentData_ = function(feature) {
 /**
  * @inheritDoc
  */
-ol.interaction.Modify.prototype.setActive = function(active) {
-  if (this.vertexFeature_ && !active) {
-    this.overlay_.getSource().removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
+ol.render.webgl.TextureReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash,
+    featureCallback, opt_hitExtent) {
+  var i, groupStart, start, end, feature, featureUid;
+  var featureIndex = this.startIndices.length - 1;
+  var hitDetectionTextures = this.getHitDetectionTextures();
+  for (i = hitDetectionTextures.length - 1; i >= 0; --i) {
+    gl.bindTexture(ol.webgl.TEXTURE_2D, 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;
+        }
+      }
+
+      end = start;
+      featureIndex--;
+    }
   }
-  ol.interaction.Pointer.prototype.setActive.call(this, active);
+  return undefined;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.interaction.Modify.prototype.setMap = function(map) {
-  this.overlay_.setMap(map);
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
+ol.render.webgl.TextureReplay.prototype.finish = function(context) {
+  this.anchorX = undefined;
+  this.anchorY = undefined;
+  this.height = undefined;
+  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;
 };
 
 
 /**
- * @param {ol.Collection.Event} evt Event.
- * @private
+ * @abstract
+ * @protected
+ * @param {boolean=} opt_all Return hit detection textures with regular ones.
+ * @returns {Array.<WebGLTexture>} Textures.
  */
-ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) {
-  this.addFeature_(/** @type {ol.Feature} */ (evt.element));
-};
+ol.render.webgl.TextureReplay.prototype.getTextures = function(opt_all) {};
 
 
 /**
- * @param {ol.events.Event} evt Event.
- * @private
+ * @abstract
+ * @protected
+ * @returns {Array.<WebGLTexture>} Textures.
  */
-ol.interaction.Modify.prototype.handleFeatureChange_ = function(evt) {
-  if (!this.changingFeature_) {
-    var feature = /** @type {ol.Feature} */ (evt.target);
-    this.removeFeature_(feature);
-    this.addFeature_(feature);
-  }
-};
+ol.render.webgl.TextureReplay.prototype.getHitDetectionTextures = function() {};
+
+goog.provide('ol.render.webgl.ImageReplay');
+
+goog.require('ol');
+goog.require('ol.render.webgl.TextureReplay');
+goog.require('ol.webgl.Buffer');
 
 
 /**
- * @param {ol.Collection.Event} evt Event.
- * @private
+ * @constructor
+ * @extends {ol.render.webgl.TextureReplay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @struct
  */
-ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
-  var feature = /** @type {ol.Feature} */ (evt.element);
-  this.removeFeature_(feature);
+ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
+  ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent);
+
+  /**
+   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+   * @protected
+   */
+  this.images_ = [];
+
+  /**
+   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+   * @protected
+   */
+  this.hitDetectionImages_ = [];
+
+  /**
+   * @type {Array.<WebGLTexture>}
+   * @private
+   */
+  this.textures_ = [];
+
+  /**
+   * @type {Array.<WebGLTexture>}
+   * @private
+   */
+  this.hitDetectionTextures_ = [];
+
 };
+ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.TextureReplay);
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Point} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
+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);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPoint} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
-  }
+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);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.LineString} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
-  }
-};
+ol.render.webgl.ImageReplay.prototype.finish = function(context) {
+  var gl = context.getGL();
 
+  this.groupIndices.push(this.indices.length);
+  this.hitDetectionGroupIndices.push(this.indices.length);
 
-/**
- * @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);
-    }
-  }
+  // 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.images_ = null;
+  this.hitDetectionImages_ = null;
+  ol.render.webgl.TextureReplay.prototype.finish.call(this, context);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Polygon} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
+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 {
+    currentImage = this.images_[this.images_.length - 1];
+    if (ol.getUid(currentImage) != ol.getUid(image)) {
+      this.groupIndices.push(this.indices.length);
+      this.images_.push(image);
     }
   }
-};
-
 
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) {
-  var polygons = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
-  for (k = 0, kk = polygons.length; k < kk; ++k) {
-    rings = polygons[k];
-    for (j = 0, jj = rings.length; j < jj; ++j) {
-      coordinates = rings[j];
-      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-        segment = coordinates.slice(i, i + 2);
-        segmentData = /** @type {ol.ModifySegmentDataType} */ ({
-          feature: feature,
-          geometry: geometry,
-          depth: [j, k],
-          index: i,
-          segment: segment
-        });
-        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-      }
+  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);
     }
   }
+
+  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];
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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);
+ol.render.webgl.ImageReplay.prototype.getTextures = function(opt_all) {
+  return opt_all ? this.textures_.concat(this.hitDetectionTextures_) : this.textures_;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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.render.webgl.ImageReplay.prototype.getHitDetectionTextures = function() {
+  return this.hitDetectionTextures_;
 };
 
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.linestringreplay.defaultshader');
+
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
+
+
+ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.webgl.Fragment(ol.DEBUG_WEBGL ?
+  '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' :
+  'precision mediump float;varying float a;varying vec2 aVertex;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((aVertex.x+1.0)/2.0*o.x*p,(aVertex.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;}');
+
+ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.webgl.Vertex(ol.DEBUG_WEBGL ?
+  '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' :
+  'varying float a;varying vec2 aVertex;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;aVertex=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);}}');
+
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.linestringreplay.defaultshader.Locations');
+
+goog.require('ol');
+
 
 /**
- * @param {ol.Coordinate} coordinates Coordinates.
- * @return {ol.Feature} Vertex feature.
- * @private
+ * @constructor
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-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.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) {
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_offsetScaleMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_offsetRotateMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j');
+
+  /**
+   * @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');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_opacity = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_color = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_color' : 'n');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_size = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_size' : 'o');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_pixelRatio = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'p');
+
+  /**
+   * @type {number}
+   */
+  this.a_lastPos = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_lastPos' : 'd');
+
+  /**
+   * @type {number}
+   */
+  this.a_position = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_position' : 'e');
+
+  /**
+   * @type {number}
+   */
+  this.a_nextPos = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_nextPos' : 'f');
+
+  /**
+   * @type {number}
+   */
+  this.a_direction = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_direction' : 'g');
 };
 
+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.render.webgl.linestringreplay.defaultshader.Locations');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
+
 
 /**
- * @param {ol.ModifySegmentDataType} a The first segment data.
- * @param {ol.ModifySegmentDataType} b The second segment data.
- * @return {number} The difference in indexes.
- * @private
+ * @constructor
+ * @extends {ol.render.webgl.Replay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @struct
  */
-ol.interaction.Modify.compareIndexes_ = function(a, b) {
-  return a.index - b.index;
+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;
+
+  /**
+   * @private
+   * @type {Array.<Array.<?>>}
+   */
+  this.styles_ = [];
+
+  /**
+   * @private
+   * @type {Array.<number>}
+   */
+  this.styleIndices_ = [];
+
+  /**
+   * @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);
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Modify}
+ * Draw one segment.
  * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  */
-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);
+ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
+
+  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 (segmentDataMatch.geometry.getType() === ol.geom.GeometryType.CIRCLE &&
-      segmentDataMatch.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
+      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.
+
+        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);
+
+          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;
 
-        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;
+        numVertices = this.addVertices_([0, 0], p1, p2,
+            lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices);
+
+        numVertices = this.addVertices_([0, 0], p1, p2,
+            -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices);
+
+        lastIndex = numVertices / 7 - 1;
+
+        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);
+
+        numVertices = this.addVertices_(p0, p1, [0, 0],
+            -lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE * (lineCap || 1), numVertices);
+
+        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);
+
+          numVertices = this.addVertices_(p0, p1, [0, 0],
+              -lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE_CAP * lineCap, numVertices);
+
+          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;
+
         }
 
-        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]);
+        break;
       }
+    } else {
+      p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]];
     }
-    if (insertVertices.length) {
-      this.willModifyFeatures_(evt);
+
+    // 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);
+
+    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);
+
+    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;
     }
-    for (var j = insertVertices.length - 1; j >= 0; --j) {
-      this.insertVertex_.apply(this, insertVertices[j]);
+
+    this.indices[numIndices++] = n;
+    this.indices[numIndices++] = n + 2;
+    this.indices[numIndices++] = n + 1;
+
+    lastIndex = n + 2;
+    lastSign = sign;
+
+    //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;
     }
   }
-  return !!this.vertexFeature_;
-};
 
+  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);
+
+    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;
+
+    this.indices[numIndices++] = n + 1;
+    this.indices[numIndices++] = n;
+    this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1;
+  }
+};
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @this {ol.interaction.Modify}
+ * @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.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];
+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;
 
-    while (vertex.length < geometry.getStride()) {
-      vertex.push(segment[index][vertex.length]);
-    }
+  return numVertices;
+};
 
-    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
-    }
+/**
+ * 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);
+  }
 
-    if (coordinates) {
-      this.setGeometryCoordinates_(geometry, coordinates);
+  return true;
+};
+
+
+/**
+ * @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);
   }
-  this.createOrUpdateVertexFeature_(vertex);
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Modify}
- * @private
+ * @inheritDoc
  */
-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);
+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);
+      }
     }
   }
-  if (this.modified_) {
-    this.dispatchEvent(new ol.interaction.Modify.Event(
-        ol.interaction.ModifyEventType.MODIFYEND, this.features_, evt));
-    this.modified_ = false;
+  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;
+    }
   }
-  return false;
 };
 
 
 /**
- * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the
- * geometry.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.Modify}
- * @api
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates.
+ * @param {number} stride Stride.
  */
-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);
+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]);
   }
-  if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) {
-    if (mapBrowserEvent.type != ol.MapBrowserEventType.SINGLECLICK ||
-        !this.ignoreNextSingleClick_) {
-      handled = this.removePoint();
-    } else {
-      handled = true;
+  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);
     }
   }
-
-  if (mapBrowserEvent.type == ol.MapBrowserEventType.SINGLECLICK) {
-    this.ignoreNextSingleClick_ = false;
-  }
-
-  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
-      !handled;
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} evt Event.
- * @private
+ * @param {ol.Feature|ol.render.Feature} feature Feature.
+ * @param {number=} opt_index Index count.
  */
-ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
-  this.lastPixel_ = evt.pixel;
-  this.handlePointerAtPixel_(evt.pixel, evt.map);
+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;
+  }
 };
 
 
 /**
- * @param {ol.Pixel} pixel Pixel
- * @param {ol.Map} map Map.
- * @private
+ * @return {number} Current index.
  */
-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);
-  };
+ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() {
+  return this.indices.length;
+};
 
-  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 = {};
+/**
+ * @inheritDoc
+ **/
+ol.render.webgl.LineStringReplay.prototype.finish = function(context) {
+  // create, bind, and populate the vertices buffer
+  this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
 
-      if (node.geometry.getType() === ol.geom.GeometryType.CIRCLE &&
-      node.index === ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
+  // create, bind, and populate the indices buffer
+  this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
-        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;
-          }
-        }
-      }
+  this.startIndices.push(this.indices.length);
 
-      vertexSegments[ol.getUid(closestSegment)] = true;
-      this.vertexSegments_ = vertexSegments;
-      return;
-    }
-  }
-  if (this.vertexFeature_) {
-    this.overlay_.getSource().removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
+  //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 the distance from a point to a line segment.
- *
- * @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.
+ * @inheritDoc
  */
-ol.interaction.Modify.pointDistanceToSegmentDataSquared_ = function(pointCoordinates, segmentData) {
-  var geometry = segmentData.geometry;
-
-  if (geometry.getType() === ol.geom.GeometryType.CIRCLE) {
-    var circleGeometry = /** @type {ol.geom.Circle} */ (geometry);
-
-    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;
-    }
-  }
-  return ol.coordinate.squaredDistanceToSegment(pointCoordinates, segmentData.segment);
+ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) {
+  var verticesBuffer = this.verticesBuffer;
+  var indicesBuffer = this.indicesBuffer;
+  return function() {
+    context.deleteBuffer(verticesBuffer);
+    context.deleteBuffer(indicesBuffer);
+  };
 };
 
+
 /**
- * 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.
+ * @inheritDoc
  */
-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);
+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_) {
+    locations = new ol.render.webgl.linestringreplay.defaultshader.Locations(gl, program);
+    this.defaultLocations_ = locations;
+  } else {
+    locations = this.defaultLocations_;
   }
-  return ol.coordinate.closestOnSegment(pointCoordinates, segmentData.segment);
-};
 
+  context.useProgram(program);
 
-/**
- * @param {ol.ModifySegmentDataType} 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 = /** @type {number} */ (segmentData.index);
-  var coordinates;
+  // enable the vertex attrib arrays
+  gl.enableVertexAttribArray(locations.a_lastPos);
+  gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT,
+      false, 28, 0);
 
-  while (vertex.length < geometry.getStride()) {
-    vertex.push(0);
-  }
+  gl.enableVertexAttribArray(locations.a_position);
+  gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
+      false, 28, 8);
 
-  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;
-  }
+  gl.enableVertexAttribArray(locations.a_nextPos);
+  gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT,
+      false, 28, 16);
 
-  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]);
+  gl.enableVertexAttribArray(locations.a_direction);
+  gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT,
+      false, 28, 24);
 
-  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;
+  // Enable renderer specific uniforms.
+  gl.uniform2fv(locations.u_size, size);
+  gl.uniform1f(locations.u_pixelRatio, pixelRatio);
+
+  return locations;
 };
 
+
 /**
- * Removes the vertex currently being pointed.
- * @return {boolean} True when a vertex was removed.
- * @api
+ * @inheritDoc
  */
-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;
+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);
 };
 
+
 /**
- * Removes a vertex from all matching features.
- * @return {boolean} True when a vertex was removed.
- * @private
+ * @inheritDoc
  */
-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;
-    }
+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);
   }
-  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;
+  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;
     }
-
   }
-  return deleted;
+  if (!hitDetection) {
+    gl.disable(gl.DEPTH_TEST);
+    gl.clear(gl.DEPTH_BUFFER_BIT);
+    //Restore GL parameters.
+    gl.depthMask(tmpDepthMask);
+    gl.depthFunc(tmpDepthFunc);
+  }
 };
 
 
 /**
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {Array} coordinates Coordinates.
  * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
  */
-ol.interaction.Modify.prototype.setGeometryCoordinates_ = function(geometry, coordinates) {
-  this.changingFeature_ = true;
-  geometry.setCoordinates(coordinates);
-  this.changingFeature_ = false;
+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();
+
+      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;
+  }
 };
 
 
 /**
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {number} index Index.
- * @param {Array.<number>|undefined} depth Depth.
- * @param {number} delta Delta (1 or -1).
- * @private
+ * @inheritDoc
  */
-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;
+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;
+        }
+
+      }
+      featureIndex--;
+      end = start;
     }
-  });
+  }
+  return undefined;
 };
 
 
 /**
- * @return {ol.StyleFunction} Styles.
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {Array.<number>} color Color.
+ * @param {number} lineWidth Line width.
+ * @param {number} miterLimit Miter limit.
  */
-ol.interaction.Modify.getDefaultStyleFunction = function() {
-  var style = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POINT];
-  };
+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);
 };
 
 
 /**
- * @classdesc
- * Events emitted by {@link ol.interaction.Modify} instances are instances of
- * this type.
- *
- * @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}.
+ * @inheritDoc
  */
-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;
+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]);
+  }
+};
 
-  /**
-   * Associated {@link ol.MapBrowserEvent}.
-   * @type {ol.MapBrowserEvent}
-   * @api
-   */
-  this.mapBrowserEvent = mapBrowserPointerEvent;
+/**
+ * @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
 };
-ol.inherits(ol.interaction.Modify.Event, ol.events.Event);
 
-goog.provide('ol.interaction.Select');
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.polygonreplay.defaultshader');
+
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
+
+
+ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.webgl.Fragment(ol.DEBUG_WEBGL ?
+  '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' :
+  '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;}');
+
+ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.webgl.Vertex(ol.DEBUG_WEBGL ?
+  '\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' :
+  'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}');
+
+// This file is automatically generated, do not edit
+goog.provide('ol.render.webgl.polygonreplay.defaultshader.Locations');
 
 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');
 
 
 /**
- * @classdesc
- * Interaction for selecting vector features. By default, selected features are
- * styled differently, so this interaction can be used for visual highlighting,
- * as well as selecting features for other actions, such as modification or
- * output. There are three ways of controlling which features are selected:
- * using the browser event as defined by the `condition` and optionally the
- * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a
- * further feature filter using the `filter` option.
- *
- * Selected features are added to an internal unmanaged layer.
- *
  * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.SelectOptions=} opt_options Options.
- * @fires ol.interaction.Select.Event
- * @api
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-ol.interaction.Select = function(opt_options) {
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.Select.handleEvent
-  });
+ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) {
 
-  var options = opt_options ? opt_options : {};
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'b');
 
   /**
-   * @private
-   * @type {ol.EventsConditionType}
+   * @type {WebGLUniformLocation}
    */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.singleClick;
+  this.u_offsetScaleMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'c');
 
   /**
-   * @private
-   * @type {ol.EventsConditionType}
+   * @type {WebGLUniformLocation}
    */
-  this.addCondition_ = options.addCondition ?
-      options.addCondition : ol.events.condition.never;
+  this.u_offsetRotateMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'd');
 
   /**
-   * @private
-   * @type {ol.EventsConditionType}
+   * @type {WebGLUniformLocation}
    */
-  this.removeCondition_ = options.removeCondition ?
-      options.removeCondition : ol.events.condition.never;
+  this.u_color = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_color' : 'e');
 
   /**
-   * @private
-   * @type {ol.EventsConditionType}
+   * @type {WebGLUniformLocation}
    */
-  this.toggleCondition_ = options.toggleCondition ?
-      options.toggleCondition : ol.events.condition.shiftKeyOnly;
+  this.u_opacity = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f');
 
   /**
-   * @private
-   * @type {boolean}
+   * @type {number}
    */
-  this.multi_ = options.multi ? options.multi : false;
+  this.a_position = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_position' : 'a');
+};
+
+goog.provide('ol.structs.LinkedList');
+
+/**
+ * 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.
+ */
+ol.structs.LinkedList = function(opt_circular) {
 
   /**
    * @private
-   * @type {ol.SelectFilterFunction}
+   * @type {ol.LinkedListItem|undefined}
    */
-  this.filter_ = options.filter ? options.filter :
-      ol.functions.TRUE;
+  this.first_ = undefined;
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.LinkedListItem|undefined}
    */
-  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
-  });
+  this.last_ = undefined;
 
   /**
    * @private
-   * @type {ol.layer.Vector}
+   * @type {ol.LinkedListItem|undefined}
    */
-  this.featureOverlay_ = featureOverlay;
-
-  /** @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;
-  }
+  this.head_ = undefined;
 
   /**
    * @private
-   * @type {function(ol.layer.Layer): boolean}
+   * @type {boolean}
    */
-  this.layerFilter_ = layerFilter;
+  this.circular_ = opt_circular === undefined ? true : opt_circular;
 
   /**
-   * An association between selected feature (key)
-   * and layer (value)
    * @private
-   * @type {Object.<number, ol.layer.Layer>}
+   * @type {number}
    */
-  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);
-
-};
-ol.inherits(ol.interaction.Select, ol.interaction.Interaction);
-
-
-/**
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @param {ol.layer.Layer} layer Layer.
- * @private
- */
-ol.interaction.Select.prototype.addFeatureLayerAssociation_ = function(feature, layer) {
-  var key = ol.getUid(feature);
-  this.featureLayerAssociation_[key] = layer;
+  this.length_ = 0;
 };
 
-
 /**
- * Get the selected features.
- * @return {ol.Collection.<ol.Feature>} Features collection.
- * @api
+ * Inserts an item into the linked list right after the current one.
+ *
+ * @param {?} data Item data.
  */
-ol.interaction.Select.prototype.getFeatures = function() {
-  return this.featureOverlay_.getSource().getFeaturesCollection();
-};
+ol.structs.LinkedList.prototype.insertItem = function(data) {
 
+  /** @type {ol.LinkedListItem} */
+  var item = {
+    prev: undefined,
+    next: undefined,
+    data: data
+  };
 
-/**
- * Returns the Hit-detection tolerance.
- * @returns {number} Hit tolerance in pixels.
- * @api
- */
-ol.interaction.Select.prototype.getHitTolerance = function() {
-  return this.hitTolerance_;
-};
+  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;
+    }
 
-/**
- * Returns the associated {@link ol.layer.Vector vectorlayer} of
- * the (last) selected feature. Note that this will not work with any
- * programmatic method like pushing features to
- * {@link ol.interaction.Select#getFeatures collection}.
- * @param {ol.Feature|ol.render.Feature} feature Feature
- * @return {ol.layer.Vector} Layer.
- * @api
- */
-ol.interaction.Select.prototype.getLayer = function(feature) {
-  var key = ol.getUid(feature);
-  return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]);
+    if (head === this.last_) {
+      this.last_ = item;
+    }
+  }
+  this.head_ = item;
+  this.length_++;
 };
 
-
 /**
- * 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
+ * Removes the current item from the list. Sets the cursor to the next item,
+ * if possible.
  */
-ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
-  if (!this.condition_(mapBrowserEvent)) {
-    return true;
-  }
-  var add = this.addCondition_(mapBrowserEvent);
-  var remove = this.removeCondition_(mapBrowserEvent);
-  var toggle = this.toggleCondition_(mapBrowserEvent);
-  var set = !add && !remove && !toggle;
-  var map = mapBrowserEvent.map;
-  var features = this.featureOverlay_.getSource().getFeaturesCollection();
-  var 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);
-      }
+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 (selected.length !== 0) {
-      features.extend(selected);
+    if (prev) {
+      prev.next = next;
     }
-  } 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]);
+    this.head_ = next || prev;
+
+    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_;
     }
-    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));
+    this.length_--;
   }
-  return ol.events.condition.pointerMove(mapBrowserEvent);
 };
 
-
 /**
- * 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
+ * Sets the cursor to the first item, and returns the associated data.
+ *
+ * @return {?} Item data.
  */
-ol.interaction.Select.prototype.setHitTolerance = function(hitTolerance) {
-  this.hitTolerance_ = hitTolerance;
+ol.structs.LinkedList.prototype.firstItem = function() {
+  this.head_ = this.first_;
+  if (this.head_) {
+    return this.head_.data;
+  }
+  return undefined;
 };
 
-
 /**
- * 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
- */
-ol.interaction.Select.prototype.setMap = function(map) {
-  var currentMap = this.getMap();
-  var selectedFeatures =
-      this.featureOverlay_.getSource().getFeaturesCollection();
-  if (currentMap) {
-    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
-  }
-  ol.interaction.Interaction.prototype.setMap.call(this, map);
-  this.featureOverlay_.setMap(map);
-  if (map) {
-    selectedFeatures.forEach(map.skipFeature, map);
+* 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;
 };
 
-
 /**
- * @return {ol.StyleFunction} Styles.
+ * Sets the cursor to the next item, and returns the associated data.
+ *
+ * @return {?} Item data.
  */
-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()];
-  };
+ol.structs.LinkedList.prototype.nextItem = function() {
+  if (this.head_ && this.head_.next) {
+    this.head_ = this.head_.next;
+    return this.head_.data;
+  }
+  return undefined;
 };
 
-
 /**
- * @param {ol.Collection.Event} evt Event.
- * @private
+ * Returns the next item's data without moving the cursor.
+ *
+ * @return {?} Item data.
  */
-ol.interaction.Select.prototype.addFeature_ = function(evt) {
-  var map = this.getMap();
-  if (map) {
-    map.skipFeature(/** @type {ol.Feature} */ (evt.element));
+ol.structs.LinkedList.prototype.getNextItem = function() {
+  if (this.head_ && this.head_.next) {
+    return this.head_.next.data;
   }
+  return undefined;
 };
 
-
 /**
- * @param {ol.Collection.Event} evt Event.
- * @private
+ * Sets the cursor to the previous item, and returns the associated data.
+ *
+ * @return {?} Item data.
  */
-ol.interaction.Select.prototype.removeFeature_ = function(evt) {
-  var map = this.getMap();
-  if (map) {
-    map.unskipFeature(/** @type {ol.Feature} */ (evt.element));
+ol.structs.LinkedList.prototype.prevItem = function() {
+  if (this.head_ && this.head_.prev) {
+    this.head_ = this.head_.prev;
+    return this.head_.data;
   }
+  return undefined;
 };
 
-
 /**
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @private
+ * Returns the previous item's data without moving the cursor.
+ *
+ * @return {?} Item data.
  */
-ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = function(feature) {
-  var key = ol.getUid(feature);
-  delete this.featureLayerAssociation_[key];
+ol.structs.LinkedList.prototype.getPrevItem = function() {
+  if (this.head_ && this.head_.prev) {
+    return this.head_.prev.data;
+  }
+  return undefined;
 };
 
-
 /**
- * @classdesc
- * Events emitted by {@link ol.interaction.Select} instances are instances of
- * this type.
+ * Returns the current item's data.
  *
- * @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
+ * @return {?} Item data.
  */
-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;
-
-  /**
-   * Deselected features array.
-   * @type {Array.<ol.Feature>}
-   * @api
-   */
-  this.deselected = deselected;
-
-  /**
-   * Associated {@link ol.MapBrowserEvent}.
-   * @type {ol.MapBrowserEvent}
-   * @api
-   */
-  this.mapBrowserEvent = mapBrowserEvent;
+ol.structs.LinkedList.prototype.getCurrItem = function() {
+  if (this.head_) {
+    return this.head_.data;
+  }
+  return undefined;
 };
-ol.inherits(ol.interaction.Select.Event, ol.events.Event);
-
 
 /**
- * @enum {string}
- * @private
+ * Sets the first item of the list. This only works for circular lists, and sets
+ * the last item accordingly.
  */
-ol.interaction.Select.EventType_ = {
-  /**
-   * Triggered when feature(s) has been (de)selected.
-   * @event ol.interaction.Select.Event#select
-   * @api
-   */
-  SELECT: 'select'
+ol.structs.LinkedList.prototype.setFirstItem = function() {
+  if (this.circular_ && this.head_) {
+    this.first_ = this.head_;
+    this.last_ = this.head_.prev;
+  }
 };
 
-goog.provide('ol.interaction.Snap');
-
-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');
-
-
 /**
- * @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
+ * Concatenates two lists.
+ * @param {ol.structs.LinkedList} list List to merge into the current list.
  */
-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 : {};
-
-  /**
-   * @type {ol.source.Vector}
-   * @private
-   */
-  this.source_ = options.source ? options.source : null;
+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 {
+      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;
+  }
+};
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.vertex_ = options.vertex !== undefined ? options.vertex : true;
+/**
+ * Returns the current length of the list.
+ *
+ * @return {number} Length.
+ */
+ol.structs.LinkedList.prototype.getLength = function() {
+  return this.length_;
+};
 
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.edge_ = options.edge !== undefined ? options.edge : true;
+goog.provide('ol.render.webgl.PolygonReplay');
 
-  /**
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
-   */
-  this.features_ = options.features ? options.features : null;
+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.polygonreplay.defaultshader.Locations');
+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');
 
-  /**
-   * @type {Array.<ol.EventsKey>}
-   * @private
-   */
-  this.featuresListenerKeys_ = [];
 
-  /**
-   * @type {Object.<number, ol.EventsKey>}
-   * @private
-   */
-  this.featureChangeListenerKeys_ = {};
+/**
+ * @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);
 
-  /**
-   * Extents are preserved so indexed segment can be quickly removed
-   * when its feature geometry changes
-   * @type {Object.<number, ol.Extent>}
-   * @private
-   */
-  this.indexedFeaturesExtents_ = {};
+  this.lineStringReplay = new ol.render.webgl.LineStringReplay(
+      tolerance, maxExtent);
 
   /**
-   * 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
+   * @type {ol.render.webgl.polygonreplay.defaultshader.Locations}
    */
-  this.pendingFeatures_ = {};
+  this.defaultLocations_ = null;
 
   /**
-   * Used for distance sorting in sortByDistance_
-   * @type {ol.Coordinate}
    * @private
+   * @type {Array.<Array.<number>>}
    */
-  this.pixelCoordinate_ = null;
+  this.styles_ = [];
 
   /**
-   * @type {number}
    * @private
+   * @type {Array.<number>}
    */
-  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
-      options.pixelTolerance : 10;
+  this.styleIndices_ = [];
 
   /**
-   * @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number}
    * @private
+   * @type {{fillColor: (Array.<number>|null),
+   *         changed: boolean}|null}
    */
-  this.sortByDistance_ = ol.interaction.Snap.sortByDistance.bind(this);
-
-
-  /**
-  * Segment RTree for each layer
-  * @type {ol.structs.RBush.<ol.SnapSegmentDataType>}
-  * @private
-  */
-  this.rBush_ = new ol.structs.RBush();
-
-
-  /**
-  * @const
-  * @private
-  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>}
-  */
-  this.SEGMENT_WRITERS_ = {
-    'Point': this.writePointGeometry_,
-    'LineString': this.writeLineStringGeometry_,
-    'LinearRing': this.writeLineStringGeometry_,
-    'Polygon': this.writePolygonGeometry_,
-    'MultiPoint': this.writeMultiPointGeometry_,
-    'MultiLineString': this.writeMultiLineStringGeometry_,
-    'MultiPolygon': this.writeMultiPolygonGeometry_,
-    'GeometryCollection': this.writeGeometryCollectionGeometry_,
-    'Circle': this.writeCircleGeometry_
+  this.state_ = {
+    fillColor: null,
+    changed: false
   };
+
 };
-ol.inherits(ol.interaction.Snap, ol.interaction.Pointer);
+ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay);
 
 
 /**
- * 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
+ * Draw one polygon.
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates.
+ * @param {number} stride Stride.
+ * @private
  */
-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);
+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
+  this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true);
+  var maxCoords = this.getMaxCoords_(outerRing);
+
+  // 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(),
+        maxCoords: undefined,
+        rtree: new ol.structs.RBush()
+      };
+      holeLists.push(holeList);
+      this.processFlatCoordinates_(holeFlatCoordinates[i],
+          stride, holeList.list, holeList.rtree, false);
+      this.classifyPoints_(holeList.list, holeList.rtree, true);
+      holeList.maxCoords = this.getMaxCoords_(holeList.list);
+    }
+    holeLists.sort(function(a, b) {
+      return b.maxCoords[0] === a.maxCoords[0] ?
+        a.maxCoords[1] - b.maxCoords[1] : b.maxCoords[0] - a.maxCoords[0];
+    });
+    for (i = 0; i < holeLists.length; ++i) {
+      var currList = holeLists[i].list;
+      var start = currList.firstItem();
+      var currItem = start;
+      var intersection;
+      do {
+        //TODO: Triangulate holes when they intersect the outer ring.
+        if (this.getIntersections_(currItem, rtree).length) {
+          intersection = true;
+          break;
+        }
+        currItem = currList.nextItem();
+      } while (start !== currItem);
+      if (!intersection) {
+        if (this.bridgeHole_(currList, holeLists[i].maxCoords[0], outerRing, maxCoords[0], rtree)) {
+          rtree.concat(holeLists[i].rtree);
+          this.classifyPoints_(outerRing, rtree, false);
+        }
+      }
     }
+  } else {
+    this.classifyPoints_(outerRing, rtree, false);
   }
+  this.triangulate_(outerRing, rtree);
+};
 
-  if (listen) {
-    this.featureChangeListenerKeys_[feature_uid] = ol.events.listen(
-        feature,
-        ol.events.EventType.CHANGE,
-        this.handleFeatureChange_, this);
+
+/**
+ * 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.
+ */
+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;
+  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;
+    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)]);
+      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 {
+    var end = flatCoordinates.length - stride;
+    start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++);
+    p0 = start;
+    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)]);
+      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);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
+ * Returns the rightmost coordinates of a polygon on the X axis.
  * @private
+ * @param {ol.structs.LinkedList} list Polygons ring.
+ * @return {Array.<number>} Max X coordinates.
  */
-ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
-  this.addFeature(feature);
+ol.render.webgl.PolygonReplay.prototype.getMaxCoords_ = function(list) {
+  var start = list.firstItem();
+  var seg = start;
+  var maxCoords = [seg.p0.x, seg.p0.y];
+
+  do {
+    seg = list.nextItem();
+    if (seg.p0.x > maxCoords[0]) {
+      maxCoords = [seg.p0.x, seg.p0.y];
+    }
+  } while (seg !== start);
+
+  return maxCoords;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature.
+ * 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.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
-  this.removeFeature(feature);
+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;
 };
 
 
 /**
- * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} Features.
  * @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.interaction.Snap.prototype.getFeatures_ = function() {
-  var features;
-  if (this.features_) {
-    features = this.features_;
-  } else if (this.source_) {
-    features = this.source_.getFeatures();
+ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX,
+    list, listMaxX, rtree) {
+  var seg = hole.firstItem();
+  while (seg.p1.x !== holeMaxX) {
+    seg = hole.nextItem();
   }
-  return /** @type {!Array.<ol.Feature>|!ol.Collection.<ol.Feature>} */ (features);
+
+  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 (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;
+        }
+      }
+    }
+  }
+
+  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};
+
+  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;
 };
 
 
 /**
- * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
  * @private
+ * @param {ol.structs.LinkedList} list Linked list of the polygon.
+ * @param {ol.structs.RBush} rtree R-Tree of the polygon.
  */
-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;
+ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) {
+  var ccw = false;
+  var simple = this.isSimple_(list, rtree);
+
+  // 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.resolveSelfIntersections_(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.resolveSelfIntersections_(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);
+            }
+          }
+        }
+      }
+    }
+  }
+  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;
   }
-  this.addFeature(/** @type {ol.Feature} */ (feature));
 };
 
 
 /**
- * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
  * @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.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));
+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 variableCriterion;
+      if (simple) {
+        variableCriterion = this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0;
+      } else {
+        variableCriterion = 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) &&
+          variableCriterion) {
+        //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;
 };
 
 
 /**
- * @param {ol.events.Event} evt Event.
  * @private
- */
-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;
+ * @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.resolveSelfIntersections_ = function(
+    list, rtree, opt_touch) {
+  var start = list.firstItem();
+  list.nextItem();
+  var s0 = start;
+  var s1 = list.nextItem();
+  var resolvedIntersections = false;
+
+  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;
+
+      resolvedIntersections = true;
+      if (breakCond) {
+        break;
+      }
     }
-  } else {
-    this.updateFeature_(feature);
-  }
+
+    s0 = list.getPrevItem();
+    s1 = list.nextItem();
+  } while (s0 !== start);
+  return resolvedIntersections;
 };
 
 
 /**
- * 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
+ * @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.
  */
-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]);
+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;
     }
-  }
-
-  if (unlisten) {
-    ol.events.unlistenByKey(this.featureChangeListenerKeys_[feature_uid]);
-    delete this.featureChangeListenerKeys_[feature_uid];
-  }
+    seg = list.nextItem();
+  } while (seg !== start);
+  return true;
 };
 
 
 /**
- * @inheritDoc
+ * @private
+ * @param {ol.structs.LinkedList} list Linked list of the polygon.
+ * @return {boolean} Orientation is clockwise.
  */
-ol.interaction.Snap.prototype.setMap = function(map) {
-  var currentMap = this.getMap();
-  var keys = this.featuresListenerKeys_;
-  var features = this.getFeatures_();
+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);
+};
 
-  if (currentMap) {
-    keys.forEach(ol.events.unlistenByKey);
-    keys.length = 0;
-    features.forEach(this.forEachFeatureRemove_, this);
-  }
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
 
-  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)
-      );
+/**
+ * @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;
     }
-    features.forEach(this.forEachFeatureAdd_, this);
-  }
+    s0 = list.nextItem();
+  } while (s0 !== start);
 };
 
 
 /**
- * @inheritDoc
- */
-ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE;
+ * @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;
+};
 
 
 /**
- * @param {ol.Pixel} pixel Pixel
- * @param {ol.Coordinate} pixelCoordinate Coordinate
- * @param {ol.Map} map Map.
- * @return {ol.SnapResultType} Snap result
+ * @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.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]);
+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;
+};
 
-  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;
-    });
+/**
+  * @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);
   }
+};
 
-  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);
-          }
+
+/**
+ * @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);
         }
       }
     }
-    if (snapped) {
-      vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
-    }
   }
-  return /** @type {ol.SnapResultType} */ ({
-    snapped: snapped,
-    vertex: vertex,
-    vertexPixel: vertexPixel
-  });
+  return result;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
  * @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.interaction.Snap.prototype.updateFeature_ = function(feature) {
-  this.removeFeature(feature, false);
-  this.addFeature(feature, false);
+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;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Circle} geometry Geometry.
+ * 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.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);
+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;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.GeometryCollection} geometry Geometry.
  * @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.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]);
+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;
+};
+
+
+/**
+ * @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);
+  }
 };
 
 
 /**
- * @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.SnapSegmentDataType} */ ({
-      feature: feature,
-      segment: segment
-    });
-    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
+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);
+
+      this.lineStringReplay.drawPolygonCoordinates(outerRing, holes, stride);
+      this.drawCoordinates_(outerRing, holes, stride);
+    }
   }
 };
 
 
 /**
- * @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);
-    }
+ * @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);
+
+  this.startIndices.push(this.indices.length);
+
+  this.lineStringReplay.finish(context);
+
+  //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;
 };
 
 
 /**
- * @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.SnapSegmentDataType} */ ({
-      feature: feature,
-      segment: [coordinates, coordinates]
-    });
-    this.rBush_.insert(geometry.getExtent(), segmentData);
-  }
+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();
+  };
 };
 
 
 /**
- * @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.SnapSegmentDataType} */ ({
-          feature: feature,
-          segment: segment
-        });
-        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-      }
-    }
+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_) {
+    locations = new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program);
+    this.defaultLocations_ = locations;
+  } else {
+    locations = this.defaultLocations_;
   }
+
+  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);
+
+  return locations;
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Point} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
+ol.render.webgl.PolygonReplay.prototype.shutDownProgram = function(gl, locations) {
+  gl.disableVertexAttribArray(locations.a_position);
 };
 
 
 /**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Polygon} geometry Geometry.
- * @private
+ * @inheritDoc
  */
-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);
+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);
+  }
+
+  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);
+  }
 };
 
 
 /**
- * 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.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);
+
+        if (result) {
+          return result;
+        }
+
+      }
+      featureIndex--;
+      end = start;
+    }
   }
-  return ol.interaction.Pointer.handleEvent.call(this, evt);
+  return undefined;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Snap}
  * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
  */
-ol.interaction.Snap.handleUpEvent_ = function(evt) {
-  var featuresToUpdate = ol.obj.getValues(this.pendingFeatures_);
-  if (featuresToUpdate.length) {
-    featuresToUpdate.forEach(this.updateFeature_, this);
-    this.pendingFeatures_ = {};
+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];
+
+    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;
   }
-  return false;
 };
 
 
 /**
- * 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}
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {Array.<number>} color Color.
  */
-ol.interaction.Snap.sortByDistance = function(a, b) {
-  return ol.coordinate.squaredDistanceToSegment(
-      this.pixelCoordinate_, a.segment) -
-      ol.coordinate.squaredDistanceToSegment(
-      this.pixelCoordinate_, b.segment);
+ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) {
+  gl.uniform4fv(this.defaultLocations_.u_color, color);
 };
 
-goog.provide('ol.interaction.TranslateEventType');
-
 
 /**
- * @enum {string}
+ * @inheritDoc
  */
-ol.interaction.TranslateEventType = {
-  /**
-   * Triggered upon feature translation start.
-   * @event ol.interaction.Translate.Event#translatestart
-   * @api
-   */
-  TRANSLATESTART: 'translatestart',
-  /**
-   * Triggered upon feature translation.
-   * @event ol.interaction.Translate.Event#translating
-   * @api
-   */
-  TRANSLATING: 'translating',
-  /**
-   * Triggered upon feature translation end.
-   * @event ol.interaction.Translate.Event#translateend
-   * @api
-   */
-  TRANSLATEEND: 'translateend'
+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);
+  }
 };
 
-goog.provide('ol.interaction.Translate');
+goog.provide('ol.style.Atlas');
 
-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');
+goog.require('ol.dom');
 
 
 /**
- * @classdesc
- * Interaction for translating (moving) features.
+ * This class facilitates the creation of image atlases.
+ *
+ * Images added to an atlas will be rendered onto a single
+ * atlas canvas. The distribution of images on the canvas is
+ * managed with the bin packing algorithm described in:
+ * http://www.blackpawn.com/texts/lightmaps/
  *
  * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.interaction.Translate.Event
- * @param {olx.interaction.TranslateOptions=} opt_options Options.
- * @api
+ * @struct
+ * @param {number} size The size in pixels of the sprite image.
+ * @param {number} space The space in pixels between images.
+ *    Because texture coordinates are float values, the edges of
+ *    images might not be completely correct (in a way that the
+ *    edges overlap when being rendered). To avoid this we add a
+ *    padding around each image.
  */
-ol.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 : {};
+ol.style.Atlas = function(size, space) {
 
   /**
-   * The last position we translated to.
-   * @type {ol.Coordinate}
    * @private
+   * @type {number}
    */
-  this.lastCoordinate_ = null;
-
+  this.space_ = space;
 
   /**
-   * @type {ol.Collection.<ol.Feature>}
    * @private
+   * @type {Array.<ol.AtlasBlock>}
    */
-  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;
-  }
+  this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
 
   /**
    * @private
-   * @type {function(ol.layer.Layer): boolean}
+   * @type {Object.<string, ol.AtlasInfo>}
    */
-  this.layerFilter_ = layerFilter;
+  this.entries_ = {};
 
   /**
    * @private
-   * @type {number}
+   * @type {CanvasRenderingContext2D}
    */
-  this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0;
+  this.context_ = ol.dom.createCanvasContext2D(size, size);
 
   /**
-   * @type {ol.Feature}
    * @private
+   * @type {HTMLCanvasElement}
    */
-  this.lastFeature_ = null;
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.interaction.Property.ACTIVE),
-      this.handleActiveChanged_, this);
-
+  this.canvas_ = this.context_.canvas;
 };
-ol.inherits(ol.interaction.Translate, ol.interaction.Pointer);
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Translate}
- * @private
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.AtlasInfo} The atlas info.
  */
-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_]);
-
-    this.dispatchEvent(
-        new ol.interaction.Translate.Event(
-            ol.interaction.TranslateEventType.TRANSLATESTART, features,
-            event.coordinate));
-    return true;
-  }
-  return false;
+ol.style.Atlas.prototype.get = function(id) {
+  return this.entries_[id] || null;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Translate}
- * @private
+ * @param {string} id The identifier of the entry to add.
+ * @param {number} width The width.
+ * @param {number} height The height.
+ * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
+ *    Called to render the new image onto an atlas image.
+ * @param {Object=} opt_this Value to use as `this` when executing
+ *    `renderCallback`.
+ * @return {?ol.AtlasInfo} The position and atlas image for the entry.
  */
-ol.interaction.Translate.handleUpEvent_ = function(event) {
-  if (this.lastCoordinate_) {
-    this.lastCoordinate_ = null;
-    ol.interaction.Translate.handleMoveEvent_.call(this, event);
+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;
 
-    var features = this.features_ || new ol.Collection([this.lastFeature_]);
+      // render the image on the atlas image
+      renderCallback.call(opt_this, this.context_,
+          block.x + this.space_, block.y + this.space_);
 
-    this.dispatchEvent(
-        new ol.interaction.Translate.Event(
-            ol.interaction.TranslateEventType.TRANSLATEEND, features,
-            event.coordinate));
-    return true;
+      // split the block after the insertion, either horizontally or vertically
+      this.split_(i, block, width + this.space_, height + this.space_);
+
+      return entry;
+    }
   }
-  return false;
+
+  // there is no space for the new entry in this atlas
+  return null;
 };
 
 
 /**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @this {ol.interaction.Translate}
  * @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.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.style.Atlas.prototype.split_ = function(index, block, width, height) {
+  var deltaWidth = block.width - width;
+  var deltaHeight = block.height - height;
 
-    var features = this.features_ || new ol.Collection([this.lastFeature_]);
+  /** @type {ol.AtlasBlock} */
+  var newBlock1;
+  /** @type {ol.AtlasBlock} */
+  var newBlock2;
 
-    features.forEach(function(feature) {
-      var geom = feature.getGeometry();
-      geom.translate(deltaX, deltaY);
-      feature.setGeometry(geom);
-    });
+  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
+    };
 
-    this.lastCoordinate_ = newCoordinate;
-    this.dispatchEvent(
-        new ol.interaction.Translate.Event(
-            ol.interaction.TranslateEventType.TRANSLATING, features,
-            newCoordinate));
+    // 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);
   }
 };
 
 
 /**
- * @param {ol.MapBrowserEvent} event Event.
- * @this {ol.interaction.Translate}
+ * 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.AtlasBlock} newBlock1 The 1st block to add.
+ * @param {ol.AtlasBlock} newBlock2 The 2nd block to add.
  */
-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');
+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);
 };
 
+goog.provide('ol.style.AtlasManager');
 
-/**
- * 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) {
-  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_
-      });
-};
+goog.require('ol');
+goog.require('ol.style.Atlas');
 
 
 /**
- * Returns the Hit-detection tolerance.
- * @returns {number} Hit tolerance in pixels.
+ * 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.interaction.Translate.prototype.getHitTolerance = function() {
-  return this.hitTolerance_;
+ol.style.AtlasManager = function(opt_options) {
+
+  var options = opt_options || {};
+
+  /**
+   * The size in pixels of the latest atlas image.
+   * @private
+   * @type {number}
+   */
+  this.currentSize_ = options.initialSize !== undefined ?
+    options.initialSize : ol.INITIAL_ATLAS_SIZE;
+
+  /**
+   * The maximum size in pixels of atlas images.
+   * @private
+   * @type {number}
+   */
+  this.maxSize_ = options.maxSize !== undefined ?
+    options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
+      ol.MAX_ATLAS_SIZE : ol.WEBGL_MAX_TEXTURE_SIZE !== undefined ?
+        ol.WEBGL_MAX_TEXTURE_SIZE : 2048;
+
+  /**
+   * The size in pixels between images.
+   * @private
+   * @type {number}
+   */
+  this.space_ = options.space !== undefined ? options.space : 1;
+
+  /**
+   * @private
+   * @type {Array.<ol.style.Atlas>}
+   */
+  this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
+
+  /**
+   * The size in pixels of the latest atlas image for hit-detection images.
+   * @private
+   * @type {number}
+   */
+  this.currentHitSize_ = this.currentSize_;
+
+  /**
+   * @private
+   * @type {Array.<ol.style.Atlas>}
+   */
+  this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
 };
 
 
 /**
- * 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
+ * @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.interaction.Translate.prototype.setHitTolerance = function(hitTolerance) {
-  this.hitTolerance_ = hitTolerance;
-};
+ol.style.AtlasManager.prototype.getInfo = function(id) {
+  /** @type {?ol.AtlasInfo} */
+  var info = this.getInfo_(this.atlases_, id);
 
+  if (!info) {
+    return null;
+  }
+  var hitInfo = /** @type {ol.AtlasInfo} */ (this.getInfo_(this.hitAtlases_, id));
 
-/**
- * @inheritDoc
- */
-ol.interaction.Translate.prototype.setMap = function(map) {
-  var oldMap = this.getMap();
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
-  this.updateState_(oldMap);
+  return this.mergeInfos_(info, hitInfo);
 };
 
 
 /**
  * @private
+ * @param {Array.<ol.style.Atlas>} atlases The atlases to search.
+ * @param {string} id The identifier of the entry to check.
+ * @return {?ol.AtlasInfo} The position and atlas image for the entry,
+ *    or `null` if the entry is not part of the atlases.
  */
-ol.interaction.Translate.prototype.handleActiveChanged_ = function() {
-  this.updateState_(null);
+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;
 };
 
 
 /**
- * @param {ol.Map} oldMap Old map.
  * @private
+ * @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.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');
-  }
+ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
+  return /** @type {ol.AtlasManagerInfo} */ ({
+    offsetX: info.offsetX,
+    offsetY: info.offsetY,
+    image: info.image,
+    hitImage: hitInfo.image
+  });
 };
 
 
 /**
- * @classdesc
- * Events emitted by {@link ol.interaction.Translate} instances are instances of
- * this type.
+ * Add an image to the atlas manager.
  *
- * @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.
+ * 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.interaction.Translate.Event = function(type, features, coordinate) {
+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.events.Event.call(this, type);
+  /** @type {?ol.AtlasInfo} */
+  var info = this.add_(false,
+      id, width, height, renderCallback, opt_this);
+  if (!info) {
+    return null;
+  }
 
-  /**
-   * The features being translated.
-   * @type {ol.Collection.<ol.Feature>}
-   * @api
-   */
-  this.features = features;
+  // 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;
 
-  /**
-   * The coordinate of the drag event.
-   * @const
-   * @type {ol.Coordinate}
-   * @api
-   */
-  this.coordinate = coordinate;
+  var hitInfo = /** @type {ol.AtlasInfo} */ (this.add_(true,
+      id, width, height, renderHitCallback, opt_this));
+
+  return this.mergeInfos_(info, hitInfo);
 };
-ol.inherits(ol.interaction.Translate.Event, ol.events.Event);
 
-goog.provide('ol.layer.Heatmap');
 
-goog.require('ol.events');
+/**
+ * @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.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 null;
+};
+
+goog.provide('ol.render.webgl.TextReplay');
+
 goog.require('ol');
-goog.require('ol.Object');
+goog.require('ol.colorlike');
 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');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.has');
+goog.require('ol.render.replay');
+goog.require('ol.render.webgl');
+goog.require('ol.render.webgl.TextureReplay');
+goog.require('ol.style.AtlasManager');
+goog.require('ol.webgl.Buffer');
 
 
 /**
- * @classdesc
- * Layer for rendering vector data as a heatmap.
- * Note that any property set in the options is set as a {@link ol.Object}
- * property on the layer object; for example, setting `title: 'My Title'` in the
- * options means that `title` is observable, and has get/set accessors.
- *
  * @constructor
- * @extends {ol.layer.Vector}
- * @fires ol.render.Event
- * @param {olx.layer.HeatmapOptions=} opt_options Options.
- * @api
+ * @extends {ol.render.webgl.TextureReplay}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @struct
  */
-ol.layer.Heatmap = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  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));
+ol.render.webgl.TextReplay = function(tolerance, maxExtent) {
+  ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent);
 
   /**
    * @private
-   * @type {Uint8ClampedArray}
+   * @type {Array.<HTMLCanvasElement>}
    */
-  this.gradient_ = null;
+  this.images_ = [];
 
   /**
    * @private
-   * @type {number}
+   * @type {Array.<WebGLTexture>}
    */
-  this.shadow_ = options.shadow !== undefined ? options.shadow : 250;
+  this.textures_ = [];
 
   /**
    * @private
-   * @type {string|undefined}
+   * @type {HTMLCanvasElement}
    */
-  this.circleImage_ = undefined;
+  this.measureCanvas_ = ol.dom.createCanvasContext2D(0, 0).canvas;
 
   /**
    * @private
-   * @type {Array.<Array.<ol.style.Style>>}
+   * @type {{strokeColor: (ol.ColorLike|null),
+   *         lineCap: (string|undefined),
+   *         lineDash: Array.<number>,
+   *         lineDashOffset: (number|undefined),
+   *         lineJoin: (string|undefined),
+   *         lineWidth: number,
+   *         miterLimit: (number|undefined),
+   *         fillColor: (ol.ColorLike|null),
+   *         font: (string|undefined),
+   *         scale: (number|undefined)}}
    */
-  this.styleCache_ = null;
+  this.state_ = {
+    strokeColor: null,
+    lineCap: undefined,
+    lineDash: null,
+    lineDashOffset: undefined,
+    lineJoin: undefined,
+    lineWidth: 0,
+    miterLimit: undefined,
+    fillColor: null,
+    font: undefined,
+    scale: undefined
+  };
 
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.Heatmap.Property_.GRADIENT),
-      this.handleGradientChanged_, this);
+  /**
+   * @private
+   * @type {string}
+   */
+  this.text_ = '';
 
-  this.setGradient(options.gradient ?
-      options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.textAlign_ = undefined;
 
-  this.setBlur(options.blur !== undefined ? options.blur : 15);
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.textBaseline_ = undefined;
 
-  this.setRadius(options.radius !== undefined ? options.radius : 8);
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.offsetX_ = undefined;
 
-  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);
+  /**
+   * @private
+   * @type {number|undefined}
+   */
+  this.offsetY_ = undefined;
 
-  this.handleStyleChanged_();
+  /**
+   * @private
+   * @type {Object.<string, ol.WebglGlyphAtlas>}
+   */
+  this.atlases_ = {};
 
-  var weight = options.weight ? options.weight : 'weight';
-  var weightFunction;
-  if (typeof weight === 'string') {
-    weightFunction = function(feature) {
-      return feature.get(weight);
-    };
-  } else {
-    weightFunction = weight;
-  }
+  /**
+   * @private
+   * @type {ol.WebglGlyphAtlas|undefined}
+   */
+  this.currAtlas_ = undefined;
 
-  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));
+  this.scale = 1;
 
-  // For performance reasons, don't sort the features before rendering.
-  // The render order is not relevant for a heatmap representation.
-  this.setRenderOrder(null);
+  this.opacity = 1;
 
-  ol.events.listen(this, ol.render.EventType.RENDER, this.handleRender_, this);
 };
-ol.inherits(ol.layer.Heatmap, ol.layer.Vector);
+ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.TextureReplay);
 
 
 /**
- * @const
- * @type {Array.<string>}
+ * @inheritDoc
  */
-ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
-
+ol.render.webgl.TextReplay.prototype.drawText = function(geometry, feature) {
+  if (this.text_) {
+    var flatCoordinates = null;
+    var offset = 0;
+    var end = 2;
+    var stride = 2;
+    switch (geometry.getType()) {
+      case ol.geom.GeometryType.POINT:
+      case ol.geom.GeometryType.MULTI_POINT:
+        flatCoordinates = geometry.getFlatCoordinates();
+        end = flatCoordinates.length;
+        stride = geometry.getStride();
+        break;
+      case ol.geom.GeometryType.CIRCLE:
+        flatCoordinates = /** @type {ol.geom.Circle} */ (geometry).getCenter();
+        break;
+      case ol.geom.GeometryType.LINE_STRING:
+        flatCoordinates = /** @type {ol.geom.LineString} */ (geometry).getFlatMidpoint();
+        break;
+      case ol.geom.GeometryType.MULTI_LINE_STRING:
+        flatCoordinates = /** @type {ol.geom.MultiLineString} */ (geometry).getFlatMidpoints();
+        end = flatCoordinates.length;
+        break;
+      case ol.geom.GeometryType.POLYGON:
+        flatCoordinates = /** @type {ol.geom.Polygon} */ (geometry).getFlatInteriorPoint();
+        break;
+      case ol.geom.GeometryType.MULTI_POLYGON:
+        flatCoordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getFlatInteriorPoints();
+        end = flatCoordinates.length;
+        break;
+      default:
+    }
+    this.startIndices.push(this.indices.length);
+    this.startIndicesFeature.push(feature);
 
-/**
- * @param {Array.<string>} colors A list of colored.
- * @return {Uint8ClampedArray} An array.
- * @private
- */
-ol.layer.Heatmap.createGradient_ = function(colors) {
-  var width = 1;
-  var height = 256;
-  var context = ol.dom.createCanvasContext2D(width, height);
+    var glyphAtlas = this.currAtlas_;
+    var lines = this.text_.split('\n');
+    var textSize = this.getTextSize_(lines);
+    var i, ii, j, jj, currX, currY, charArr, charInfo;
+    var anchorX = Math.round(textSize[0] * this.textAlign_ - this.offsetX_);
+    var anchorY = Math.round(textSize[1] * this.textBaseline_ - this.offsetY_);
+    var lineWidth = (this.state_.lineWidth / 2) * this.state_.scale;
+
+    for (i = 0, ii = lines.length; i < ii; ++i) {
+      currX = 0;
+      currY = glyphAtlas.height * i;
+      charArr = lines[i].split('');
+
+      for (j = 0, jj = charArr.length; j < jj; ++j) {
+        charInfo = glyphAtlas.atlas.getInfo(charArr[j]);
+
+        if (charInfo) {
+          var image = charInfo.image;
+
+          this.anchorX = anchorX - currX;
+          this.anchorY = anchorY - currY;
+          this.originX = j === 0 ? charInfo.offsetX - lineWidth : charInfo.offsetX;
+          this.originY = charInfo.offsetY;
+          this.height = glyphAtlas.height;
+          this.width = j === 0 || j === charArr.length - 1 ?
+            glyphAtlas.width[charArr[j]] + lineWidth : glyphAtlas.width[charArr[j]];
+          this.imageHeight = image.height;
+          this.imageWidth = image.width;
+
+          var currentImage;
+          if (this.images_.length === 0) {
+            this.images_.push(image);
+          } else {
+            currentImage = this.images_[this.images_.length - 1];
+            if (ol.getUid(currentImage) != ol.getUid(image)) {
+              this.groupIndices.push(this.indices.length);
+              this.images_.push(image);
+            }
+          }
 
-  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]);
+          this.drawText_(flatCoordinates, offset, end, stride);
+        }
+        currX += this.width;
+      }
+    }
   }
-
-  context.fillStyle = gradient;
-  context.fillRect(0, 0, width, height);
-
-  return context.getImageData(0, 0, width, height).data;
 };
 
 
 /**
- * @return {string} Data URL for a circle.
  * @private
+ * @param {Array.<string>} lines Label to draw split to lines.
+ * @return {Array.<number>} Size of the label in pixels.
  */
-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();
-};
-
-
-/**
- * Return the blur size in pixels.
- * @return {number} Blur size in pixels.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.getBlur = function() {
-  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property_.BLUR));
-};
-
-
-/**
- * Return the gradient colors as array of strings.
- * @return {Array.<string>} Colors.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.getGradient = function() {
-  return /** @type {Array.<string>} */ (
-      this.get(ol.layer.Heatmap.Property_.GRADIENT));
-};
-
+ol.render.webgl.TextReplay.prototype.getTextSize_ = function(lines) {
+  var self = this;
+  var glyphAtlas = this.currAtlas_;
+  var textHeight = lines.length * glyphAtlas.height;
+  //Split every line to an array of chars, sum up their width, and select the longest.
+  var textWidth = lines.map(function(str) {
+    var sum = 0;
+    var i, ii;
+    for (i = 0, ii = str.length; i < ii; ++i) {
+      var curr = str[i];
+      if (!glyphAtlas.width[curr]) {
+        self.addCharToAtlas_(curr);
+      }
+      sum += glyphAtlas.width[curr] ? glyphAtlas.width[curr] : 0;
+    }
+    return sum;
+  }).reduce(function(max, curr) {
+    return Math.max(max, curr);
+  });
 
-/**
- * Return the size of the radius in pixels.
- * @return {number} Radius size in pixel.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.getRadius = function() {
-  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property_.RADIUS));
+  return [textWidth, textHeight];
 };
 
 
 /**
  * @private
+ * @param {Array.<number>} flatCoordinates Flat coordinates.
+ * @param {number} offset Offset.
+ * @param {number} end End.
+ * @param {number} stride Stride.
  */
-ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
-  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
+ol.render.webgl.TextReplay.prototype.drawText_ = function(flatCoordinates, offset,
+    end, stride) {
+  var i, ii;
+  for (i = offset, ii = end; i < ii; i += stride) {
+    this.drawCoordinates(flatCoordinates, offset, end, stride);
+  }
 };
 
 
 /**
  * @private
+ * @param {string} char Character.
  */
-ol.layer.Heatmap.prototype.handleStyleChanged_ = function() {
-  this.circleImage_ = this.createCircle_();
-  this.styleCache_ = new Array(256);
-  this.changed();
-};
+ol.render.webgl.TextReplay.prototype.addCharToAtlas_ = function(char) {
+  if (char.length === 1) {
+    var glyphAtlas = this.currAtlas_;
+    var state = this.state_;
+    var mCtx = this.measureCanvas_.getContext('2d');
+    mCtx.font = state.font;
+    var width = Math.ceil(mCtx.measureText(char).width * state.scale);
 
+    var info = glyphAtlas.atlas.add(char, width, glyphAtlas.height,
+        function(ctx, x, y) {
+          //Parameterize the canvas
+          ctx.font = /** @type {string} */ (state.font);
+          ctx.fillStyle = state.fillColor;
+          ctx.strokeStyle = state.strokeColor;
+          ctx.lineWidth = state.lineWidth;
+          ctx.lineCap = /*** @type {string} */ (state.lineCap);
+          ctx.lineJoin = /** @type {string} */ (state.lineJoin);
+          ctx.miterLimit = /** @type {number} */ (state.miterLimit);
+          ctx.textAlign = 'left';
+          ctx.textBaseline = 'top';
+          if (ol.has.CANVAS_LINE_DASH && state.lineDash) {
+            //FIXME: use pixelRatio
+            ctx.setLineDash(state.lineDash);
+            ctx.lineDashOffset = /** @type {number} */ (state.lineDashOffset);
+          }
+          if (state.scale !== 1) {
+            //FIXME: use pixelRatio
+            ctx.setTransform(/** @type {number} */ (state.scale), 0, 0,
+                /** @type {number} */ (state.scale), 0, 0);
+          }
 
-/**
- * @param {ol.render.Event} event Post compose event
- * @private
- */
-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];
+          //Draw the character on the canvas
+          if (state.strokeColor) {
+            ctx.strokeText(char, x, y);
+          }
+          if (state.fillColor) {
+            ctx.fillText(char, x, y);
+          }
+        });
+
+    if (info) {
+      glyphAtlas.width[char] = width;
     }
   }
-  context.putImageData(image, 0, 0);
-};
-
-
-/**
- * Set the blur size in pixels.
- * @param {number} blur Blur size in pixels.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.setBlur = function(blur) {
-  this.set(ol.layer.Heatmap.Property_.BLUR, blur);
-};
-
-
-/**
- * Set the gradient colors as array of strings.
- * @param {Array.<string>} colors Gradient.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.setGradient = function(colors) {
-  this.set(ol.layer.Heatmap.Property_.GRADIENT, colors);
-};
-
-
-/**
- * Set the size of the radius in pixels.
- * @param {number} radius Radius size in pixel.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.setRadius = function(radius) {
-  this.set(ol.layer.Heatmap.Property_.RADIUS, radius);
 };
 
 
 /**
- * @enum {string}
- * @private
+ * @inheritDoc
  */
-ol.layer.Heatmap.Property_ = {
-  BLUR: 'blur',
-  GRADIENT: 'gradient',
-  RADIUS: 'radius'
-};
-
-goog.provide('ol.renderer.canvas.IntermediateCanvas');
-
-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');
+ol.render.webgl.TextReplay.prototype.finish = function(context) {
+  var gl = context.getGL();
 
+  this.groupIndices.push(this.indices.length);
+  this.hitDetectionGroupIndices = this.groupIndices;
 
-/**
- * @constructor
- * @abstract
- * @extends {ol.renderer.canvas.Layer}
- * @param {ol.layer.Layer} layer Layer.
- */
-ol.renderer.canvas.IntermediateCanvas = function(layer) {
+  // create, bind, and populate the vertices buffer
+  this.verticesBuffer = new ol.webgl.Buffer(this.vertices);
 
-  ol.renderer.canvas.Layer.call(this, layer);
+  // create, bind, and populate the indices buffer
+  this.indicesBuffer = new ol.webgl.Buffer(this.indices);
 
-  /**
-   * @protected
-   * @type {ol.Transform}
-   */
-  this.coordinateToCanvasPixelTransform = ol.transform.create();
+  // create textures
+  /** @type {Object.<string, WebGLTexture>} */
+  var texturePerImage = {};
 
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.hitCanvasContext_ = null;
+  this.createTextures(this.textures_, this.images_, texturePerImage, gl);
 
+  this.state_ = {
+    strokeColor: null,
+    lineCap: undefined,
+    lineDash: null,
+    lineDashOffset: undefined,
+    lineJoin: undefined,
+    lineWidth: 0,
+    miterLimit: undefined,
+    fillColor: null,
+    font: undefined,
+    scale: undefined
+  };
+  this.text_ = '';
+  this.textAlign_ = undefined;
+  this.textBaseline_ = undefined;
+  this.offsetX_ = undefined;
+  this.offsetY_ = undefined;
+  this.images_ = null;
+  this.atlases_ = {};
+  this.currAtlas_ = undefined;
+  ol.render.webgl.TextureReplay.prototype.finish.call(this, context);
 };
-ol.inherits(ol.renderer.canvas.IntermediateCanvas, ol.renderer.canvas.Layer);
 
 
 /**
  * @inheritDoc
  */
-ol.renderer.canvas.IntermediateCanvas.prototype.composeFrame = function(frameState, layerState, context) {
-
-  this.preCompose(context, frameState);
-
-  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));
+ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {
+  var state = this.state_;
+  var textFillStyle = textStyle.getFill();
+  var textStrokeStyle = textStyle.getStroke();
+  if (!textStyle || !textStyle.getText() || (!textFillStyle && !textStrokeStyle)) {
+    this.text_ = '';
+  } else {
+    if (!textFillStyle) {
+      state.fillColor = null;
+    } else {
+      var textFillStyleColor = textFillStyle.getColor();
+      state.fillColor = ol.colorlike.asColorLike(textFillStyleColor ?
+        textFillStyleColor : ol.render.webgl.defaultFillStyle);
     }
-
-    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();
+    if (!textStrokeStyle) {
+      state.strokeColor = null;
+      state.lineWidth = 0;
+    } else {
+      var textStrokeStyleColor = textStrokeStyle.getColor();
+      state.strokeColor = ol.colorlike.asColorLike(textStrokeStyleColor ?
+        textStrokeStyleColor : ol.render.webgl.defaultStrokeStyle);
+      state.lineWidth = textStrokeStyle.getWidth() || ol.render.webgl.defaultLineWidth;
+      state.lineCap = textStrokeStyle.getLineCap() || ol.render.webgl.defaultLineCap;
+      state.lineDashOffset = textStrokeStyle.getLineDashOffset() || ol.render.webgl.defaultLineDashOffset;
+      state.lineJoin = textStrokeStyle.getLineJoin() || ol.render.webgl.defaultLineJoin;
+      state.miterLimit = textStrokeStyle.getMiterLimit() || ol.render.webgl.defaultMiterLimit;
+      var lineDash = textStrokeStyle.getLineDash();
+      state.lineDash = lineDash ? lineDash.slice() : ol.render.webgl.defaultLineDash;
     }
-  }
+    state.font = textStyle.getFont() || ol.render.webgl.defaultFont;
+    state.scale = textStyle.getScale() || 1;
+    this.text_ = /** @type {string} */ (textStyle.getText());
+    var textAlign = ol.render.replay.TEXT_ALIGN[textStyle.getTextAlign()];
+    var textBaseline = ol.render.replay.TEXT_ALIGN[textStyle.getTextBaseline()];
+    this.textAlign_ = textAlign === undefined ?
+      ol.render.webgl.defaultTextAlign : textAlign;
+    this.textBaseline_ = textBaseline === undefined ?
+      ol.render.webgl.defaultTextBaseline : textBaseline;
+    this.offsetX_ = textStyle.getOffsetX() || 0;
+    this.offsetY_ = textStyle.getOffsetY() || 0;
+    this.rotateWithView = !!textStyle.getRotateWithView();
+    this.rotation = textStyle.getRotation() || 0;
 
-  this.postCompose(context, frameState, layerState);
+    this.currAtlas_ = this.getAtlas_(state);
+  }
 };
 
 
 /**
- * @abstract
- * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
+ * @private
+ * @param {Object} state Font attributes.
+ * @return {ol.WebglGlyphAtlas} Glyph atlas.
  */
-ol.renderer.canvas.IntermediateCanvas.prototype.getImage = function() {};
+ol.render.webgl.TextReplay.prototype.getAtlas_ = function(state) {
+  var params = [];
+  var i;
+  for (i in state) {
+    if (state[i] || state[i] === 0) {
+      if (Array.isArray(state[i])) {
+        params = params.concat(state[i]);
+      } else {
+        params.push(state[i]);
+      }
+    }
+  }
+  var hash = this.calculateHash_(params);
+  if (!this.atlases_[hash]) {
+    var mCtx = this.measureCanvas_.getContext('2d');
+    mCtx.font = state.font;
+    var height = Math.ceil((mCtx.measureText('M').width * 1.5 +
+        state.lineWidth / 2) * state.scale);
+
+    this.atlases_[hash] = {
+      atlas: new ol.style.AtlasManager({
+        space: state.lineWidth + 1
+      }),
+      width: {},
+      height: height
+    };
+  }
+  return this.atlases_[hash];
+};
 
 
 /**
- * @abstract
- * @return {!ol.Transform} Image transform.
+ * @private
+ * @param {Array.<string|number>} params Array of parameters.
+ * @return {string} Hash string.
  */
-ol.renderer.canvas.IntermediateCanvas.prototype.getImageTransform = function() {};
+ol.render.webgl.TextReplay.prototype.calculateHash_ = function(params) {
+  //TODO: Create a more performant, reliable, general hash function.
+  var i, ii;
+  var hash = '';
+  for (i = 0, ii = params.length; i < ii; ++i) {
+    hash += params[i];
+  }
+  return hash;
+};
 
 
 /**
  * @inheritDoc
  */
-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);
-      });
+ol.render.webgl.TextReplay.prototype.getTextures = function(opt_all) {
+  return this.textures_;
 };
 
 
 /**
  * @inheritDoc
  */
-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;
-    }
-  }
+ol.render.webgl.TextReplay.prototype.getHitDetectionTextures = function() {
+  return this.textures_;
 };
 
-goog.provide('ol.renderer.canvas.ImageLayer');
+goog.provide('ol.render.webgl.ReplayGroup');
 
 goog.require('ol');
-goog.require('ol.ViewHint');
+goog.require('ol.array');
 goog.require('ol.extent');
-goog.require('ol.renderer.canvas.IntermediateCanvas');
-goog.require('ol.transform');
+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');
 
 
 /**
  * @constructor
- * @extends {ol.renderer.canvas.IntermediateCanvas}
- * @param {ol.layer.Image} imageLayer Single image layer.
+ * @extends {ol.render.ReplayGroup}
+ * @param {number} tolerance Tolerance.
+ * @param {ol.Extent} maxExtent Max extent.
+ * @param {number=} opt_renderBuffer Render buffer.
+ * @struct
  */
-ol.renderer.canvas.ImageLayer = function(imageLayer) {
+ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) {
+  ol.render.ReplayGroup.call(this);
 
-  ol.renderer.canvas.IntermediateCanvas.call(this, imageLayer);
+  /**
+   * @type {ol.Extent}
+   * @private
+   */
+  this.maxExtent_ = maxExtent;
 
   /**
+   * @type {number}
    * @private
-   * @type {?ol.ImageBase}
    */
-  this.image_ = null;
+  this.tolerance_ = tolerance;
 
   /**
+   * @type {number|undefined}
    * @private
-   * @type {ol.Transform}
    */
-  this.imageTransform_ = ol.transform.create();
+  this.renderBuffer_ = opt_renderBuffer;
+
+  /**
+   * @private
+   * @type {!Object.<string,
+   *        Object.<ol.render.ReplayType, ol.render.webgl.Replay>>}
+   */
+  this.replaysByZIndex_ = {};
 
 };
-ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.IntermediateCanvas);
+ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);
 
 
 /**
- * @inheritDoc
+ * @param {ol.style.Style} style Style.
+ * @param {boolean} group Group with previous replay.
  */
-ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
-  return !this.image_ ? null : this.image_.getImage();
-};
+ol.render.webgl.ReplayGroup.prototype.addDeclutter = function(style, group) {};
 
 
 /**
- * @inheritDoc
+ * @param {ol.webgl.Context} context WebGL context.
+ * @return {function()} Delete resources function.
  */
-ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() {
-  return this.imageTransform_;
+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;
+  };
 };
 
 
 /**
- * @inheritDoc
+ * @param {ol.webgl.Context} context Context.
  */
-ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) {
-
-  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);
-  }
-
-  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;
-      }
+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);
     }
   }
-
-  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;
-  }
-
-  return !!this.image_;
 };
 
-goog.provide('ol.reproj');
-
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.proj');
-
 
 /**
- * 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.
+ * @inheritDoc
  */
-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;
+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;
   }
-
-  // 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;
+  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 sourceResolution;
+  return replay;
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-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)];
+ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
+  return ol.obj.isEmpty(this.replaysByZIndex_);
 };
 
 
 /**
- * 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 {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} 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.
+ * @param {number} opacity Global opacity.
+ * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
+ *  to skip.
  */
-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));
+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);
 
-  if (sources.length === 0) {
-    return context.canvas;
+  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);
+      }
+    }
   }
+};
 
-  context.scale(pixelRatio, pixelRatio);
-
-  var sourceDataExtent = ol.extent.createEmpty();
-  sources.forEach(function(src, i, arr) {
-    ol.extent.extend(sourceDataExtent, src.extent);
-  });
-
-  var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent);
-  var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent);
-  var stitchContext = ol.dom.createCanvasContext2D(
-      Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
-      Math.round(pixelRatio * canvasHeightInUnits / sourceResolution));
-
-  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);
+/**
+ * @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 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;
+  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;
+};
 
-    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();
+/**
+ * @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());
 
-    context.transform(
-        affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);
 
-    context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
-                      sourceDataExtent[3] - sourceNumericalShiftY);
+  /**
+   * @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_);
+  }
 
-    context.scale(sourceResolution / pixelRatio,
-                  -sourceResolution / pixelRatio);
+  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);
 
-    context.drawImage(stitchContext.canvas, 0, 0);
-    context.restore();
-  });
+        if (imageData[3] > 0) {
+          var result = callback(feature);
+          if (result) {
+            return result;
+          }
+        }
+      }, true, hitExtent);
+};
 
-  if (opt_renderEdges) {
-    context.save();
 
-    context.strokeStyle = 'black';
-    context.lineWidth = 1;
+/**
+ * @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);
 
-    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;
+  return hasFeature !== undefined;
+};
 
-      context.beginPath();
-      context.moveTo(u1, v1);
-      context.lineTo(u0, v0);
-      context.lineTo(u2, v2);
-      context.closePath();
-      context.stroke();
-    });
+/**
+ * @const
+ * @private
+ * @type {Array.<number>}
+ */
+ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1];
 
-    context.restore();
-  }
-  return context.canvas;
+/**
+ * @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
 };
 
-goog.provide('ol.reproj.Triangulation');
+goog.provide('ol.render.webgl.Immediate');
 
 goog.require('ol');
 goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.proj');
+goog.require('ol.geom.GeometryType');
+goog.require('ol.render.ReplayType');
+goog.require('ol.render.VectorContext');
+goog.require('ol.render.webgl.ReplayGroup');
 
 
 /**
- * @classdesc
- * Class containing triangulation of the given target extent.
- * Used for determining source data and the reprojection itself.
- *
- * @param {ol.proj.Projection} sourceProj Source projection.
- * @param {ol.proj.Projection} targetProj Target projection.
- * @param {ol.Extent} targetExtent Target extent to triangulate.
- * @param {ol.Extent} maxSourceExtent Maximal source extent that can be used.
- * @param {number} errorThreshold Acceptable error (in source units).
  * @constructor
+ * @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.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
-    maxSourceExtent, errorThreshold) {
+ol.render.webgl.Immediate = function(context, center, resolution, rotation, size, extent, pixelRatio) {
+  ol.render.VectorContext.call(this);
 
   /**
-   * @type {ol.proj.Projection}
    * @private
    */
-  this.sourceProj_ = sourceProj;
+  this.context_ = context;
 
   /**
-   * @type {ol.proj.Projection}
    * @private
    */
-  this.targetProj_ = targetProj;
+  this.center_ = center;
 
-  /** @type {!Object.<string, ol.Coordinate>} */
-  var transformInvCache = {};
-  var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_);
+  /**
+   * @private
+   */
+  this.extent_ = extent;
 
   /**
-   * @param {ol.Coordinate} c A coordinate.
-   * @return {ol.Coordinate} Transformed coordinate.
    * @private
    */
-  this.transformInv_ = function(c) {
-    var key = c[0] + '/' + c[1];
-    if (!transformInvCache[key]) {
-      transformInvCache[key] = transformInv(c);
-    }
-    return transformInvCache[key];
-  };
+  this.pixelRatio_ = pixelRatio;
 
   /**
-   * @type {ol.Extent}
    * @private
    */
-  this.maxSourceExtent_ = maxSourceExtent;
+  this.size_ = size;
 
   /**
-   * @type {number}
    * @private
    */
-  this.errorThresholdSquared_ = errorThreshold * errorThreshold;
+  this.rotation_ = rotation;
 
   /**
-   * @type {Array.<ol.ReprojTriangle>}
    * @private
    */
-  this.triangles_ = [];
+  this.resolution_ = resolution;
 
   /**
-   * Indicates that the triangulation crosses edge of the source projection.
-   * @type {boolean}
    * @private
+   * @type {ol.style.Image}
    */
-  this.wrapsXInSource_ = false;
+  this.imageStyle_ = null;
 
   /**
-   * @type {boolean}
    * @private
+   * @type {ol.style.Fill}
    */
-  this.canWrapXInSource_ = this.sourceProj_.canWrapX() &&
-      !!maxSourceExtent &&
-      !!this.sourceProj_.getExtent() &&
-      (ol.extent.getWidth(maxSourceExtent) ==
-       ol.extent.getWidth(this.sourceProj_.getExtent()));
+  this.fillStyle_ = null;
 
   /**
-   * @type {?number}
    * @private
+   * @type {ol.style.Stroke}
    */
-  this.sourceWorldWidth_ = this.sourceProj_.getExtent() ?
-      ol.extent.getWidth(this.sourceProj_.getExtent()) : null;
+  this.strokeStyle_ = null;
 
   /**
-   * @type {?number}
    * @private
+   * @type {ol.style.Text}
    */
-  this.targetWorldWidth_ = this.targetProj_.getExtent() ?
-      ol.extent.getWidth(this.targetProj_.getExtent()) : null;
+  this.textStyle_ = 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);
+};
+ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext);
 
-  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]);
-    });
+/**
+ * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group.
+ * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
+ * @private
+ */
+ol.render.webgl.Immediate.prototype.drawText_ = function(replayGroup, geometry) {
+  var context = this.context_;
+  var replay = /** @type {ol.render.webgl.TextReplay} */ (
+    replayGroup.getReplay(0, ol.render.ReplayType.TEXT));
+  replay.setTextStyle(this.textStyle_);
+  replay.drawText(geometry, null);
+  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)();
+};
 
-    // Shift triangles to be as close to `leftBound` as possible
-    // (if the distance is more than `worldWidth / 2` it can be closer.
-    this.triangles_.forEach(function(triangle) {
-      if (Math.max(triangle.source[0][0], triangle.source[1][0],
-          triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) {
-        var newTriangle = [[triangle.source[0][0], triangle.source[0][1]],
-                           [triangle.source[1][0], triangle.source[1][1]],
-                           [triangle.source[2][0], triangle.source[2][1]]];
-        if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) {
-          newTriangle[0][0] -= this.sourceWorldWidth_;
-        }
-        if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) {
-          newTriangle[1][0] -= this.sourceWorldWidth_;
-        }
-        if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) {
-          newTriangle[2][0] -= this.sourceWorldWidth_;
-        }
 
-        // Rarely (if the extent contains both the dateline and prime meridian)
-        // the shift can in turn break some triangles.
-        // Detect this here and don't shift in such cases.
-        var minX = Math.min(
-            newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
-        var maxX = Math.max(
-            newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
-        if ((maxX - minX) < this.sourceWorldWidth_ / 2) {
-          triangle.source = newTriangle;
-        }
-      }
-    }, this);
+/**
+ * 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());
+  this.setTextStyle(style.getText());
+};
+
+
+/**
+ * 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
   }
+};
 
-  transformInvCache = {};
+
+/**
+ * @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);
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c,
-    aSrc, bSrc, cSrc) {
-  this.triangles_.push({
-    source: [aSrc, bSrc, cSrc],
-    target: [a, b, c]
-  });
+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]);
+  }
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d,
-    aSrc, bSrc, cSrc, dSrc, maxSubdivision) {
+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)();
 
-  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_);
+  if (this.textStyle_) {
+    this.drawText_(replayGroup, geometry);
+  }
+};
 
-  // 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;
+/**
+ * @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)();
 
-  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 (this.textStyle_) {
+    this.drawText_(replayGroup, geometry);
   }
+};
 
-  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;
-      }
-    }
+/**
+ * @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)();
+
+  if (this.textStyle_) {
+    this.drawText_(replayGroup, geometry);
   }
+};
 
-  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);
+/**
+ * @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)();
 
-        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);
+  if (this.textStyle_) {
+    this.drawText_(replayGroup, geometry);
+  }
+};
 
-        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;
-    }
+
+/**
+ * @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)();
+
+  if (this.textStyle_) {
+    this.drawText_(replayGroup, geometry);
   }
+};
 
-  if (wrapsX) {
-    if (!this.canWrapXInSource_) {
-      return;
-    }
-    this.wrapsXInSource_ = true;
+
+/**
+ * @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)();
+
+  if (this.textStyle_) {
+    this.drawText_(replayGroup, geometry);
   }
+};
 
-  this.addTriangle_(a, c, d, aSrc, cSrc, dSrc);
-  this.addTriangle_(a, b, c, aSrc, bSrc, cSrc);
+
+/**
+ * @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)();
+
+  if (this.textStyle_) {
+    this.drawText_(replayGroup, geometry);
+  }
 };
 
 
 /**
- * Calculates extent of the 'source' coordinates from all the triangles.
- *
- * @return {ol.Extent} Calculated extent.
+ * @inheritDoc
  */
-ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
-  var extent = ol.extent.createEmpty();
+ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
+  this.imageStyle_ = imageStyle;
+};
 
-  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;
+/**
+ * @inheritDoc
+ */
+ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
+  this.fillStyle_ = fillStyle;
+  this.strokeStyle_ = strokeStyle;
 };
 
 
 /**
- * @return {Array.<ol.ReprojTriangle>} Array of the calculated triangles.
+ * @inheritDoc
  */
-ol.reproj.Triangulation.prototype.getTriangles = function() {
-  return this.triangles_;
+ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) {
+  this.textStyle_ = textStyle;
 };
 
-goog.provide('ol.reproj.Image');
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.defaultmapshader');
+
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
+
+
+ol.renderer.webgl.defaultmapshader.fragment = new ol.webgl.Fragment(ol.DEBUG_WEBGL ?
+  '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' :
+  '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;}');
+
+ol.renderer.webgl.defaultmapshader.vertex = new ol.webgl.Vertex(ol.DEBUG_WEBGL ?
+  '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' :
+  '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;}');
+
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.defaultmapshader.Locations');
 
 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');
 
 
 /**
- * @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).
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-ol.reproj.Image = function(sourceProj, targetProj,
-    targetExtent, targetResolution, pixelRatio, getImageFunction) {
+ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) {
 
   /**
-   * @private
-   * @type {ol.proj.Projection}
+   * @type {WebGLUniformLocation}
    */
-  this.targetProj_ = targetProj;
+  this.u_texCoordMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_texCoordMatrix' : 'd');
 
   /**
-   * @private
-   * @type {ol.Extent}
+   * @type {WebGLUniformLocation}
    */
-  this.maxSourceExtent_ = sourceProj.getExtent();
-  var maxTargetExtent = targetProj.getExtent();
-
-  var limitedTargetExtent = maxTargetExtent ?
-      ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
+  this.u_projectionMatrix = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'e');
 
-  var targetCenter = ol.extent.getCenter(limitedTargetExtent);
-  var sourceResolution = ol.reproj.calculateSourceResolution(
-      sourceProj, targetProj, targetCenter, targetResolution);
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_opacity = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f');
 
-  var errorThresholdInPixels = ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_texture = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_texture' : 'g');
 
   /**
-   * @private
-   * @type {!ol.reproj.Triangulation}
+   * @type {number}
    */
-  this.triangulation_ = new ol.reproj.Triangulation(
-      sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_,
-      sourceResolution * errorThresholdInPixels);
+  this.a_position = gl.getAttribLocation(
+      program, ol.DEBUG_WEBGL ? 'a_position' : 'b');
 
   /**
-   * @private
    * @type {number}
    */
-  this.targetResolution_ = targetResolution;
+  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.renderer.webgl.defaultmapshader.Locations');
+goog.require('ol.transform');
+goog.require('ol.vec.Mat4');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.Context');
+
+
+/**
+ * @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) {
+
+  ol.renderer.Layer.call(this, layer);
+
+  /**
+   * @protected
+   * @type {ol.renderer.webgl.Map}
+   */
+  this.mapRenderer = mapRenderer;
 
   /**
    * @private
-   * @type {ol.Extent}
+   * @type {ol.webgl.Buffer}
    */
-  this.targetExtent_ = targetExtent;
+  this.arrayBuffer_ = new ol.webgl.Buffer([
+    -1, -1, 0, 0,
+    1, -1, 1, 0,
+    -1, 1, 0, 1,
+    1, 1, 1, 1
+  ]);
 
-  var sourceExtent = this.triangulation_.calculateSourceExtent();
+  /**
+   * @protected
+   * @type {WebGLTexture}
+   */
+  this.texture = null;
 
   /**
-   * @private
-   * @type {ol.ImageBase}
+   * @protected
+   * @type {WebGLFramebuffer}
    */
-  this.sourceImage_ =
-      getImageFunction(sourceExtent, sourceResolution, pixelRatio);
+  this.framebuffer = null;
+
+  /**
+   * @protected
+   * @type {number|undefined}
+   */
+  this.framebufferDimension = undefined;
+
+  /**
+   * @protected
+   * @type {ol.Transform}
+   */
+  this.texCoordMatrix = ol.transform.create();
 
   /**
-   * @private
-   * @type {number}
+   * @protected
+   * @type {ol.Transform}
    */
-  this.sourcePixelRatio_ =
-      this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1;
+  this.projectionMatrix = ol.transform.create();
 
   /**
+   * @type {Array.<number>}
    * @private
-   * @type {HTMLCanvasElement}
    */
-  this.canvas_ = null;
+  this.tmpMat4_ = ol.vec.Mat4.create();
 
   /**
    * @private
-   * @type {?ol.EventsKey}
+   * @type {ol.renderer.webgl.defaultmapshader.Locations}
    */
-  this.sourceListenerKey_ = null;
+  this.defaultLocations_ = null;
 
+};
+ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);
 
-  var state = ol.ImageState.LOADED;
-  var attributions = [];
 
-  if (this.sourceImage_) {
-    state = ol.ImageState.IDLE;
-    attributions = this.sourceImage_.getAttributions();
+/**
+ * @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) {
+    /**
+     * @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)
+    );
+
+    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);
+
+    this.texture = texture;
+    this.framebuffer = framebuffer;
+    this.framebufferDimension = framebufferDimension;
+
+  } else {
+    gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer);
   }
 
-  ol.ImageBase.call(this, targetExtent, targetResolution, this.sourcePixelRatio_,
-            state, attributions);
 };
-ol.inherits(ol.reproj.Image, ol.ImageBase);
 
 
 /**
- * @inheritDoc
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {ol.LayerState} layerState Layer state.
+ * @param {ol.webgl.Context} context Context.
  */
-ol.reproj.Image.prototype.disposeInternal = function() {
-  if (this.state == ol.ImageState.LOADING) {
-    this.unlistenSource_();
+ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) {
+
+  this.dispatchComposeEvent_(
+      ol.render.EventType.PRECOMPOSE, context, frameState);
+
+  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_);
+
+  var gl = context.getGL();
+
+  var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment;
+  var vertexShader = ol.renderer.webgl.defaultmapshader.vertex;
+
+  var program = context.getProgram(fragmentShader, vertexShader);
+
+  var locations;
+  if (!this.defaultLocations_) {
+    locations = new ol.renderer.webgl.defaultmapshader.Locations(gl, program);
+    this.defaultLocations_ = locations;
+  } else {
+    locations = this.defaultLocations_;
   }
-  ol.ImageBase.prototype.disposeInternal.call(this);
+
+  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);
+  }
+
+  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
+ * @param {ol.render.EventType} type Event type.
+ * @param {ol.webgl.Context} context WebGL context.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-ol.reproj.Image.prototype.getImage = function(opt_context) {
-  return this.canvas_;
+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);
+  }
 };
 
 
 /**
- * @return {ol.proj.Projection} Projection.
+ * @return {!ol.Transform} Matrix.
  */
-ol.reproj.Image.prototype.getProjection = function() {
-  return this.targetProj_;
+ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
+  return this.texCoordMatrix;
 };
 
 
 /**
- * @private
+ * @return {WebGLTexture} Texture.
  */
-ol.reproj.Image.prototype.reproject_ = function() {
-  var sourceState = this.sourceImage_.getState();
-  if (sourceState == ol.ImageState.LOADED) {
-    var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_;
-    var height =
-        ol.extent.getHeight(this.targetExtent_) / this.targetResolution_;
-
-    this.canvas_ = ol.reproj.render(width, height, this.sourcePixelRatio_,
-        this.sourceImage_.getResolution(), this.maxSourceExtent_,
-        this.targetResolution_, this.targetExtent_, this.triangulation_, [{
-          extent: this.sourceImage_.getExtent(),
-          image: this.sourceImage_.getImage()
-        }], 0);
-  }
-  this.state = sourceState;
-  this.changed();
+ol.renderer.webgl.Layer.prototype.getTexture = function() {
+  return this.texture;
 };
 
 
 /**
- * @inheritDoc
+ * @return {!ol.Transform} Matrix.
  */
-ol.reproj.Image.prototype.load = function() {
-  if (this.state == ol.ImageState.IDLE) {
-    this.state = ol.ImageState.LOADING;
-    this.changed();
-
-    var sourceState = this.sourceImage_.getState();
-    if (sourceState == ol.ImageState.LOADED ||
-        sourceState == ol.ImageState.ERROR) {
-      this.reproject_();
-    } else {
-      this.sourceListenerKey_ = 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();
-    }
-  }
+ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
+  return this.projectionMatrix;
 };
 
 
 /**
- * @private
+ * Handle webglcontextlost.
  */
-ol.reproj.Image.prototype.unlistenSource_ = function() {
-  ol.events.unlistenByKey(/** @type {!ol.EventsKey} */ (this.sourceListenerKey_));
-  this.sourceListenerKey_ = null;
+ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
+  this.texture = null;
+  this.framebuffer = null;
+  this.framebufferDimension = undefined;
 };
 
-goog.provide('ol.source.Image');
+
+/**
+ * @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) {};
+
+
+/**
+ * @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) {};
+
+goog.provide('ol.renderer.webgl.ImageLayer');
 
 goog.require('ol');
-goog.require('ol.ImageState');
-goog.require('ol.array');
-goog.require('ol.events.Event');
+goog.require('ol.LayerType');
+goog.require('ol.ViewHint');
+goog.require('ol.dom');
 goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.reproj.Image');
-goog.require('ol.source.Source');
+goog.require('ol.functions');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.transform');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Context');
 
 
 /**
- * @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.
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Image} imageLayer Tile layer.
  * @api
  */
-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
-  });
+ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) {
+
+  ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer);
 
   /**
+   * The last rendered image.
    * @private
-   * @type {Array.<number>}
+   * @type {?ol.ImageBase}
    */
-  this.resolutions_ = options.resolutions !== undefined ?
-      options.resolutions : null;
-
+  this.image_ = null;
 
   /**
    * @private
-   * @type {ol.reproj.Image}
+   * @type {CanvasRenderingContext2D}
    */
-  this.reprojectedImage_ = null;
-
+  this.hitCanvasContext_ = null;
 
   /**
    * @private
-   * @type {number}
+   * @type {?ol.Transform}
    */
-  this.reprojectedRevision_ = 0;
+  this.hitTransformationMatrix_ = null;
+
 };
-ol.inherits(ol.source.Image, ol.source.Source);
+ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);
 
 
 /**
- * @return {Array.<number>} Resolutions.
- * @override
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @param {ol.layer.Layer} layer The candidate layer.
+ * @return {boolean} The renderer can render the layer.
  */
-ol.source.Image.prototype.getResolutions = function() {
-  return this.resolutions_;
+ol.renderer.webgl.ImageLayer['handles'] = function(type, layer) {
+  return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.IMAGE;
 };
 
 
 /**
- * @protected
- * @param {number} resolution Resolution.
- * @return {number} Resolution.
+ * Create a layer renderer.
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @param {ol.layer.Layer} layer The layer to be rendererd.
+ * @return {ol.renderer.webgl.ImageLayer} The layer renderer.
  */
-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;
+ol.renderer.webgl.ImageLayer['create'] = function(mapRenderer, layer) {
+  return new ol.renderer.webgl.ImageLayer(
+      /** @type {ol.renderer.webgl.Map} */ (mapRenderer),
+      /** @type {ol.layer.Image} */ (layer)
+  );
 };
 
 
 /**
- * @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.
+ * @param {ol.ImageBase} image Image.
+ * @private
+ * @return {WebGLTexture} Texture.
  */
-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;
-    }
+ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
 
-    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();
+  // 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
 
-    return this.reprojectedImage_;
-  }
+  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);
 };
 
 
 /**
- * @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
+ * @inheritDoc
  */
-ol.source.Image.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {};
-
+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,
 
-/**
- * Handle image change events.
- * @param {ol.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.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
-  }
+      /**
+       * @param {ol.Feature|ol.render.Feature} feature Feature.
+       * @return {?} Callback result.
+       */
+      function(feature) {
+        return callback.call(thisArg, feature, layer);
+      });
 };
 
 
 /**
- * Default image load function for image sources that use ol.Image image
- * instances.
- * @param {ol.Image} image Image.
- * @param {string} src Source.
+ * @inheritDoc
  */
-ol.source.Image.defaultImageLoadFunction = function(image, src) {
-  image.getImage().src = src;
-};
+ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) {
 
+  var gl = this.mapRenderer.getGL();
 
-/**
- * @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.source.Image.Event = function(type, image) {
+  var pixelRatio = frameState.pixelRatio;
+  var viewState = frameState.viewState;
+  var viewCenter = viewState.center;
+  var viewResolution = viewState.resolution;
+  var viewRotation = viewState.rotation;
 
-  ol.events.Event.call(this, type);
+  var image = this.image_;
+  var texture = this.texture;
+  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
+  var imageSource = imageLayer.getSource();
 
-  /**
-   * The image related to the event.
-   * @type {ol.Image}
-   * @api
-   */
-  this.image = image;
+  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;
+      }
+    }
+    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)
+          );
+        }
+      }
+    }
+  }
+
+  if (image) {
+    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;
+    ol.transform.reset(texCoordMatrix);
+    ol.transform.scale(texCoordMatrix, 1, -1);
+    ol.transform.translate(texCoordMatrix, 0, -1);
 
+    this.image_ = image;
+    this.texture = texture;
+
+    this.updateLogos(frameState, imageSource);
+  }
+
+  return !!image;
 };
-ol.inherits(ol.source.Image.Event, ol.events.Event);
 
 
 /**
- * @enum {string}
+ * @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.source.Image.EventType_ = {
-
-  /**
-   * Triggered when an image starts loading.
-   * @event ol.source.Image.Event#imageloadstart
-   * @api
-   */
-  IMAGELOADSTART: 'imageloadstart',
+ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvasWidth, canvasHeight, pixelRatio,
+    viewCenter, viewResolution, viewRotation, imageExtent) {
 
-  /**
-   * Triggered when an image finishes loading.
-   * @event ol.source.Image.Event#imageloadend
-   * @api
-   */
-  IMAGELOADEND: 'imageloadend',
+  var canvasExtentWidth = canvasWidth * viewResolution;
+  var canvasExtentHeight = canvasHeight * viewResolution;
 
-  /**
-   * Triggered if image loading results in an error.
-   * @event ol.source.Image.Event#imageloaderror
-   * @api
-   */
-  IMAGELOADERROR: 'imageloaderror'
+  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);
 
 };
 
-goog.provide('ol.source.ImageCanvas');
 
-goog.require('ol');
-goog.require('ol.ImageCanvas');
-goog.require('ol.extent');
-goog.require('ol.source.Image');
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
+  var hasFeature = this.forEachFeatureAtCoordinate(
+      coordinate, frameState, 0, ol.functions.TRUE, this);
+  return hasFeature !== undefined;
+};
 
 
 /**
- * @classdesc
- * Base class for image sources where a canvas element is the image.
- *
- * @constructor
- * @extends {ol.source.Image}
- * @param {olx.source.ImageCanvasOptions} options Constructor options.
- * @api
+ * @inheritDoc
  */
-ol.source.ImageCanvas = function(options) {
+ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
+  if (!this.image_ || !this.image_.getImage()) {
+    return undefined;
+  }
 
-  ol.source.Image.call(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection,
-    resolutions: options.resolutions,
-    state: options.state
-  });
+  if (this.getLayer().getSource().forEachFeatureAtCoordinate !== ol.nullFunction) {
+    // for ImageCanvas 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);
 
-  /**
-   * @private
-   * @type {ol.CanvasFunctionType}
-   */
-  this.canvasFunction_ = options.canvasFunction;
+    if (hasFeature) {
+      return callback.call(thisArg, this.getLayer(), null);
+    } else {
+      return undefined;
+    }
+  } else {
+    var imageSize =
+        [this.image_.getImage().width, this.image_.getImage().height];
 
-  /**
-   * @private
-   * @type {ol.ImageCanvas}
-   */
-  this.canvas_ = null;
+    if (!this.hitTransformationMatrix_) {
+      this.hitTransformationMatrix_ = this.getHitTransformationMatrix_(
+          frameState.size, imageSize);
+    }
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = 0;
+    var pixelOnFrameBuffer = ol.transform.apply(
+        this.hitTransformationMatrix_, pixel.slice());
 
-  /**
-   * @private
-   * @type {number}
-   */
-  this.ratio_ = options.ratio !== undefined ?
-      options.ratio : 1.5;
+    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;
+    }
+  }
 };
-ol.inherits(ol.source.ImageCanvas, ol.source.Image);
 
 
 /**
- * @inheritDoc
+ * 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.source.ImageCanvas.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
-  resolution = this.findNearestResolution(resolution);
+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);
 
-  var canvas = this.canvas_;
-  if (canvas &&
-      this.renderedRevision_ == this.getRevision() &&
-      canvas.getResolution() == resolution &&
-      canvas.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(canvas.getExtent(), extent)) {
-    return canvas;
-  }
+  // the second matrix is the inverse of the projection matrix used in the
+  // shader for drawing
+  var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice());
 
-  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];
+  // 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);
 
-  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();
+  ol.transform.multiply(transform, projectionMatrixInv);
+  ol.transform.multiply(transform, mapCoordTransform);
 
-  return canvas;
+  return transform;
 };
 
-goog.provide('ol.source.ImageVector');
+// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)
+
+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.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');
+goog.require('ol.has');
+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');
 
 
 /**
- * @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.
+ * @extends {ol.renderer.Map}
+ * @param {Element} container Container.
+ * @param {ol.PluggableMap} map Map.
  * @api
  */
-ol.source.ImageVector = function(options) {
+ol.renderer.webgl.Map = function(container, map) {
+  ol.renderer.Map.call(this, container, map);
 
   /**
    * @private
-   * @type {ol.source.Vector}
+   * @type {HTMLCanvasElement}
    */
-  this.source_ = options.source;
+  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);
 
   /**
    * @private
-   * @type {ol.Transform}
+   * @type {number}
    */
-  this.transform_ = ol.transform.create();
+  this.clipTileCanvasWidth_ = 0;
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.clipTileCanvasHeight_ = 0;
 
   /**
    * @private
    * @type {CanvasRenderingContext2D}
    */
-  this.canvasContext_ = ol.dom.createCanvasContext2D();
+  this.clipTileContext_ = ol.dom.createCanvasContext2D();
 
   /**
    * @private
-   * @type {ol.Size}
+   * @type {boolean}
    */
-  this.canvasSize_ = [0, 0];
+  this.renderedVisible_ = true;
 
   /**
    * @private
-   * @type {number}
+   * @type {WebGLRenderingContext}
    */
-  this.renderBuffer_ = options.renderBuffer == undefined ? 100 : options.renderBuffer;
+  this.gl_ = ol.webgl.getContext(this.canvas_, {
+    antialias: true,
+    depth: true,
+    failIfMajorPerformanceCaveat: true,
+    preserveDrawingBuffer: false,
+    stencil: true
+  });
 
   /**
    * @private
-   * @type {ol.render.canvas.ReplayGroup}
+   * @type {ol.webgl.Context}
    */
-  this.replayGroup_ = null;
+  this.context_ = new ol.webgl.Context(this.canvas_, this.gl_);
 
-  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()
-  });
+  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
+      this.handleWebGLContextLost, this);
+  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
+      this.handleWebGLContextRestored, this);
 
   /**
-   * User provided style.
-   * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
    * @private
+   * @type {ol.structs.LRUCache.<ol.WebglTextureCacheEntry|null>}
    */
-  this.style_ = null;
+  this.textureCache_ = new ol.structs.LRUCache();
+
+  /**
+   * @private
+   * @type {ol.Coordinate}
+   */
+  this.focus_ = null;
 
   /**
-   * Style function for use within the library.
-   * @type {ol.StyleFunction|undefined}
    * @private
+   * @type {ol.structs.PriorityQueue.<Array>}
    */
-  this.styleFunction_ = undefined;
+  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();
+      });
 
-  this.setStyle(options.style);
 
-  ol.events.listen(this.source_, ol.events.EventType.CHANGE,
-      this.handleSourceChange_, this);
+  /**
+   * @param {ol.PluggableMap} 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);
+
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.textureCacheFrameMarkerCount_ = 0;
 
+  this.initializeGL_();
 };
-ol.inherits(ol.source.ImageVector, ol.source.ImageCanvas);
+ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
 
 
 /**
- * @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
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @return {boolean} The renderer can render the layer.
  */
-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_);
+ol.renderer.webgl.Map['handles'] = function(type) {
+  return ol.has.WEBGL && type === ol.renderer.Type.WEBGL;
+};
 
-  this.source_.loadFeatures(extent, resolution, projection);
 
-  var loading = false;
-  this.source_.forEachFeatureInExtent(extent,
-      /**
-       * @param {ol.Feature} feature Feature.
-       */
-      function(feature) {
-        loading = loading ||
-            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
-      }, this);
-  replayGroup.finish();
+/**
+ * Create the map renderer.
+ * @param {Element} container Container.
+ * @param {ol.PluggableMap} map Map.
+ * @return {ol.renderer.webgl.Map} The map renderer.
+ */
+ol.renderer.webgl.Map['create'] = function(container, map) {
+  return new ol.renderer.webgl.Map(container, map);
+};
 
-  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];
+/**
+ * @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 {
-    this.canvasContext_.clearRect(0, 0, size[0], size[1]);
+    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
+    });
   }
+};
 
-  var transform = this.getTransform_(ol.extent.getCenter(extent),
-      resolution, pixelRatio, size);
-  replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {});
 
-  this.replayGroup_ = replayGroup;
+/**
+ * @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_;
 
-  return this.canvasContext_.canvas;
+    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, vectorContext,
+        frameState, null, context);
+    map.dispatchEvent(composeEvent);
+  }
 };
 
 
 /**
  * @inheritDoc
  */
-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,
+ol.renderer.webgl.Map.prototype.disposeInternal = function() {
+  var gl = this.getGL();
+  if (!gl.isContextLost()) {
+    this.textureCache_.forEach(
         /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @return {?} Callback result.
+         * @param {?ol.WebglTextureCacheEntry} textureCacheEntry
+         *     Texture cache entry.
          */
-        function(feature) {
-          var key = ol.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback(feature);
+        function(textureCacheEntry) {
+          if (textureCacheEntry) {
+            gl.deleteTexture(textureCacheEntry.texture);
           }
         });
   }
+  this.context_.dispose();
+  ol.renderer.Map.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * Get a reference to the wrapped source.
- * @return {ol.source.Vector} Source.
- * @api
+ * @param {ol.PluggableMap} map Map.
+ * @param {olx.FrameState} frameState Frame state.
+ * @private
  */
-ol.source.ImageVector.prototype.getSource = function() {
-  return this.source_;
+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();
+  }
 };
 
 
 /**
- * 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
+ * @return {ol.webgl.Context} The context.
  */
-ol.source.ImageVector.prototype.getStyle = function() {
-  return this.style_;
+ol.renderer.webgl.Map.prototype.getContext = function() {
+  return this.context_;
 };
 
 
 /**
- * Get the style function.
- * @return {ol.StyleFunction|undefined} Layer style function.
- * @api
+ * @return {WebGLRenderingContext} GL.
  */
-ol.source.ImageVector.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
+ol.renderer.webgl.Map.prototype.getGL = function() {
+  return this.gl_;
 };
 
 
 /**
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Size} size Size.
- * @return {!ol.Transform} Transform.
- * @private
+ * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
  */
-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];
-
-  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2);
+ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
+  return this.tileTextureQueue_;
 };
 
 
 /**
- * Handle changes in image style state.
- * @param {ol.events.Event} event Image style change event.
- * @private
+ * @inheritDoc
  */
-ol.source.ImageVector.prototype.handleImageChange_ = function(event) {
-  this.changed();
+ol.renderer.webgl.Map.prototype.getType = function() {
+  return ol.renderer.Type.WEBGL;
 };
 
 
 /**
- * @private
+ * @param {ol.events.Event} event Event.
+ * @protected
  */
-ol.source.ImageVector.prototype.handleSourceChange_ = function() {
-  // setState will trigger a CHANGE event, so we always rely
-  // change events by calling setState.
-  this.setState(this.source_.getState());
+ol.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();
+  }
 };
 
 
 /**
- * @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
+ * @protected
  */
-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;
-  if (!Array.isArray(styles)) {
-    styles = [styles];
-  }
-  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;
+ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
+  this.initializeGL_();
+  this.getMap().render();
 };
 
 
 /**
- * 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
+ * @private
  */
-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();
+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);
 };
 
-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;
 
-  };
-  ol.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) {
+/**
+ * @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());
+};
 
-    // 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();
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
 
-    return ol.webgl.Context.createTexture(
-        gl, imageElement, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE);
-  };
+  var context = this.getContext();
+  var gl = this.getGL();
 
+  if (gl.isContextLost()) {
+    return false;
+  }
 
-  /**
-   * @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,
+  if (!frameState) {
+    if (this.renderedVisible_) {
+      this.canvas_.style.display = 'none';
+      this.renderedVisible_ = false;
+    }
+    return false;
+  }
 
-        /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @return {?} Callback result.
-         */
-        function(feature) {
-          return callback.call(thisArg, feature, layer);
-        });
-  };
+  this.focus_ = frameState.focus;
 
+  this.textureCache_.set((-frameState.index).toString(), null);
+  ++this.textureCacheFrameMarkerCount_;
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) {
+  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
 
-    var gl = this.mapRenderer.getGL();
+  /** @type {Array.<ol.LayerState>} */
+  var layerStatesToDraw = [];
+  var layerStatesArray = frameState.layerStatesArray;
+  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
 
-    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;
-        }
-      }
-      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)
-            );
-          }
-        }
+  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);
       }
     }
+  }
 
-    if (image) {
-      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;
-      ol.transform.reset(texCoordMatrix);
-      ol.transform.scale(texCoordMatrix, 1, -1);
-      ol.transform.translate(texCoordMatrix, 0, -1);
+  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;
+  }
 
-      this.image_ = image;
-      this.texture = texture;
+  gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null);
 
-      this.updateAttributions(frameState.attributions, image.getAttributions());
-      this.updateLogos(frameState, imageSource);
-    }
+  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);
 
-    return !!image;
-  };
+  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);
+  }
 
+  if (!this.renderedVisible_) {
+    this.canvas_.style.display = '';
+    this.renderedVisible_ = 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) {
+  this.calculateMatrices2D(frameState);
 
-    var canvasExtentWidth = canvasWidth * viewResolution;
-    var canvasExtentHeight = canvasHeight * viewResolution;
+  if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
+      ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
+    frameState.postRenderFunctions.push(
+        /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this))
+    );
+  }
 
-    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);
+  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.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;
-    }
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg,
+    layerFilter, thisArg2) {
+  var result;
 
-    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 (this.getGL().isContextLost()) {
+    return false;
+  }
 
-      if (hasFeature) {
-        return callback.call(thisArg, this.getLayer(), null);
-      } else {
-        return undefined;
-      }
-    } else {
-      var imageSize =
-          [this.image_.getImage().width, this.image_.getImage().height];
+  var viewState = frameState.viewState;
 
-      if (!this.hitTransformationMatrix_) {
-        this.hitTransformationMatrix_ = this.getHitTransformationMatrix_(
-            frameState.size, imageSize);
+  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 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;
-      }
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) {
+  var hasFeature = false;
 
-      if (!this.hitCanvasContext_) {
-        this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
-      }
+  if (this.getGL().isContextLost()) {
+    return false;
+  }
 
-      this.hitCanvasContext_.clearRect(0, 0, 1, 1);
-      this.hitCanvasContext_.drawImage(this.image_.getImage(),
-          pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1);
+  var viewState = frameState.viewState;
 
-      var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
-      if (imageData[3] > 0) {
-        return callback.call(thisArg, this.getLayer(),  imageData);
-      } else {
-        return undefined;
+  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;
       }
     }
-  };
-
-
-  /**
-   * 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());
-
-    // 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');
-
-
-/**
- * @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.layer.Image = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (options));
+  }
+  return hasFeature;
 };
-ol.inherits(ol.layer.Image, ol.layer.Layer);
 
 
 /**
  * @inheritDoc
  */
-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);
+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 = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layer));
+      result = layerRenderer.forEachLayerAtPixel(
+          pixel, frameState, callback, thisArg);
+      if (result) {
+        return result;
+      }
+    }
   }
-  return renderer;
+  return undefined;
 };
 
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.tilelayershader');
 
-/**
- * Return the associated {@link ol.source.Image source} of the image layer.
- * @function
- * @return {ol.source.Image} Source.
- * @api
- */
-ol.layer.Image.prototype.getSource;
+goog.require('ol');
+goog.require('ol.webgl.Fragment');
+goog.require('ol.webgl.Vertex');
+
+
+ol.renderer.webgl.tilelayershader.fragment = new ol.webgl.Fragment(ol.DEBUG_WEBGL ?
+  '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' :
+  'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}');
+
+ol.renderer.webgl.tilelayershader.vertex = new ol.webgl.Vertex(ol.DEBUG_WEBGL ?
+  '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' :
+  '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;}');
+
+// This file is automatically generated, do not edit
+goog.provide('ol.renderer.webgl.tilelayershader.Locations');
+
+goog.require('ol');
 
-goog.provide('ol.layer.TileProperty');
 
 /**
- * @enum {string}
+ * @constructor
+ * @param {WebGLRenderingContext} gl GL.
+ * @param {WebGLProgram} program Program.
+ * @struct
  */
-ol.layer.TileProperty = {
-  PRELOAD: 'preload',
-  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
+ol.renderer.webgl.tilelayershader.Locations = function(gl, program) {
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_tileOffset = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_tileOffset' : 'd');
+
+  /**
+   * @type {WebGLUniformLocation}
+   */
+  this.u_texture = gl.getUniformLocation(
+      program, ol.DEBUG_WEBGL ? 'u_texture' : 'e');
+
+  /**
+   * @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 find correct globalCompositeOperation
+// FIXME large resolutions lead to too large framebuffers :-(
+// FIXME animated shaders! check in redraw
 
-goog.provide('ol.renderer.canvas.TileLayer');
+goog.provide('ol.renderer.webgl.TileLayer');
 
 goog.require('ol');
+goog.require('ol.LayerType');
 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.math');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.renderer.webgl.tilelayershader');
+goog.require('ol.renderer.webgl.tilelayershader.Locations');
+goog.require('ol.size');
 goog.require('ol.transform');
+goog.require('ol.webgl');
+goog.require('ol.webgl.Buffer');
 
 
 /**
  * @constructor
- * @extends {ol.renderer.canvas.IntermediateCanvas}
- * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer.
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Tile} tileLayer Tile layer.
+ * @api
  */
-ol.renderer.canvas.TileLayer = function(tileLayer) {
+ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
 
-  ol.renderer.canvas.IntermediateCanvas.call(this, tileLayer);
+  ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer);
 
   /**
-   * @protected
-   * @type {CanvasRenderingContext2D}
+   * @private
+   * @type {ol.webgl.Fragment}
    */
-  this.context = this.context === null ? null :  ol.dom.createCanvasContext2D();
+  this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment;
 
   /**
    * @private
-   * @type {number}
+   * @type {ol.webgl.Vertex}
    */
-  this.oversampling_;
+  this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex;
 
   /**
    * @private
-   * @type {ol.Extent}
+   * @type {ol.renderer.webgl.tilelayershader.Locations}
    */
-  this.renderedExtent_ = null;
+  this.locations_ = null;
 
   /**
-   * @protected
-   * @type {number}
+   * @private
+   * @type {ol.webgl.Buffer}
    */
-  this.renderedRevision;
+  this.renderArrayBuffer_ = new ol.webgl.Buffer([
+    0, 0, 0, 1,
+    1, 0, 1, 1,
+    0, 1, 0, 0,
+    1, 1, 1, 0
+  ]);
 
   /**
-   * @protected
-   * @type {!Array.<ol.Tile>}
+   * @private
+   * @type {ol.TileRange}
    */
-  this.renderedTiles = [];
+  this.renderedTileRange_ = null;
 
   /**
-   * @protected
+   * @private
    * @type {ol.Extent}
    */
-  this.tmpExtent = ol.extent.createEmpty();
+  this.renderedFramebufferExtent_ = null;
 
   /**
    * @private
-   * @type {ol.TileRange}
+   * @type {number}
    */
-  this.tmpTileRange_ = new ol.TileRange(0, 0, 0, 0);
+  this.renderedRevision_ = -1;
 
   /**
    * @private
-   * @type {ol.Transform}
+   * @type {ol.Size}
    */
-  this.imageTransform_ = ol.transform.create();
+  this.tmpSize_ = [0, 0];
+
+};
+ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
 
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.zDirection = 0;
 
+/**
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @param {ol.layer.Layer} layer The candidate layer.
+ * @return {boolean} The renderer can render the layer.
+ */
+ol.renderer.webgl.TileLayer['handles'] = function(type, layer) {
+  return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.TILE;
 };
-ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.IntermediateCanvas);
 
 
 /**
- * @private
- * @param {ol.Tile} tile Tile.
- * @return {boolean} Tile is drawable.
+ * Create a layer renderer.
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @param {ol.layer.Layer} layer The layer to be rendererd.
+ * @return {ol.renderer.webgl.TileLayer} The layer renderer.
  */
-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;
+ol.renderer.webgl.TileLayer['create'] = function(mapRenderer, layer) {
+  return new ol.renderer.webgl.TileLayer(
+      /** @type {ol.renderer.webgl.Map} */ (mapRenderer),
+      /** @type {ol.layer.Tile} */ (layer)
+  );
 };
 
+
 /**
  * @inheritDoc
  */
-ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(frameState, layerState) {
+ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
+  var context = this.mapRenderer.getContext();
+  context.deleteBuffer(this.renderArrayBuffer_);
+  ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
+  var mapRenderer = this.mapRenderer;
+
+  return (
+    /**
+     * @param {number} zoom Zoom level.
+     * @param {ol.TileRange} tileRange Tile range.
+     * @return {boolean} The tile range is fully loaded.
+     */
+    function(zoom, tileRange) {
+      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);
+    });
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
+  ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this);
+  this.locations_ = null;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) {
+
+  var mapRenderer = this.mapRenderer;
+  var gl = context.getGL();
 
-  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 tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer());
+  var tileSource = tileLayer.getSource();
   var tileGrid = tileSource.getTileGridForProjection(projection);
-  var z = tileGrid.getZForResolution(viewResolution, this.zDirection);
+  var z = tileGrid.getZForResolution(viewState.resolution);
   var tileResolution = tileGrid.getResolution(z);
-  var oversampling = Math.round(viewResolution / tileResolution) || 1;
+
+  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.getTileRangeForExtentAndZ(extent, z);
 
-  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 framebufferExtent;
+  if (this.renderedTileRange_ &&
+      this.renderedTileRange_.equals(tileRange) &&
+      this.renderedRevision_ == tileSource.getRevision()) {
+    framebufferExtent = this.renderedFramebufferExtent_;
+  } else {
 
-  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
-      extent, tileResolution);
-  var imageExtent = tileGrid.getTileRangeExtent(z, tileRange);
+    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
+    ];
 
-  var tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);
+    this.bindFramebuffer(frameState, framebufferDimension);
+    gl.viewport(0, 0, framebufferDimension, framebufferDimension);
 
-  /**
-   * @type {Object.<number, Object.<string, ol.Tile>>}
-   */
-  var tilesToDrawByZ = {};
-  tilesToDrawByZ[z] = {};
+    gl.clearColor(0, 0, 0, 0);
+    gl.clear(ol.webgl.COLOR_BUFFER_BIT);
+    gl.disable(ol.webgl.BLEND);
 
-  var findLoadedTiles = this.createLoadedTileFinder(
-      tileSource, projection, tilesToDrawByZ);
+    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
+    context.useProgram(program);
+    if (!this.locations_) {
+      this.locations_ = new ol.renderer.webgl.tilelayershader.Locations(gl, program);
+    }
 
-  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;
+    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;
           }
         }
-        continue;
-      }
-
-      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);
+        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;
         }
-      }
-
-    }
-  }
 
-  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
-      )) {
+        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);
+          }
+        }
 
-    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_;
       }
+
     }
 
-    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;
+    var u_tileOffset = new Float32Array(4);
+    var i, ii, tileKey, tilesToDraw;
     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);
+      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);
       }
     }
 
-    this.renderedRevision = sourceRevision;
-    this.renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling;
-    this.renderedExtent_ = imageExtent;
-  }
-
-  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]);
+    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);
-  this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
-      projection, extent, z, tileLayer.getPreload());
+  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);
 
-  return this.renderedTiles.length > 0;
-};
-
+  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);
 
-/**
- * @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 true;
 };
 
 
 /**
  * @inheritDoc
  */
-ol.renderer.canvas.TileLayer.prototype.getImage = function() {
-  var context = this.context;
-  return context ? context.canvas : null;
-};
+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]];
 
-/**
- * @function
- * @return {ol.layer.Tile|ol.layer.VectorTile}
- */
-ol.renderer.canvas.TileLayer.prototype.getLayer;
+  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);
 
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() {
-  return this.imageTransform_;
+  if (imageData[3] > 0) {
+    return callback.call(thisArg, this.getLayer(), imageData);
+  } else {
+    return undefined;
+  }
 };
 
-// This file is automatically generated, do not edit
-/* eslint openlayers-internal/no-missing-requires: 0 */
-goog.provide('ol.renderer.webgl.tilelayershader');
+goog.provide('ol.renderer.webgl.VectorLayer');
 
 goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
+goog.require('ol.LayerType');
+goog.require('ol.ViewHint');
+goog.require('ol.extent');
+goog.require('ol.render.webgl.ReplayGroup');
+goog.require('ol.renderer.Type');
+goog.require('ol.renderer.vector');
+goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.transform');
 
-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);
+/**
+ * @constructor
+ * @extends {ol.renderer.webgl.Layer}
+ * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
+ * @param {ol.layer.Vector} vectorLayer Vector layer.
+ * @api
+ */
+ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
 
+  ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer);
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {boolean}
    */
-  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';
-
+  this.dirty_ = false;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {number}
    */
-  ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}';
-
+  this.renderedRevision_ = -1;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {number}
    */
-  ol.renderer.webgl.tilelayershader.Fragment.SOURCE = ol.DEBUG_WEBGL ?
-      ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE :
-      ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE;
-
-
-  ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment();
-
+  this.renderedResolution_ = NaN;
 
   /**
-   * @constructor
-   * @extends {ol.webgl.Vertex}
-   * @struct
+   * @private
+   * @type {ol.Extent}
    */
-  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);
-
+  this.renderedExtent_ = ol.extent.createEmpty();
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {function(ol.Feature, ol.Feature): number|null}
    */
-  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';
-
+  this.renderedRenderOrder_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * @private
+   * @type {ol.render.webgl.ReplayGroup}
    */
-  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;}';
-
+  this.replayGroup_ = null;
 
   /**
-   * @const
-   * @type {string}
+   * The last layer state.
+   * @private
+   * @type {?ol.LayerState}
    */
-  ol.renderer.webgl.tilelayershader.Vertex.SOURCE = ol.DEBUG_WEBGL ?
-      ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE :
-      ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE;
+  this.layerState_ = null;
 
+};
+ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
 
-  ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex();
 
+/**
+ * Determine if this renderer handles the provided layer.
+ * @param {ol.renderer.Type} type The renderer type.
+ * @param {ol.layer.Layer} layer The candidate layer.
+ * @return {boolean} The renderer can render the layer.
+ */
+ol.renderer.webgl.VectorLayer['handles'] = function(type, layer) {
+  return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.VECTOR;
+};
 
-  /**
-   * @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');
+/**
+ * Create a layer renderer.
+ * @param {ol.renderer.Map} mapRenderer The map renderer.
+ * @param {ol.layer.Layer} layer The layer to be rendererd.
+ * @return {ol.renderer.webgl.VectorLayer} The layer renderer.
+ */
+ol.renderer.webgl.VectorLayer['create'] = function(mapRenderer, layer) {
+  return new ol.renderer.webgl.VectorLayer(
+      /** @type {ol.renderer.webgl.Map} */ (mapRenderer),
+      /** @type {ol.layer.Vector} */ (layer)
+  );
+};
 
-    /**
-     * @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');
+/**
+ * @inheritDoc
+ */
+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}
-     */
-    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
+/**
+ * @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;
+  }
+  ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
+};
 
-goog.provide('ol.renderer.webgl.TileLayer');
 
-goog.require('ol');
-goog.require('ol.TileState');
-goog.require('ol.TileRange');
-goog.require('ol.array');
-goog.require('ol.extent');
-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');
+/**
+ * @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 (ol.ENABLE_WEBGL) {
+/**
+ * @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);
+  }
+};
 
-  /**
-   * @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);
+/**
+ * @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);
 
-    /**
-     * @private
-     * @type {ol.webgl.Fragment}
-     */
-    this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment;
+  if (hasFeature) {
+    return callback.call(thisArg, this.getLayer(), null);
+  } else {
+    return undefined;
+  }
+};
 
-    /**
-     * @private
-     * @type {ol.webgl.Vertex}
-     */
-    this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex;
 
-    /**
-     * @private
-     * @type {ol.renderer.webgl.tilelayershader.Locations}
-     */
-    this.locations_ = null;
+/**
+ * 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();
+};
 
-    /**
-     * @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;
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) {
+
+  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
+  var vectorSource = vectorLayer.getSource();
+
+  this.updateLogos(frameState, vectorSource);
 
-    /**
-     * @private
-     * @type {ol.Extent}
-     */
-    this.renderedFramebufferExtent_ = null;
+  var animating = frameState.viewHints[ol.ViewHint.ANIMATING];
+  var interacting = frameState.viewHints[ol.ViewHint.INTERACTING];
+  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
+  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();
 
-    /**
-     * @private
-     * @type {number}
-     */
-    this.renderedRevision_ = -1;
+  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
+      (!updateWhileInteracting && interacting)) {
+    return true;
+  }
 
-    /**
-     * @private
-     * @type {ol.Size}
-     */
-    this.tmpSize_ = [0, 0];
+  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();
 
-  };
-  ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
+  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;
+  }
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
-    var context = this.mapRenderer.getContext();
-    context.deleteBuffer(this.renderArrayBuffer_);
-    ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
-  };
+  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);
   /**
-   * @inheritDoc
+   * @param {ol.Feature} feature Feature.
+   * @this {ol.renderer.webgl.VectorLayer}
    */
-  ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
-    var mapRenderer = this.mapRenderer;
-
-    return (
+  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 {number} zoom Zoom level.
-         * @param {ol.TileRange} tileRange Tile range.
-         * @return {boolean} The tile range is fully loaded.
+         * @param {ol.Feature} feature Feature.
          */
-        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);
-        });
-  };
+        function(feature) {
+          features.push(feature);
+        }, this);
+    features.sort(vectorLayerRenderOrder);
+    features.forEach(renderFeature, this);
+  } else {
+    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
+  }
+  replayGroup.finish(context);
 
+  this.renderedResolution_ = resolution;
+  this.renderedRevision_ = vectorLayerRevision;
+  this.renderedRenderOrder_ = vectorLayerRenderOrder;
+  this.renderedExtent_ = extent;
+  this.replayGroup_ = replayGroup;
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
-    ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this);
-    this.locations_ = null;
-  };
+  return true;
+};
 
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) {
+/**
+ * @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 {
+    loading = ol.renderer.vector.renderFeature(
+        replayGroup, feature, styles,
+        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
+        this.handleStyleImageChange_, this) || loading;
+  }
+  return loading;
+};
 
-    var mapRenderer = this.mapRenderer;
-    var gl = context.getGL();
+goog.provide('ol.Map');
 
-    var viewState = frameState.viewState;
-    var projection = viewState.projection;
+goog.require('ol');
+goog.require('ol.PluggableMap');
+goog.require('ol.PluginType');
+goog.require('ol.control');
+goog.require('ol.interaction');
+goog.require('ol.obj');
+goog.require('ol.plugins');
+goog.require('ol.renderer.canvas.ImageLayer');
+goog.require('ol.renderer.canvas.Map');
+goog.require('ol.renderer.canvas.TileLayer');
+goog.require('ol.renderer.canvas.VectorLayer');
+goog.require('ol.renderer.canvas.VectorTileLayer');
+goog.require('ol.renderer.webgl.ImageLayer');
+goog.require('ol.renderer.webgl.Map');
+goog.require('ol.renderer.webgl.TileLayer');
+goog.require('ol.renderer.webgl.VectorLayer');
 
-    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);
+if (ol.ENABLE_CANVAS) {
+  ol.plugins.register(ol.PluginType.MAP_RENDERER, ol.renderer.canvas.Map);
+  ol.plugins.registerMultiple(ol.PluginType.LAYER_RENDERER, [
+    ol.renderer.canvas.ImageLayer,
+    ol.renderer.canvas.TileLayer,
+    ol.renderer.canvas.VectorLayer,
+    ol.renderer.canvas.VectorTileLayer
+  ]);
+}
 
-    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 {
+if (ol.ENABLE_WEBGL) {
+  ol.plugins.register(ol.PluginType.MAP_RENDERER, ol.renderer.webgl.Map);
+  ol.plugins.registerMultiple(ol.PluginType.LAYER_RENDERER, [
+    ol.renderer.webgl.ImageLayer,
+    ol.renderer.webgl.TileLayer,
+    ol.renderer.webgl.VectorLayer
+  ]);
+}
 
-      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);
+/**
+ * @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.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.PluggableMap}
+ * @param {olx.MapOptions} options Map options.
+ * @fires ol.MapBrowserEvent
+ * @fires ol.MapEvent
+ * @fires ol.render.Event#postcompose
+ * @fires ol.render.Event#precompose
+ * @api
+ */
+ol.Map = function(options) {
+  options = ol.obj.assign({}, options);
+  if (!options.controls) {
+    options.controls = ol.control.defaults();
+  }
+  if (!options.interactions) {
+    options.interactions = ol.interaction.defaults();
+  }
 
-      gl.clearColor(0, 0, 0, 0);
-      gl.clear(ol.webgl.COLOR_BUFFER_BIT);
-      gl.disable(ol.webgl.BLEND);
+  ol.PluggableMap.call(this, options);
+};
+ol.inherits(ol.Map, ol.PluggableMap);
 
-      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);
-      }
+goog.provide('ol.net');
 
-      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);
+goog.require('ol');
 
-      /**
-       * @type {Object.<number, Object.<string, ol.Tile>>}
-       */
-      var tilesToDrawByZ = {};
-      tilesToDrawByZ[z] = {};
 
-      var findLoadedTiles = this.createLoadedTileFinder(
-          tileSource, projection, tilesToDrawByZ);
+/**
+ * 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);
+};
 
-      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) {
+goog.provide('ol.proj.common');
 
-          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;
-          }
+goog.require('ol.proj');
 
-          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);
-            }
-          }
 
-        }
+/**
+ * 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.render');
 
-      /** @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);
-        }
-      }
+goog.require('ol.has');
+goog.require('ol.transform');
+goog.require('ol.render.canvas.Immediate');
 
-      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;
-      }
 
-    }
+/**
+ * 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.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);
+};
 
-    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);
+goog.provide('ol.reproj');
 
-    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);
+goog.require('ol.dom');
+goog.require('ol.extent');
+goog.require('ol.math');
+goog.require('ol.proj');
 
-    return true;
-  };
 
+/**
+ * Calculates ideal resolution to use from the source in order to achieve
+ * pixel mapping as close as possible to 1:1 during reprojection.
+ * The resolution is calculated regardless of what resolutions
+ * are actually available in the dataset (TileGrid, Image, ...).
+ *
+ * @param {ol.proj.Projection} sourceProj Source projection.
+ * @param {ol.proj.Projection} targetProj Target projection.
+ * @param {ol.Coordinate} targetCenter Target center.
+ * @param {number} targetResolution Target resolution.
+ * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
+ */
+ol.reproj.calculateSourceResolution = function(sourceProj, targetProj,
+    targetCenter, targetResolution) {
 
-  /**
-   * @inheritDoc
-   */
-  ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
-    if (!this.framebuffer) {
-      return undefined;
-    }
+  var sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj);
 
-    var pixelOnMapScaled = [
-      pixel[0] / frameState.size[0],
-      (frameState.size[1] - pixel[1]) / frameState.size[1]];
+  // calculate the ideal resolution of the source data
+  var sourceResolution =
+      ol.proj.getPointResolution(targetProj, targetResolution, targetCenter);
 
-    var pixelOnFrameBufferScaled = ol.transform.apply(
-        this.texCoordMatrix, pixelOnMapScaled.slice());
-    var pixelOnFrameBuffer = [
-      pixelOnFrameBufferScaled[0] * this.framebufferDimension,
-      pixelOnFrameBufferScaled[1] * this.framebufferDimension];
+  var targetMetersPerUnit = targetProj.getMetersPerUnit();
+  if (targetMetersPerUnit !== undefined) {
+    sourceResolution *= targetMetersPerUnit;
+  }
+  var sourceMetersPerUnit = sourceProj.getMetersPerUnit();
+  if (sourceMetersPerUnit !== undefined) {
+    sourceResolution /= sourceMetersPerUnit;
+  }
 
-    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);
+  // 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.
 
-    if (imageData[3] > 0) {
-      return callback.call(thisArg, this.getLayer(), imageData);
-    } else {
-      return undefined;
+  var sourceExtent = sourceProj.getExtent();
+  if (!sourceExtent || ol.extent.containsCoordinate(sourceExtent, sourceCenter)) {
+    var compensationFactor =
+        ol.proj.getPointResolution(sourceProj, sourceResolution, sourceCenter) /
+        sourceResolution;
+    if (isFinite(compensationFactor) && compensationFactor > 0) {
+      sourceResolution /= compensationFactor;
     }
-  };
+  }
 
-}
+  return sourceResolution;
+};
 
-goog.provide('ol.layer.Tile');
 
-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');
+/**
+ * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap
+ * in order to mask gaps caused by antialiasing.
+ *
+ * @param {number} centroidX Centroid of the triangle (x coordinate in pixels).
+ * @param {number} centroidY Centroid of the triangle (y coordinate in pixels).
+ * @param {number} x X coordinate of the point (in pixels).
+ * @param {number} y Y coordinate of the point (in pixels).
+ * @return {ol.Coordinate} New point 1 px farther from the centroid.
+ * @private
+ */
+ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, x, y) {
+  var dX = x - centroidX, dY = y - centroidY;
+  var distance = Math.sqrt(dX * dX + dY * dY);
+  return [Math.round(x + dX / distance), Math.round(y + dY / distance)];
+};
 
 
 /**
- * @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.
+ * Renders the source data into new canvas based on the triangulation.
  *
- * @constructor
- * @extends {ol.layer.Layer}
- * @fires ol.render.Event
- * @param {olx.layer.TileOptions=} opt_options Tile layer options.
- * @api
+ * @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.layer.Tile = function(opt_options) {
-  var options = opt_options ? opt_options : {};
+ol.reproj.render = function(width, height, pixelRatio,
+    sourceResolution, sourceExtent, targetResolution, targetExtent,
+    triangulation, sources, gutter, opt_renderEdges) {
 
-  var baseOptions = ol.obj.assign({}, options);
+  var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width),
+      Math.round(pixelRatio * height));
 
-  delete baseOptions.preload;
-  delete baseOptions.useInterimTilesOnError;
-  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (baseOptions));
+  if (sources.length === 0) {
+    return context.canvas;
+  }
 
-  this.setPreload(options.preload !== undefined ? options.preload : 0);
-  this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ?
-      options.useInterimTilesOnError : true);
-};
-ol.inherits(ol.layer.Tile, ol.layer.Layer);
+  context.scale(pixelRatio, pixelRatio);
 
+  var sourceDataExtent = ol.extent.createEmpty();
+  sources.forEach(function(src, i, arr) {
+    ol.extent.extend(sourceDataExtent, src.extent);
+  });
 
-/**
- * @inheritDoc
- */
-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);
-  }
-  return renderer;
-};
+  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);
+  });
+
+  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;
+    }
 
-/**
- * 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));
-};
+    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();
 
-/**
- * Return the associated {@link ol.source.Tile tilesource} of the layer.
- * @function
- * @return {ol.source.Tile} Source.
- * @api
- */
-ol.layer.Tile.prototype.getSource;
+    context.transform(
+        affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);
 
+    context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
+        sourceDataExtent[3] - sourceNumericalShiftY);
 
-/**
- * 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);
-};
+    context.scale(sourceResolution / pixelRatio,
+        -sourceResolution / pixelRatio);
 
+    context.drawImage(stitchContext.canvas, 0, 0);
+    context.restore();
+  });
 
-/**
- * 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));
-};
+  if (opt_renderEdges) {
+    context.save();
 
+    context.strokeStyle = 'black';
+    context.lineWidth = 1;
 
-/**
- * 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);
-};
+    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;
 
-goog.provide('ol.layer.VectorTileRenderType');
+      context.beginPath();
+      context.moveTo(u1, v1);
+      context.lineTo(u0, v0);
+      context.lineTo(u2, v2);
+      context.closePath();
+      context.stroke();
+    });
 
-/**
- * @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.layer.VectorTileRenderType = {
-  IMAGE: 'image',
-  HYBRID: 'hybrid',
-  VECTOR: 'vector'
+    context.restore();
+  }
+  return context.canvas;
 };
 
-goog.provide('ol.renderer.canvas.VectorTileLayer');
+goog.provide('ol.reproj.Triangulation');
 
 goog.require('ol');
-goog.require('ol.TileState');
-goog.require('ol.dom');
 goog.require('ol.extent');
+goog.require('ol.math');
 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');
 
 
 /**
+ * @classdesc
+ * Class containing triangulation of the given target extent.
+ * Used for determining source data and the reprojection itself.
+ *
+ * @param {ol.proj.Projection} sourceProj Source projection.
+ * @param {ol.proj.Projection} targetProj Target projection.
+ * @param {ol.Extent} targetExtent Target extent to triangulate.
+ * @param {ol.Extent} maxSourceExtent Maximal source extent that can be used.
+ * @param {number} errorThreshold Acceptable error (in source units).
  * @constructor
- * @extends {ol.renderer.canvas.TileLayer}
- * @param {ol.layer.VectorTile} layer VectorTile layer.
  */
-ol.renderer.canvas.VectorTileLayer = function(layer) {
+ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
+    maxSourceExtent, errorThreshold) {
 
   /**
-   * @type {CanvasRenderingContext2D}
+   * @type {ol.proj.Projection}
+   * @private
    */
-  this.context = null;
-
-  ol.renderer.canvas.TileLayer.call(this, layer);
+  this.sourceProj_ = sourceProj;
 
   /**
+   * @type {ol.proj.Projection}
    * @private
-   * @type {boolean}
    */
-  this.dirty_ = false;
+  this.targetProj_ = targetProj;
+
+  /** @type {!Object.<string, ol.Coordinate>} */
+  var transformInvCache = {};
+  var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_);
 
   /**
+   * @param {ol.Coordinate} c A coordinate.
+   * @return {ol.Coordinate} Transformed coordinate.
    * @private
-   * @type {number}
    */
-  this.renderedLayerRevision_;
+  this.transformInv_ = function(c) {
+    var key = c[0] + '/' + c[1];
+    if (!transformInvCache[key]) {
+      transformInvCache[key] = transformInv(c);
+    }
+    return transformInvCache[key];
+  };
 
   /**
+   * @type {ol.Extent}
    * @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;
-
-};
-ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
-
-
-/**
- * @const
- * @type {!Object.<string, Array.<ol.render.ReplayType>>}
- */
-ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = {
-  'image': ol.render.replay.ORDER,
-  'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
-};
+  this.maxSourceExtent_ = maxSourceExtent;
 
+  /**
+   * @type {number}
+   * @private
+   */
+  this.errorThresholdSquared_ = errorThreshold * errorThreshold;
 
-/**
- * @const
- * @type {!Object.<string, Array.<ol.render.ReplayType>>}
- */
-ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = {
-  'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
-  'vector': ol.render.replay.ORDER
-};
+  /**
+   * @type {Array.<ol.ReprojTriangle>}
+   * @private
+   */
+  this.triangles_ = [];
 
+  /**
+   * Indicates that the triangulation crosses edge of the source projection.
+   * @type {boolean}
+   * @private
+   */
+  this.wrapsXInSource_ = false;
 
-/**
- * @inheritDoc
- */
-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);
-};
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.canWrapXInSource_ = this.sourceProj_.canWrapX() &&
+      !!maxSourceExtent &&
+      !!this.sourceProj_.getExtent() &&
+      (ol.extent.getWidth(maxSourceExtent) ==
+       ol.extent.getWidth(this.sourceProj_.getExtent()));
 
+  /**
+   * @type {?number}
+   * @private
+   */
+  this.sourceWorldWidth_ = this.sourceProj_.getExtent() ?
+    ol.extent.getWidth(this.sourceProj_.getExtent()) : null;
 
-/**
- * @param {ol.VectorImageTile} tile Tile.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-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;
+  /**
+   * @type {?number}
+   * @private
+   */
+  this.targetWorldWidth_ = this.targetProj_.getExtent() ?
+    ol.extent.getWidth(this.targetProj_.getExtent()) : null;
 
-  var replayState = tile.getReplayState();
-  if (!replayState.dirty && replayState.renderedRevision == revision &&
-      replayState.renderedRenderOrder == renderOrder) {
-    return;
-  }
+  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);
 
-  for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
-    var sourceTile = tile.getTile(tile.tileKeys[t]);
-    sourceTile.replayGroup = null;
-    replayState.dirty = false;
+  this.addQuad_(
+      destinationTopLeft, destinationTopRight,
+      destinationBottomRight, destinationBottomLeft,
+      sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
+      ol.RASTER_REPROJECTION_MAX_SUBDIVISION);
 
-    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);
+  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]);
+    });
 
-    /**
-     * @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);
+    // Shift triangles to be as close to `leftBound` as possible
+    // (if the distance is more than `worldWidth / 2` it can be closer.
+    this.triangles_.forEach(function(triangle) {
+      if (Math.max(triangle.source[0][0], triangle.source[1][0],
+          triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) {
+        var newTriangle = [[triangle.source[0][0], triangle.source[0][1]],
+          [triangle.source[1][0], triangle.source[1][1]],
+          [triangle.source[2][0], triangle.source[2][1]]];
+        if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) {
+          newTriangle[0][0] -= this.sourceWorldWidth_;
         }
-      }
-      if (styles) {
-        if (!Array.isArray(styles)) {
-          styles = [styles];
+        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_;
         }
-        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);
+        // 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;
+        }
       }
-      renderFeature.call(this, feature);
-    }
-    replayGroup.finish();
-    sourceTile.setReplayGroup(tile.tileCoord.toString(), replayGroup);
+    }, this);
   }
-  replayState.renderedRevision = revision;
-  replayState.renderedRenderOrder = renderOrder;
+
+  transformInvCache = {};
 };
 
 
 /**
- * @inheritDoc
+ * 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.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);
-  }
+ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c,
+    aSrc, bSrc, cSrc) {
+  this.triangles_.push({
+    source: [aSrc, bSrc, cSrc],
+    target: [a, b, c]
+  });
 };
 
 
 /**
- * @inheritDoc
+ * 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.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 = {};
+ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d,
+    aSrc, bSrc, cSrc, dSrc, maxSubdivision) {
 
-  /** @type {Array.<ol.VectorImageTile>} */
-  var renderedTiles = this.renderedTiles;
+  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_);
 
-  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;
+  // 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;
     }
-    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);
-            }
-          });
+    if (!wrapsX && this.sourceProj_.isGlobal() && sourceCoverageX) {
+      needsSubdivision |=
+          sourceCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH;
     }
   }
-  return found;
-};
 
+  if (!needsSubdivision && this.maxSourceExtent_) {
+    if (!ol.extent.intersects(sourceQuadExtent, this.maxSourceExtent_)) {
+      // whole quad outside source projection extent -> ignore
+      return;
+    }
+  }
 
-/**
- * @param {ol.VectorTile} tile Tile.
- * @param {olx.FrameState} frameState Frame state.
- * @return {ol.Transform} transform Transform.
- * @private
- */
-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);
+  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);
 
-/**
- * Handle changes in image style state.
- * @param {ol.events.Event} event Image style change event.
- * @private
- */
-ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(event) {
-  this.renderIfReadyAndVisible();
-};
+      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);
 
-/**
- * @inheritDoc
- */
-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);
+        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;
     }
   }
-  ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
+
+  if (wrapsX) {
+    if (!this.canWrapXInSource_) {
+      return;
+    }
+    this.wrapsXInSource_ = true;
+  }
+
+  this.addTriangle_(a, c, d, aSrc, cSrc, dSrc);
+  this.addTriangle_(a, b, c, aSrc, bSrc, cSrc);
 };
 
 
 /**
- * @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.
+ * Calculates extent of the 'source' coordinates from all the triangles.
+ *
+ * @return {ol.Extent} Calculated extent.
  */
-ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) {
-  if (!styles) {
-    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;
+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;
 };
 
 
 /**
- * @param {ol.VectorImageTile} tile Tile.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @private
+ * @return {Array.<ol.ReprojTriangle>} Array of the calculated triangles.
  */
-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);
-    }
-  }
+ol.reproj.Triangulation.prototype.getTriangles = function() {
+  return this.triangles_;
 };
 
-goog.provide('ol.layer.VectorTile');
+goog.provide('ol.reproj.Image');
 
 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');
+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');
 
 
 /**
  * @classdesc
- * Layer for vector tile data that is rendered client-side.
- * Note that any property set in the options is set as a {@link ol.Object}
- * property on the layer object; for example, setting `title: 'My Title'` in the
- * options means that `title` is observable, and has get/set accessors.
+ * Class encapsulating single reprojected image.
+ * See {@link ol.source.Image}.
  *
  * @constructor
- * @extends {ol.layer.Vector}
- * @param {olx.layer.VectorTileOptions=} opt_options Options.
- * @api
+ * @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.layer.VectorTile = function(opt_options) {
-  var options = opt_options ? opt_options : {};
+ol.reproj.Image = function(sourceProj, targetProj,
+    targetExtent, targetResolution, pixelRatio, getImageFunction) {
 
-  var baseOptions = ol.obj.assign({}, options);
+  /**
+   * @private
+   * @type {ol.proj.Projection}
+   */
+  this.targetProj_ = targetProj;
 
-  delete baseOptions.preload;
-  delete baseOptions.useInterimTilesOnError;
-  ol.layer.Vector.call(this,  /** @type {olx.layer.VectorOptions} */ (baseOptions));
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.maxSourceExtent_ = sourceProj.getExtent();
+  var maxTargetExtent = targetProj.getExtent();
 
-  this.setPreload(options.preload ? options.preload : 0);
-  this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
-      options.useInterimTilesOnError : true);
+  var limitedTargetExtent = maxTargetExtent ?
+    ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
+
+  var targetCenter = ol.extent.getCenter(limitedTargetExtent);
+  var sourceResolution = ol.reproj.calculateSourceResolution(
+      sourceProj, targetProj, targetCenter, targetResolution);
 
-  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'`
+  var errorThresholdInPixels = ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
 
   /**
    * @private
-   * @type {ol.layer.VectorTileRenderType|string}
+   * @type {!ol.reproj.Triangulation}
    */
-  this.renderMode_ = options.renderMode || ol.layer.VectorTileRenderType.HYBRID;
+  this.triangulation_ = new ol.reproj.Triangulation(
+      sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_,
+      sourceResolution * errorThresholdInPixels);
 
-};
-ol.inherits(ol.layer.VectorTile, ol.layer.Vector);
+  /**
+   * @private
+   * @type {number}
+   */
+  this.targetResolution_ = targetResolution;
+
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.targetExtent_ = targetExtent;
 
+  var sourceExtent = this.triangulation_.calculateSourceExtent();
 
-/**
- * @inheritDoc
- */
-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 renderer;
-};
+  /**
+   * @private
+   * @type {ol.ImageBase}
+   */
+  this.sourceImage_ =
+      getImageFunction(sourceExtent, sourceResolution, pixelRatio);
 
+  /**
+   * @private
+   * @type {number}
+   */
+  this.sourcePixelRatio_ =
+      this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1;
 
-/**
- * Return the level as number to which we will preload tiles up to.
- * @return {number} The level to preload tiles up to.
- * @observable
- * @api
- */
-ol.layer.VectorTile.prototype.getPreload = function() {
-  return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
-};
+  /**
+   * @private
+   * @type {HTMLCanvasElement}
+   */
+  this.canvas_ = null;
+
+  /**
+   * @private
+   * @type {?ol.EventsKey}
+   */
+  this.sourceListenerKey_ = null;
 
 
-/**
- * @return {ol.layer.VectorTileRenderType|string} The render mode.
- */
-ol.layer.VectorTile.prototype.getRenderMode = function() {
-  return this.renderMode_;
+  var state = ol.ImageState.LOADED;
+
+  if (this.sourceImage_) {
+    state = ol.ImageState.IDLE;
+  }
+
+  ol.ImageBase.call(this, targetExtent, targetResolution, this.sourcePixelRatio_, state);
 };
+ol.inherits(ol.reproj.Image, ol.ImageBase);
 
 
 /**
- * Whether we use interim tiles on error.
- * @return {boolean} Use interim tiles on error.
- * @observable
- * @api
+ * @inheritDoc
  */
-ol.layer.VectorTile.prototype.getUseInterimTilesOnError = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
+ol.reproj.Image.prototype.disposeInternal = function() {
+  if (this.state == ol.ImageState.LOADING) {
+    this.unlistenSource_();
+  }
+  ol.ImageBase.prototype.disposeInternal.call(this);
 };
 
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.layer.VectorTile.prototype.setPreload = function(preload) {
-  this.set(ol.layer.TileProperty.PRELOAD, preload);
+ol.reproj.Image.prototype.getImage = function() {
+  return this.canvas_;
 };
 
 
 /**
- * Set whether we use interim tiles on error.
- * @param {boolean} useInterimTilesOnError Use interim tiles on error.
- * @observable
- * @api
+ * @return {ol.proj.Projection} Projection.
  */
-ol.layer.VectorTile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
-  this.set(
-      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
+ol.reproj.Image.prototype.getProjection = function() {
+  return this.targetProj_;
 };
 
-goog.provide('ol.net');
-
-goog.require('ol');
-
 
 /**
- * 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'.
+ * @private
  */
-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);
+ol.reproj.Image.prototype.reproject_ = function() {
+  var sourceState = this.sourceImage_.getState();
+  if (sourceState == ol.ImageState.LOADED) {
+    var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_;
+    var height =
+        ol.extent.getHeight(this.targetExtent_) / this.targetResolution_;
+
+    this.canvas_ = ol.reproj.render(width, height, this.sourcePixelRatio_,
+        this.sourceImage_.getResolution(), this.maxSourceExtent_,
+        this.targetResolution_, this.targetExtent_, this.triangulation_, [{
+          extent: this.sourceImage_.getExtent(),
+          image: this.sourceImage_.getImage()
+        }], 0);
   }
-  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);
+  this.state = sourceState;
+  this.changed();
 };
 
-goog.provide('ol.proj.common');
-
-goog.require('ol.proj');
-
 
 /**
- * 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
+ * @inheritDoc
  */
-ol.proj.common.add = ol.proj.addCommon;
-
-goog.provide('ol.render');
+ol.reproj.Image.prototype.load = function() {
+  if (this.state == ol.ImageState.IDLE) {
+    this.state = ol.ImageState.LOADING;
+    this.changed();
 
-goog.require('ol.has');
-goog.require('ol.transform');
-goog.require('ol.render.canvas.Immediate');
+    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();
+    }
+  }
+};
 
 
 /**
- * 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
+ * @private
  */
-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);
+ol.reproj.Image.prototype.unlistenSource_ = function() {
+  ol.events.unlistenByKey(/** @type {!ol.EventsKey} */ (this.sourceListenerKey_));
+  this.sourceListenerKey_ = null;
 };
 
 goog.provide('ol.reproj.Tile');
@@ -71963,8 +74376,7 @@ goog.require('ol.reproj.Triangulation');
 ol.reproj.Tile = function(sourceProj, sourceTileGrid,
     targetProj, targetTileGrid, tileCoord, wrappedTileCoord,
     pixelRatio, gutter, getTileFunction,
-    opt_errorThreshold,
-    opt_renderEdges) {
+    opt_errorThreshold, opt_renderEdges) {
   ol.Tile.call(this, tileCoord, ol.TileState.IDLE);
 
   /**
@@ -72032,7 +74444,7 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid,
   var maxSourceExtent = this.sourceTileGrid_.getExtent();
 
   var limitedTargetExtent = maxTargetExtent ?
-      ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
+    ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
 
   if (ol.extent.getArea(limitedTargetExtent) === 0) {
     // Tile is completely outside range -> EMPTY
@@ -72066,7 +74478,7 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid,
   }
 
   var errorThresholdInPixels = opt_errorThreshold !== undefined ?
-      opt_errorThreshold : ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
+    opt_errorThreshold : ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
 
   /**
    * @private
@@ -72252,31 +74664,31 @@ ol.TileUrlFunction.createFromTemplate = function(template, tileGrid) {
   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();
-              });
-        }
-      });
+    /**
+     * @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();
+            });
+      }
+    });
 };
 
 
@@ -72305,21 +74717,21 @@ ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
     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);
-        }
-      });
+    /**
+     * @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);
+      }
+    });
 };
 
 
@@ -72368,6 +74780,7 @@ goog.provide('ol.TileCache');
 
 goog.require('ol');
 goog.require('ol.structs.LRUCache');
+goog.require('ol.tilecoord');
 
 
 /**
@@ -72378,25 +74791,12 @@ goog.require('ol.structs.LRUCache');
  */
 ol.TileCache = function(opt_highWaterMark) {
 
-  ol.structs.LRUCache.call(this);
-
-  /**
-   * @type {number}
-   */
-  this.highWaterMark = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048;
+  ol.structs.LRUCache.call(this, opt_highWaterMark);
 
 };
 ol.inherits(ol.TileCache, ol.structs.LRUCache);
 
 
-/**
- * @return {boolean} Can expire cache.
- */
-ol.TileCache.prototype.canExpireCache = function() {
-  return this.getCount() > this.highWaterMark;
-};
-
-
 /**
  * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
  */
@@ -72413,6 +74813,25 @@ ol.TileCache.prototype.expireCache = function(usedTiles) {
   }
 };
 
+
+/**
+ * Prune all tiles from the cache that don't have the same z as the newest tile.
+ */
+ol.TileCache.prototype.pruneExceptNewestZ = function() {
+  if (this.getCount() === 0) {
+    return;
+  }
+  var key = this.peekFirstKey();
+  var tileCoord = ol.tilecoord.fromKey(key);
+  var z = tileCoord[0];
+  this.forEach(function(tile) {
+    if (tile.tileCoord[0] !== z) {
+      this.remove(ol.tilecoord.getKey(tile.tileCoord));
+      tile.dispose();
+    }
+  }, this);
+};
+
 goog.provide('ol.source.Tile');
 
 goog.require('ol');
@@ -72460,7 +74879,7 @@ ol.source.Tile = function(options) {
    * @type {number}
    */
   this.tilePixelRatio_ = options.tilePixelRatio !== undefined ?
-      options.tilePixelRatio : 1;
+    options.tilePixelRatio : 1;
 
   /**
    * @protected
@@ -72486,6 +74905,12 @@ ol.source.Tile = function(options) {
    */
   this.key_ = '';
 
+  /**
+   * @protected
+   * @type {olx.TileOptions}
+   */
+  this.tileOptions = {transition: options.transition};
+
 };
 ol.inherits(ol.source.Tile, ol.source.Source);
 
@@ -72529,7 +74954,7 @@ ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange,
   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);
+      tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
       loaded = false;
       if (tileCache.containsKey(tileCoordKey)) {
         tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey));
@@ -72579,16 +75004,6 @@ ol.source.Tile.prototype.setKey = function(key) {
 };
 
 
-/**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {string} Key.
- * @protected
- */
-ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
-
-
 /**
  * @param {ol.proj.Projection} projection Projection.
  * @return {boolean} Opaque.
@@ -72659,12 +75074,11 @@ ol.source.Tile.prototype.getTileCacheForProjection = function(projection) {
 /**
  * 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.
+ * provided `pixelRatio` as close as possible.
+ * @param {number} pixelRatio Pixel ratio.
  * @return {number} Tile pixel ratio.
  */
-ol.source.Tile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
+ol.source.Tile.prototype.getTilePixelRatio = function(pixelRatio) {
   return this.tilePixelRatio_;
 };
 
@@ -72698,7 +75112,7 @@ ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection)
  */
 ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) {
   var projection = opt_projection !== undefined ?
-      opt_projection : this.getProjection();
+    opt_projection : this.getProjection();
   var tileGrid = this.getTileGridForProjection(projection);
   if (this.getWrapX() && projection.isGlobal()) {
     tileCoord = ol.tilegrid.wrapX(tileGrid, tileCoord, projection);
@@ -72766,7 +75180,8 @@ ol.source.TileEventType = {
   TILELOADSTART: 'tileloadstart',
 
   /**
-   * Triggered when a tile finishes loading.
+   * Triggered when a tile finishes loading, either when its data is loaded,
+   * or when loading was aborted because the tile is no longer needed.
    * @event ol.source.Tile.Event#tileloadend
    * @api
    */
@@ -72788,6 +75203,7 @@ goog.require('ol.TileState');
 goog.require('ol.TileUrlFunction');
 goog.require('ol.source.Tile');
 goog.require('ol.source.TileEventType');
+goog.require('ol.tilecoord');
 
 
 /**
@@ -72812,7 +75228,8 @@ ol.source.UrlTile = function(options) {
     state: options.state,
     tileGrid: options.tileGrid,
     tilePixelRatio: options.tilePixelRatio,
-    wrapX: options.wrapX
+    wrapX: options.wrapX,
+    transition: options.transition
   });
 
   /**
@@ -72826,8 +75243,8 @@ ol.source.UrlTile = function(options) {
    * @type {ol.TileUrlFunctionType}
    */
   this.tileUrlFunction = this.fixedTileUrlFunction ?
-      this.fixedTileUrlFunction.bind(this) :
-      ol.TileUrlFunction.nullTileUrlFunction;
+    this.fixedTileUrlFunction.bind(this) :
+    ol.TileUrlFunction.nullTileUrlFunction;
 
   /**
    * @protected
@@ -72844,6 +75261,12 @@ ol.source.UrlTile = function(options) {
     this.setTileUrlFunction(options.tileUrlFunction);
   }
 
+  /**
+   * @private
+   * @type {Object.<number, boolean>}
+   */
+  this.tileLoadingKeys_ = {};
+
 };
 ol.inherits(ol.source.UrlTile, ol.source.Tile);
 
@@ -72893,21 +75316,20 @@ ol.source.UrlTile.prototype.getUrls = function() {
  */
 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
+  var uid = ol.getUid(tile);
+  var tileState = tile.getState();
+  var type;
+  if (tileState == ol.TileState.LOADING) {
+    this.tileLoadingKeys_[uid] = true;
+    type = ol.source.TileEventType.TILELOADSTART;
+  } else if (uid in this.tileLoadingKeys_) {
+    delete this.tileLoadingKeys_[uid];
+    type = tileState == ol.TileState.ERROR ? ol.source.TileEventType.TILELOADERROR :
+      (tileState == ol.TileState.LOADED || tileState == ol.TileState.ABORT) ?
+        ol.source.TileEventType.TILELOADEND : undefined;
+  }
+  if (type != undefined) {
+    this.dispatchEvent(new ol.source.Tile.Event(type, tile));
   }
 };
 
@@ -72932,6 +75354,7 @@ ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) {
  */
 ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) {
   this.tileUrlFunction = tileUrlFunction;
+  this.tileCache.pruneExceptNewestZ();
   if (typeof opt_key !== 'undefined') {
     this.setKey(opt_key);
   } else {
@@ -72948,8 +75371,8 @@ ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_k
 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);
+    this.fixedTileUrlFunction.bind(this) :
+    ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url);
 };
 
 
@@ -72962,8 +75385,8 @@ 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);
+    this.fixedTileUrlFunction.bind(this) :
+    ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), key);
 };
 
 
@@ -72971,7 +75394,7 @@ ol.source.UrlTile.prototype.setUrls = function(urls) {
  * @inheritDoc
  */
 ol.source.UrlTile.prototype.useTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
+  var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
   if (this.tileCache.containsKey(tileCoordKey)) {
     this.tileCache.get(tileCoordKey);
   }
@@ -72988,6 +75411,7 @@ goog.require('ol.events.EventType');
 goog.require('ol.proj');
 goog.require('ol.reproj.Tile');
 goog.require('ol.source.UrlTile');
+goog.require('ol.tilecoord');
 goog.require('ol.tilegrid');
 
 
@@ -73013,12 +75437,13 @@ ol.source.TileImage = function(options) {
     state: options.state,
     tileGrid: options.tileGrid,
     tileLoadFunction: options.tileLoadFunction ?
-        options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction,
+      options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction,
     tilePixelRatio: options.tilePixelRatio,
     tileUrlFunction: options.tileUrlFunction,
     url: options.url,
     urls: options.urls,
-    wrapX: options.wrapX
+    wrapX: options.wrapX,
+    transition: options.transition
   });
 
   /**
@@ -73031,10 +75456,10 @@ ol.source.TileImage = function(options) {
   /**
    * @protected
    * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string,
-   *        ?string, ol.TileLoadFunctionType)}
+   *        ?string, ol.TileLoadFunctionType, olx.TileOptions=)}
    */
   this.tileClass = options.tileClass !== undefined ?
-      options.tileClass : ol.ImageTile;
+    options.tileClass : ol.ImageTile;
 
   /**
    * @protected
@@ -73195,13 +75620,14 @@ ol.source.TileImage.prototype.createTile_ = function(z, x, y, pixelRatio, projec
   var urlTileCoord = this.getTileCoordForTileUrlFunction(
       tileCoord, projection);
   var tileUrl = urlTileCoord ?
-      this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
+    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);
+      this.tileLoadFunction,
+      this.tileOptions);
   tile.key = key;
   ol.events.listen(tile, ol.events.EventType.CHANGE,
       this.handleTileChange, this);
@@ -73213,16 +75639,16 @@ ol.source.TileImage.prototype.createTile_ = function(z, x, y, pixelRatio, projec
  * @inheritDoc
  */
 ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection) {
+  var sourceProjection = /** @type {!ol.proj.Projection} */ (this.getProjection());
   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));
+      !sourceProjection || !projection ||
+      ol.proj.equivalent(sourceProjection, projection)) {
+    return this.getTileInternal(z, x, y, pixelRatio, sourceProjection || projection);
   } else {
     var cache = this.getTileCacheForProjection(projection);
     var tileCoord = [z, x, y];
     var tile;
-    var tileCoordKey = this.getKeyZXY.apply(this, tileCoord);
+    var tileCoordKey = ol.tilecoord.getKey(tileCoord);
     if (cache.containsKey(tileCoordKey)) {
       tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey));
     }
@@ -73230,7 +75656,6 @@ ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection
     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 =
@@ -73248,6 +75673,7 @@ ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection
 
       if (tile) {
         newTile.interimTile = tile;
+        newTile.refreshInterimChain();
         cache.replace(tileCoordKey, newTile);
       } else {
         cache.set(tileCoordKey, newTile);
@@ -73269,7 +75695,7 @@ ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection
  */
 ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) {
   var tile = null;
-  var tileCoordKey = this.getKeyZXY(z, x, y);
+  var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
   var key = this.getKey();
   if (!this.tileCache.containsKey(tileCoordKey)) {
     tile = this.createTile_(z, x, y, pixelRatio, projection, key);
@@ -73352,7 +75778,6 @@ ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
 goog.provide('ol.source.BingMaps');
 
 goog.require('ol');
-goog.require('ol.Attribution');
 goog.require('ol.TileUrlFunction');
 goog.require('ol.extent');
 goog.require('ol.net');
@@ -73389,7 +75814,8 @@ ol.source.BingMaps = function(options) {
     state: ol.source.State.LOADING,
     tileLoadFunction: options.tileLoadFunction,
     tilePixelRatio: this.hidpi_ ? 2 : 1,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
+    wrapX: options.wrapX !== undefined ? options.wrapX : true,
+    transition: options.transition
   });
 
   /**
@@ -73418,7 +75844,8 @@ ol.source.BingMaps = function(options) {
 
   var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
       this.imagerySet_ +
-      '?uriScheme=https&include=ImageryProviders&key=' + this.apiKey_;
+      '?uriScheme=https&include=ImageryProviders&key=' + this.apiKey_ +
+      '&c=' + this.culture_;
 
   ol.net.jsonp(url, this.handleImageryMetadataResponse.bind(this), undefined,
       'jsonp');
@@ -73431,14 +75858,12 @@ 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}
+ * @type {string}
  * @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.BingMaps.TOS_ATTRIBUTION = '<a class="ol-attribution-bing-tos" ' +
+      'href="https://www.microsoft.com/maps/product/terms.html">' +
+      'Terms of Use</a>';
 
 
 /**
@@ -73487,12 +75912,12 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse = function(response)
   var sourceProjection = this.getProjection();
   var extent = ol.tilegrid.extentFromProjection(sourceProjection);
   var tileSize = resource.imageWidth == resource.imageHeight ?
-      resource.imageWidth : [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()
+    tileSize: tileSize / (this.hidpi_ ? 2 : 1)
   });
   this.tileGrid = tileGrid;
 
@@ -73505,57 +75930,58 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse = function(response)
             .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));
+          /**
+           * @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];
+    this.setAttributions(function(frameState) {
+      var attributions = [];
+      var zoom = frameState.viewState.zoom;
+      resource.imageryProviders.map(function(imageryProvider) {
+        var intersects = false;
+        var coverageAreas = imageryProvider.coverageAreas;
+        for (var i = 0, ii = coverageAreas.length; i < ii; ++i) {
+          var coverageArea = coverageAreas[i];
+          if (zoom >= coverageArea.zoomMin && zoom <= coverageArea.zoomMax) {
+            var bbox = coverageArea.bbox;
+            var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]];
+            var extent = ol.extent.applyTransform(epsg4326Extent, transform);
+            if (ol.extent.intersects(extent, frameState.extent)) {
+              intersects = true;
+              break;
+            }
           }
         }
+        if (intersects) {
+          attributions.push(imageryProvider.attribution);
+        }
       });
-      return new ol.Attribution({html: html, tileRanges: tileRanges});
+
+      attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION);
+      return attributions;
     });
-    attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION);
-    this.setAttributions(attributions);
   }
 
   this.setLogo(brandLogoUri);
@@ -73595,15 +76021,15 @@ goog.require('ol.tilegrid');
 ol.source.XYZ = function(opt_options) {
   var options = opt_options || {};
   var projection = options.projection !== undefined ?
-      options.projection : 'EPSG:3857';
+    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.tilegrid.createXYZ({
+      extent: ol.tilegrid.extentFromProjection(projection),
+      maxZoom: options.maxZoom,
+      minZoom: options.minZoom,
+      tileSize: options.tileSize
+    });
 
   ol.source.TileImage.call(this, {
     attributions: options.attributions,
@@ -73619,7 +76045,8 @@ ol.source.XYZ = function(opt_options) {
     tileUrlFunction: options.tileUrlFunction,
     url: options.url,
     urls: options.urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
+    wrapX: options.wrapX !== undefined ? options.wrapX : true,
+    transition: options.transition
   });
 
 };
@@ -73635,7 +76062,7 @@ goog.require('ol.source.XYZ');
 
 /**
  * @classdesc
- * Layer source for the CartoDB tiles.
+ * Layer source for the CartoDB Maps API.
  *
  * @constructor
  * @extends {ol.source.XYZ}
@@ -73729,7 +76156,7 @@ ol.source.CartoDB.prototype.initializeMap_ = function() {
     this.applyTemplate_(this.templateCache_[paramHash]);
     return;
   }
-  var mapUrl = 'https://' + this.account_ + '.cartodb.com/api/v1/map';
+  var mapUrl = 'https://' + this.account_ + '.carto.com/api/v1/map';
 
   if (this.mapId_) {
     mapUrl += '/named/' + this.mapId_;
@@ -73991,6 +76418,236 @@ ol.source.Cluster.prototype.createCluster = function(features) {
   return cluster;
 };
 
+goog.provide('ol.source.Image');
+
+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');
+
+
+/**
+ * @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.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;
+
+
+  /**
+   * @private
+   * @type {ol.reproj.Image}
+   */
+  this.reprojectedImage_ = null;
+
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.reprojectedRevision_ = 0;
+};
+ol.inherits(ol.source.Image, ol.source.Source);
+
+
+/**
+ * @return {Array.<number>} Resolutions.
+ * @override
+ */
+ol.source.Image.prototype.getResolutions = function() {
+  return this.resolutions_;
+};
+
+
+/**
+ * @protected
+ * @param {number} resolution Resolution.
+ * @return {number} Resolution.
+ */
+ol.source.Image.prototype.findNearestResolution = function(resolution) {
+  if (this.resolutions_) {
+    var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0);
+    resolution = this.resolutions_[idx];
+  }
+  return resolution;
+};
+
+
+/**
+ * @param {ol.Extent} extent Extent.
+ * @param {number} resolution Resolution.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {ol.ImageBase} Single image.
+ */
+ol.source.Image.prototype.getImage = function(extent, resolution, pixelRatio, projection) {
+  var sourceProjection = this.getProjection();
+  if (!ol.ENABLE_RASTER_REPROJECTION ||
+      !sourceProjection ||
+      !projection ||
+      ol.proj.equivalent(sourceProjection, projection)) {
+    if (sourceProjection) {
+      projection = sourceProjection;
+    }
+    return this.getImageInternal(extent, resolution, pixelRatio, projection);
+  } else {
+    if (this.reprojectedImage_) {
+      if (this.reprojectedRevision_ == this.getRevision() &&
+          ol.proj.equivalent(
+              this.reprojectedImage_.getProjection(), projection) &&
+          this.reprojectedImage_.getResolution() == resolution &&
+          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();
+
+    return this.reprojectedImage_;
+  }
+};
+
+
+/**
+ * @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.source.Image.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {};
+
+
+/**
+ * Handle image change events.
+ * @param {ol.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.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
+  }
+};
+
+
+/**
+ * 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 {ol.events.Event}
+ * @implements {oli.source.ImageEvent}
+ * @param {string} type Type.
+ * @param {ol.Image} image The image.
+ */
+ol.source.Image.Event = function(type, image) {
+
+  ol.events.Event.call(this, type);
+
+  /**
+   * The image related to the event.
+   * @type {ol.Image}
+   * @api
+   */
+  this.image = image;
+
+};
+ol.inherits(ol.source.Image.Event, ol.events.Event);
+
+
+/**
+ * @enum {string}
+ * @private
+ */
+ol.source.Image.EventType_ = {
+
+  /**
+   * Triggered when an image starts loading.
+   * @event ol.source.Image.Event#imageloadstart
+   * @api
+   */
+  IMAGELOADSTART: 'imageloadstart',
+
+  /**
+   * Triggered when an image finishes loading.
+   * @event ol.source.Image.Event#imageloadend
+   * @api
+   */
+  IMAGELOADEND: 'imageloadend',
+
+  /**
+   * Triggered if image loading results in an error.
+   * @event ol.source.Image.Event#imageloaderror
+   * @api
+   */
+  IMAGELOADERROR: 'imageloaderror'
+
+};
+
 goog.provide('ol.uri');
 
 
@@ -74081,7 +76738,7 @@ ol.source.ImageArcGISRest = function(opt_options) {
    * @type {ol.ImageLoadFunctionType}
    */
   this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+    options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
 
 
   /**
@@ -74189,7 +76846,7 @@ ol.source.ImageArcGISRest.prototype.getImageInternal = function(extent, resoluti
       projection, params);
 
   this.image_ = new ol.Image(extent, resolution, pixelRatio,
-      this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);
+      url, this.crossOrigin_, this.imageLoadFunction_);
 
   this.renderedRevision_ = this.getRevision();
 
@@ -74233,8 +76890,8 @@ ol.source.ImageArcGISRest.prototype.getRequestUrl_ = function(extent, size, pixe
   var url = this.url_;
 
   var modifiedUrl = url
-   .replace(/MapServer\/?$/, 'MapServer/export')
-   .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
+      .replace(/MapServer\/?$/, 'MapServer/export')
+      .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
   if (modifiedUrl == url) {
     ol.asserts.assert(false, 50); // `options.featureTypes` should be an Array
   }
@@ -74289,6 +76946,94 @@ ol.source.ImageArcGISRest.prototype.updateParams = function(params) {
   this.changed();
 };
 
+goog.provide('ol.source.ImageCanvas');
+
+goog.require('ol');
+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 Constructor options.
+ * @api
+ */
+ol.source.ImageCanvas = function(options) {
+
+  ol.source.Image.call(this, {
+    attributions: options.attributions,
+    logo: options.logo,
+    projection: options.projection,
+    resolutions: options.resolutions,
+    state: options.state
+  });
+
+  /**
+   * @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;
+
+};
+ol.inherits(ol.source.ImageCanvas, ol.source.Image);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageCanvas.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
+  resolution = this.findNearestResolution(resolution);
+
+  var canvas = this.canvas_;
+  if (canvas &&
+      this.renderedRevision_ == this.getRevision() &&
+      canvas.getResolution() == resolution &&
+      canvas.getPixelRatio() == pixelRatio &&
+      ol.extent.containsExtent(canvas.getExtent(), extent)) {
+    return canvas;
+  }
+
+  extent = extent.slice();
+  ol.extent.scaleFromCenter(extent, this.ratio_);
+  var width = ol.extent.getWidth(extent) / resolution;
+  var height = ol.extent.getHeight(extent) / resolution;
+  var size = [width * pixelRatio, height * pixelRatio];
+
+  var canvasElement = this.canvasFunction_(
+      extent, resolution, pixelRatio, size, projection);
+  if (canvasElement) {
+    canvas = new ol.ImageCanvas(extent, resolution, pixelRatio, canvasElement);
+  }
+  this.canvas_ = canvas;
+  this.renderedRevision_ = this.getRevision();
+
+  return canvas;
+};
+
 goog.provide('ol.source.ImageMapGuide');
 
 goog.require('ol');
@@ -74330,7 +77075,7 @@ ol.source.ImageMapGuide = function(options) {
    * @type {number}
    */
   this.displayDpi_ = options.displayDpi !== undefined ?
-      options.displayDpi : 96;
+    options.displayDpi : 96;
 
   /**
    * @private
@@ -74349,7 +77094,7 @@ ol.source.ImageMapGuide = function(options) {
    * @type {ol.ImageLoadFunctionType}
    */
   this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+    options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
 
   /**
    * @private
@@ -74362,7 +77107,7 @@ ol.source.ImageMapGuide = function(options) {
    * @type {number}
    */
   this.metersPerUnit_ = options.metersPerUnit !== undefined ?
-      options.metersPerUnit : 1;
+    options.metersPerUnit : 1;
 
   /**
    * @private
@@ -74375,7 +77120,7 @@ ol.source.ImageMapGuide = function(options) {
    * @type {boolean}
    */
   this.useOverlay_ = options.useOverlay !== undefined ?
-      options.useOverlay : false;
+    options.useOverlay : false;
 
   /**
    * @private
@@ -74432,7 +77177,7 @@ ol.source.ImageMapGuide.prototype.getImageInternal = function(extent, resolution
     var imageUrl = this.getUrl(this.url_, this.params_, extent, size,
         projection);
     image = new ol.Image(extent, resolution, pixelRatio,
-        this.getAttributions(), imageUrl, this.crossOrigin_,
+        imageUrl, this.crossOrigin_,
         this.imageLoadFunction_);
     ol.events.listen(image, ol.events.EventType.CHANGE,
         this.handleImageChange, this);
@@ -74556,11 +77301,11 @@ ol.source.ImageStatic = function(options) {
   var imageExtent = options.imageExtent;
 
   var crossOrigin = options.crossOrigin !== undefined ?
-      options.crossOrigin : null;
+    options.crossOrigin : null;
 
   var /** @type {ol.ImageLoadFunctionType} */ imageLoadFunction =
       options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+        options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
 
   ol.source.Image.call(this, {
     attributions: options.attributions,
@@ -74572,8 +77317,7 @@ ol.source.ImageStatic = function(options) {
    * @private
    * @type {ol.Image}
    */
-  this.image_ = new ol.Image(imageExtent, undefined, 1, this.getAttributions(),
-      options.url, crossOrigin, imageLoadFunction);
+  this.image_ = new ol.Image(imageExtent, undefined, 1, options.url, crossOrigin, imageLoadFunction);
 
   /**
    * @private
@@ -74627,6 +77371,323 @@ ol.source.ImageStatic.prototype.handleImageChange = function(evt) {
   ol.source.Image.prototype.handleImageChange.call(this, evt);
 };
 
+goog.provide('ol.source.ImageVector');
+
+goog.require('ol');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
+goog.require('ol.ext.rbush');
+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');
+
+
+/**
+ * @deprecated
+ * @classdesc
+ * **Deprecated**. Use an `ol.layer.Vector` with `renderMode: 'image'` and an
+ * `ol.source.Vector` instead.
+ *
+ * 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 {ol.Transform}
+   */
+  this.transform_ = ol.transform.create();
+
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.canvasContext_ = ol.dom.createCanvasContext2D();
+
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.canvasSize_ = [0, 0];
+
+  /**
+   * Declutter tree.
+   * @private
+   */
+  this.declutterTree_ = ol.ext.rbush(9);
+
+  /**
+   * @private
+   * @type {number}
+   */
+  this.renderBuffer_ = options.renderBuffer == undefined ? 100 : options.renderBuffer;
+
+  /**
+   * @private
+   * @type {ol.render.canvas.ReplayGroup}
+   */
+  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()
+  });
+
+  /**
+   * 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;
+
+  this.setStyle(options.style);
+
+  ol.events.listen(this.source_, ol.events.EventType.CHANGE,
+      this.handleSourceChange_, this);
+
+};
+ol.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, pixelRatio, this.source_.getOverlaps(), this.declutterTree_, this.renderBuffer_);
+
+  this.source_.loadFeatures(extent, resolution, projection);
+
+  var loading = false;
+  this.source_.forEachFeatureInExtent(extent,
+      /**
+       * @param {ol.Feature} feature Feature.
+       */
+      function(feature) {
+        loading = loading ||
+            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
+      }, this);
+  replayGroup.finish();
+
+  if (loading) {
+    return null;
+  }
+
+  if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) {
+    this.canvasContext_.canvas.width = size[0];
+    this.canvasContext_.canvas.height = size[1];
+    this.canvasSize_[0] = size[0];
+    this.canvasSize_[1] = size[1];
+  } else {
+    this.canvasContext_.clearRect(0, 0, size[0], size[1]);
+  }
+
+  this.declutterTree_.clear();
+
+  var transform = this.getTransform_(ol.extent.getCenter(extent),
+      resolution, pixelRatio, size);
+  replayGroup.replay(this.canvasContext_, transform, 0, {});
+
+  this.replayGroup_ = replayGroup;
+
+  return this.canvasContext_.canvas;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
+    coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, callback) {
+  if (!this.replayGroup_) {
+    return undefined;
+  } else {
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+    var result = 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);
+          }
+        }, null);
+    return result;
+  }
+};
+
+
+/**
+ * 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.StyleFunction}
+ *     Layer style.
+ * @api
+ */
+ol.source.ImageVector.prototype.getStyle = function() {
+  return this.style_;
+};
+
+
+/**
+ * Get the style function.
+ * @return {ol.StyleFunction|undefined} Layer style function.
+ * @api
+ */
+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 {!ol.Transform} Transform.
+ * @private
+ */
+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];
+
+  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2);
+};
+
+
+/**
+ * Handle changes in image style state.
+ * @param {ol.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;
+  if (!Array.isArray(styles)) {
+    styles = [styles];
+  }
+  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.StyleFunction|undefined}
+ *     style Layer style.
+ * @api
+ */
+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.source.WMSServerType');
 
 
@@ -74655,6 +77716,7 @@ goog.require('ol.events.EventType');
 goog.require('ol.extent');
 goog.require('ol.obj');
 goog.require('ol.proj');
+goog.require('ol.reproj');
 goog.require('ol.source.Image');
 goog.require('ol.source.WMSServerType');
 goog.require('ol.string');
@@ -74700,7 +77762,7 @@ ol.source.ImageWMS = function(opt_options) {
    * @type {ol.ImageLoadFunctionType}
    */
   this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
+    options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
 
   /**
    * @private
@@ -74719,8 +77781,7 @@ ol.source.ImageWMS = function(opt_options) {
    * @private
    * @type {ol.source.WMSServerType|undefined}
    */
-  this.serverType_ =
-      /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
+  this.serverType_ = /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
 
   /**
    * @private
@@ -74782,6 +77843,13 @@ ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolut
   if (this.url_ === undefined) {
     return undefined;
   }
+  var projectionObj = ol.proj.get(projection);
+  var sourceProjectionObj = this.getProjection();
+
+  if (sourceProjectionObj && sourceProjectionObj !== projectionObj) {
+    resolution = ol.reproj.calculateSourceResolution(sourceProjectionObj, projectionObj, coordinate, resolution);
+    coordinate = ol.proj.transform(coordinate, projectionObj, sourceProjectionObj);
+  }
 
   var extent = ol.extent.getForViewAndSize(
       coordinate, resolution, 0,
@@ -74804,7 +77872,7 @@ ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolut
 
   return this.getRequestUrl_(
       extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_,
-      1, ol.proj.get(projection), baseParams);
+      1, sourceProjectionObj || projectionObj, baseParams);
 };
 
 
@@ -74871,7 +77939,7 @@ ol.source.ImageWMS.prototype.getImageInternal = function(extent, resolution, pix
       projection, params);
 
   this.image_ = new ol.Image(requestExtent, resolution, pixelRatio,
-      this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);
+      url, this.crossOrigin_, this.imageLoadFunction_);
 
   this.renderedRevision_ = this.getRevision();
 
@@ -75012,7 +78080,6 @@ ol.source.ImageWMS.prototype.updateV13_ = function() {
 goog.provide('ol.source.OSM');
 
 goog.require('ol');
-goog.require('ol.Attribution');
 goog.require('ol.source.XYZ');
 
 
@@ -75037,10 +78104,10 @@ ol.source.OSM = function(opt_options) {
   }
 
   var crossOrigin = options.crossOrigin !== undefined ?
-      options.crossOrigin : 'anonymous';
+    options.crossOrigin : 'anonymous';
 
   var url = options.url !== undefined ?
-      options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';
+    options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';
 
   ol.source.XYZ.call(this, {
     attributions: attributions,
@@ -75062,14 +78129,12 @@ ol.inherits(ol.source.OSM, ol.source.XYZ);
  * The attribution containing a link to the OpenStreetMap Copyright and License
  * page.
  * @const
- * @type {ol.Attribution}
+ * @type {string}
  * @api
  */
-ol.source.OSM.ATTRIBUTION = new ol.Attribution({
-  html: '&copy; ' +
+ol.source.OSM.ATTRIBUTION = '&copy; ' +
       '<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> ' +
-      'contributors.'
-});
+      'contributors.';
 
 
 /**
@@ -75310,11 +78375,11 @@ Processor.prototype._resolveJob = function() {
 var processor = Processor;
 
 var Processor_1 = processor;
-var index = {
+var lib = {
 	Processor: Processor_1
 };
 
-exports['default'] = index;
+exports['default'] = lib;
 exports.Processor = Processor_1;
 
 }((this.pixelworks = this.pixelworks || {})));}).call(ol.ext);
@@ -75355,8 +78420,8 @@ goog.require('ol.transform');
 
 /**
  * @classdesc
- * A source that transforms data from any number of input sources using an array
- * of {@link ol.RasterOperation} functions to transform input pixel values into
+ * A source that transforms data from any number of input sources using an
+ * {@link ol.RasterOperation} function to transform input pixel values into
  * output pixel values.
  *
  * @constructor
@@ -75378,7 +78443,7 @@ ol.source.Raster = function(options) {
    * @type {ol.source.RasterOperationType}
    */
   this.operationType_ = options.operationType !== undefined ?
-      options.operationType : ol.source.RasterOperationType.PIXEL;
+    options.operationType : ol.source.RasterOperationType.PIXEL;
 
   /**
    * @private
@@ -75439,7 +78504,6 @@ ol.source.Raster = function(options) {
    */
   this.frameState_ = {
     animate: false,
-    attributions: {},
     coordinateToPixelTransform: ol.transform.create(),
     extent: null,
     focus: null,
@@ -75502,10 +78566,10 @@ ol.source.Raster.prototype.setOperation = function(operation, opt_lib) {
 ol.source.Raster.prototype.updateFrameState_ = function(extent, resolution, projection) {
 
   var frameState = /** @type {olx.FrameState} */ (
-      ol.obj.assign({}, this.frameState_));
+    ol.obj.assign({}, this.frameState_));
 
   frameState.viewState = /** @type {olx.ViewState} */ (
-      ol.obj.assign({}, frameState.viewState));
+    ol.obj.assign({}, frameState.viewState));
 
   var center = ol.extent.getCenter(extent);
 
@@ -75513,6 +78577,8 @@ ol.source.Raster.prototype.updateFrameState_ = function(extent, resolution, proj
   frameState.focus = center;
   frameState.size[0] = Math.round(ol.extent.getWidth(extent) / resolution);
   frameState.size[1] = Math.round(ol.extent.getHeight(extent) / resolution);
+  frameState.time = Date.now();
+  frameState.animate = false;
 
   var viewState = frameState.viewState;
   viewState.center = center;
@@ -75552,8 +78618,6 @@ ol.source.Raster.prototype.getImage = function(extent, resolution, pixelRatio, p
   var frameState = this.updateFrameState_(extent, resolution, projection);
   this.requestedFrameState_ = frameState;
 
-  frameState.tileQueue.loadMoreTiles(16, 16);
-
   // check if we can't reuse the existing ol.ImageCanvas
   if (this.renderedImageCanvas_) {
     var renderedResolution = this.renderedImageCanvas_.getResolution();
@@ -75567,6 +78631,12 @@ ol.source.Raster.prototype.getImage = function(extent, resolution, pixelRatio, p
     this.processSources_();
   }
 
+  frameState.tileQueue.loadMoreTiles(16, 16);
+
+  if (frameState.animate) {
+    requestAnimationFrame(this.changed.bind(this));
+  }
+
   return this.renderedImageCanvas_;
 };
 
@@ -75625,8 +78695,7 @@ ol.source.Raster.prototype.onWorkerComplete_ = function(frameState, err, output,
     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);
+    this.renderedImageCanvas_ = new ol.ImageCanvas(extent, resolution, 1, context.canvas);
   }
   context.putImageData(output, 0, 0);
 
@@ -75817,7 +78886,6 @@ ol.source.Raster.EventType_ = {
 goog.provide('ol.source.Stamen');
 
 goog.require('ol');
-goog.require('ol.Attribution');
 goog.require('ol.source.OSM');
 goog.require('ol.source.XYZ');
 
@@ -75839,7 +78907,7 @@ ol.source.Stamen = function(options) {
   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 +
+    'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' + options.layer +
       '/{z}/{x}/{y}.' + layerConfig.extension;
 
   ol.source.XYZ.call(this, {
@@ -75860,14 +78928,12 @@ ol.inherits(ol.source.Stamen, ol.source.XYZ);
 
 /**
  * @const
- * @type {Array.<ol.Attribution>}
+ * @type {Array.<string>}
  */
 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>.'
-  }),
+  'Map tiles by <a href="https://stamen.com/">Stamen Design</a>, ' +
+        'under <a href="https://creativecommons.org/licenses/by/3.0/">CC BY' +
+        ' 3.0</a>.',
   ol.source.OSM.ATTRIBUTION
 ];
 
@@ -75980,7 +79046,8 @@ ol.source.TileArcGISRest = function(opt_options) {
     tileLoadFunction: options.tileLoadFunction,
     url: options.url,
     urls: options.urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
+    wrapX: options.wrapX !== undefined ? options.wrapX : true,
+    transition: options.transition
   });
 
   /**
@@ -76036,7 +79103,7 @@ ol.source.TileArcGISRest.prototype.getParams = function() {
  * @private
  */
 ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
-        pixelRatio, projection, params) {
+    pixelRatio, projection, params) {
 
   var urls = this.urls;
   if (!urls) {
@@ -76052,7 +79119,7 @@ ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize
   params['IMAGESR'] = srid;
   params['DPI'] = Math.round(
       params['DPI'] ? params['DPI'] * pixelRatio : 90 * pixelRatio
-      );
+  );
 
   var url;
   if (urls.length == 1) {
@@ -76131,6 +79198,7 @@ goog.require('ol.TileState');
 goog.require('ol.dom');
 goog.require('ol.size');
 goog.require('ol.source.Tile');
+goog.require('ol.tilecoord');
 
 
 /**
@@ -76163,7 +79231,7 @@ 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);
+  var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
   if (this.tileCache.containsKey(tileCoordKey)) {
     return /** @type {!ol.source.TileDebug.Tile_} */ (this.tileCache.get(tileCoordKey));
   } else {
@@ -76171,7 +79239,7 @@ ol.source.TileDebug.prototype.getTile = function(z, x, y) {
     var tileCoord = [z, x, y];
     var textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord);
     var text = !textTileCoord ? '' :
-        this.getTileCoordForTileUrlFunction(textTileCoord).toString();
+      this.getTileCoordForTileUrlFunction(textTileCoord).toString();
     var tile = new ol.source.TileDebug.Tile_(tileCoord, tileSize, text);
     this.tileCache.set(tileCoordKey, tile);
     return tile;
@@ -76253,7 +79321,6 @@ ol.source.TileDebug.Tile_.prototype.load = function() {};
 goog.provide('ol.source.TileJSON');
 
 goog.require('ol');
-goog.require('ol.Attribution');
 goog.require('ol.TileUrlFunction');
 goog.require('ol.asserts');
 goog.require('ol.extent');
@@ -76289,7 +79356,8 @@ ol.source.TileJSON = function(options) {
     reprojectionErrorThreshold: options.reprojectionErrorThreshold,
     state: ol.source.State.LOADING,
     tileLoadFunction: options.tileLoadFunction,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
+    wrapX: options.wrapX !== undefined ? options.wrapX : true,
+    transition: options.transition
   });
 
   if (options.url) {
@@ -76381,23 +79449,17 @@ ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) {
   this.tileUrlFunction =
       ol.TileUrlFunction.createFromTemplates(tileJSON.tiles, tileGrid);
 
-  if (tileJSON.attribution !== undefined && !this.getAttributions()) {
+  if (tileJSON.attribution !== undefined && !this.getAttributions2()) {
     var attributionExtent = extent !== undefined ?
-        extent : epsg4326Projection.getExtent();
-    /** @type {Object.<string, Array.<ol.TileRange>>} */
-    var tileRanges = {};
-    var z, zKey;
-    for (z = minZoom; z <= maxZoom; ++z) {
-      zKey = z.toString();
-      tileRanges[zKey] =
-          [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
-    }
-    this.setAttributions([
-      new ol.Attribution({
-        html: tileJSON.attribution,
-        tileRanges: tileRanges
-      })
-    ]);
+      extent : epsg4326Projection.getExtent();
+
+    this.setAttributions(function(frameState) {
+      if (ol.extent.intersects(attributionExtent, frameState.extent)) {
+        return [tileJSON.attribution];
+      }
+      return null;
+    });
+
   }
   this.tileJSON_ = tileJSON;
   this.setState(ol.source.State.READY);
@@ -76415,7 +79477,6 @@ ol.source.TileJSON.prototype.handleTileJSONError = function() {
 goog.provide('ol.source.TileUTFGrid');
 
 goog.require('ol');
-goog.require('ol.Attribution');
 goog.require('ol.Tile');
 goog.require('ol.TileState');
 goog.require('ol.TileUrlFunction');
@@ -76427,6 +79488,7 @@ goog.require('ol.net');
 goog.require('ol.proj');
 goog.require('ol.source.State');
 goog.require('ol.source.Tile');
+goog.require('ol.tilecoord');
 goog.require('ol.tilegrid');
 
 
@@ -76450,7 +79512,7 @@ ol.source.TileUTFGrid = function(options) {
    * @type {boolean}
    */
   this.preemptive_ = options.preemptive !== undefined ?
-      options.preemptive : true;
+    options.preemptive : true;
 
   /**
    * @private
@@ -76611,21 +79673,14 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {
 
   if (tileJSON.attribution !== undefined) {
     var attributionExtent = extent !== undefined ?
-        extent : epsg4326Projection.getExtent();
-    /** @type {Object.<string, Array.<ol.TileRange>>} */
-    var tileRanges = {};
-    var z, zKey;
-    for (z = minZoom; z <= maxZoom; ++z) {
-      zKey = z.toString();
-      tileRanges[zKey] =
-          [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
-    }
-    this.setAttributions([
-      new ol.Attribution({
-        html: tileJSON.attribution,
-        tileRanges: tileRanges
-      })
-    ]);
+      extent : epsg4326Projection.getExtent();
+
+    this.setAttributions(function(frameState) {
+      if (ol.extent.intersects(attributionExtent, frameState.extent)) {
+        return [tileJSON.attribution];
+      }
+      return null;
+    });
   }
 
   this.setState(ol.source.State.READY);
@@ -76637,7 +79692,7 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {
  * @inheritDoc
  */
 ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projection) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
+  var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
   if (this.tileCache.containsKey(tileCoordKey)) {
     return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
   } else {
@@ -76662,7 +79717,7 @@ ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projecti
  * @inheritDoc
  */
 ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
+  var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
   if (this.tileCache.containsKey(tileCoordKey)) {
     this.tileCache.get(tileCoordKey);
   }
@@ -76913,6 +79968,7 @@ goog.require('ol.extent');
 goog.require('ol.obj');
 goog.require('ol.math');
 goog.require('ol.proj');
+goog.require('ol.reproj');
 goog.require('ol.size');
 goog.require('ol.source.TileImage');
 goog.require('ol.source.WMSServerType');
@@ -76945,11 +80001,13 @@ ol.source.TileWMS = function(opt_options) {
     opaque: !transparent,
     projection: options.projection,
     reprojectionErrorThreshold: options.reprojectionErrorThreshold,
+    tileClass: options.tileClass,
     tileGrid: options.tileGrid,
     tileLoadFunction: options.tileLoadFunction,
     url: options.url,
     urls: options.urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
+    wrapX: options.wrapX !== undefined ? options.wrapX : true,
+    transition: options.transition
   });
 
   /**
@@ -76974,8 +80032,7 @@ ol.source.TileWMS = function(opt_options) {
    * @private
    * @type {ol.source.WMSServerType|undefined}
    */
-  this.serverType_ =
-      /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
+  this.serverType_ = /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
 
   /**
    * @private
@@ -76983,13 +80040,6 @@ ol.source.TileWMS = function(opt_options) {
    */
   this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.coordKeyPrefix_ = '';
-  this.resetCoordKeyPrefix_();
-
   /**
    * @private
    * @type {ol.Extent}
@@ -77019,14 +80069,14 @@ ol.inherits(ol.source.TileWMS, ol.source.TileImage);
  */
 ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) {
   var projectionObj = ol.proj.get(projection);
+  var sourceProjectionObj = this.getProjection();
 
   var tileGrid = this.getTileGrid();
   if (!tileGrid) {
     tileGrid = this.getTileGridForProjection(projectionObj);
   }
 
-  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(
-      coordinate, resolution);
+  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(coordinate, resolution);
 
   if (tileGrid.getResolutions().length <= tileCoord[0]) {
     return undefined;
@@ -77034,14 +80084,19 @@ ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resoluti
 
   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 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);
+    tileExtent = ol.extent.buffer(tileExtent, tileResolution * gutter, tileExtent);
+  }
+
+  if (sourceProjectionObj && sourceProjectionObj !== projectionObj) {
+    tileResolution = ol.reproj.calculateSourceResolution(sourceProjectionObj, projectionObj, coordinate, tileResolution);
+    tileExtent = ol.proj.transformExtent(tileExtent, projectionObj, sourceProjectionObj);
+    coordinate = ol.proj.transform(coordinate, projectionObj, sourceProjectionObj);
   }
 
   var baseParams = {
@@ -77061,7 +80116,7 @@ ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resoluti
   baseParams[this.v13_ ? 'J' : 'Y'] = y;
 
   return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
-      1, projectionObj, baseParams);
+      1, sourceProjectionObj || projectionObj, baseParams);
 };
 
 
@@ -77073,14 +80128,6 @@ ol.source.TileWMS.prototype.getGutterInternal = function() {
 };
 
 
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
-  return this.coordKeyPrefix_ + ol.source.TileImage.prototype.getKeyZXY.call(this, z, x, y);
-};
-
-
 /**
  * Get the user-provided params, i.e. those passed to the constructor through
  * the "params" option, and possibly updated using the updateParams method.
@@ -77103,7 +80150,7 @@ ol.source.TileWMS.prototype.getParams = function() {
  * @private
  */
 ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
-        pixelRatio, projection, params) {
+    pixelRatio, projection, params) {
 
   var urls = this.urls;
   if (!urls) {
@@ -77171,25 +80218,7 @@ ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileE
  */
 ol.source.TileWMS.prototype.getTilePixelRatio = function(pixelRatio) {
   return (!this.hidpi_ || this.serverType_ === undefined) ? 1 :
-      /** @type {number} */ (pixelRatio);
-};
-
-
-/**
- * @private
- */
-ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
-  var i = 0;
-  var res = [];
-
-  if (this.urls) {
-    var j, jj;
-    for (j = 0, jj = this.urls.length; j < jj; ++j) {
-      res[i++] = this.urls[j];
-    }
-  }
-
-  this.coordKeyPrefix_ = res.join('#');
+  /** @type {number} */ (pixelRatio);
 };
 
 
@@ -77254,15 +80283,6 @@ ol.source.TileWMS.prototype.fixedTileUrlFunction = function(tileCoord, pixelRati
       pixelRatio, projection, baseParams);
 };
 
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.setUrls = function(urls) {
-  ol.source.TileImage.prototype.setUrls.call(this, urls);
-  this.resetCoordKeyPrefix_();
-};
-
-
 /**
  * Update the user-provided params.
  * @param {Object} params Params.
@@ -77270,7 +80290,6 @@ ol.source.TileWMS.prototype.setUrls = function(urls) {
  */
 ol.source.TileWMS.prototype.updateParams = function(params) {
   ol.obj.assign(this.params_, params);
-  this.resetCoordKeyPrefix_();
   this.updateV13_();
   this.setKey(this.getKeyForParams_());
 };
@@ -77289,7 +80308,6 @@ 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');
@@ -77302,7 +80320,7 @@ goog.require('ol.featureloader');
  * @extends {ol.Tile}
  * @param {ol.TileCoord} tileCoord Tile coordinate.
  * @param {ol.TileState} state State.
- * @param {string} src Data source url.
+ * @param {number} sourceRevision Source revision.
  * @param {ol.format.Feature} format Feature format.
  * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
  * @param {ol.TileCoord} urlTileCoord Wrapped tile coordinate for source urls.
@@ -77317,18 +80335,19 @@ goog.require('ol.featureloader');
  *     instantiate for source tiles.
  * @param {function(this: ol.source.VectorTile, ol.events.Event)} handleTileChange
  *     Function to call when a source tile's state changes.
+ * @param {olx.TileOptions=} opt_options Tile options.
  */
-ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction,
-    urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles,
-    pixelRatio, projection, tileClass, handleTileChange) {
+ol.VectorImageTile = function(tileCoord, state, sourceRevision, format,
+    tileLoadFunction, urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid,
+    sourceTiles, pixelRatio, projection, tileClass, handleTileChange, opt_options) {
 
-  ol.Tile.call(this, tileCoord, state);
+  ol.Tile.call(this, tileCoord, state, opt_options);
 
   /**
    * @private
-   * @type {CanvasRenderingContext2D}
+   * @type {Object.<string, CanvasRenderingContext2D>}
    */
-  this.context_ = null;
+  this.context_ = {};
 
   /**
    * @private
@@ -77338,14 +80357,9 @@ ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction,
 
   /**
    * @private
-   * @type {ol.TileReplayState}
+   * @type {Object.<string, ol.TileReplayState>}
    */
-  this.replayState_ = {
-    dirty: false,
-    renderedRenderOrder: null,
-    renderedRevision: -1,
-    renderedTileRevision: -1
-  };
+  this.replayState_ = {};
 
   /**
    * @private
@@ -77360,9 +80374,9 @@ ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction,
   this.tileKeys = [];
 
   /**
-   * @type {string}
+   * @type {number}
    */
-  this.src_ = src;
+  this.sourceRevision_ = sourceRevision;
 
   /**
    * @type {ol.TileCoord}
@@ -77386,6 +80400,10 @@ ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction,
     sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) {
       var sharedExtent = ol.extent.getIntersection(extent,
           sourceTileGrid.getTileCoordExtent(sourceTileCoord));
+      var sourceExtent = sourceTileGrid.getExtent();
+      if (sourceExtent) {
+        sharedExtent = ol.extent.getIntersection(sharedExtent, sourceExtent);
+      }
       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
@@ -77425,10 +80443,8 @@ ol.VectorImageTile.prototype.disposeInternal = function() {
   }
   this.tileKeys.length = 0;
   this.sourceTiles_ = null;
-  if (this.state == ol.TileState.LOADING) {
-    this.loadListenerKeys_.forEach(ol.events.unlistenByKey);
-    this.loadListenerKeys_.length = 0;
-  }
+  this.loadListenerKeys_.forEach(ol.events.unlistenByKey);
+  this.loadListenerKeys_.length = 0;
   if (this.interimTile) {
     this.interimTile.dispose();
   }
@@ -77441,31 +80457,44 @@ ol.VectorImageTile.prototype.disposeInternal = function() {
 
 
 /**
+ * @param {ol.layer.Layer} layer Layer.
  * @return {CanvasRenderingContext2D} The rendering context.
  */
-ol.VectorImageTile.prototype.getContext = function() {
-  if (!this.context_) {
-    this.context_ = ol.dom.createCanvasContext2D();
+ol.VectorImageTile.prototype.getContext = function(layer) {
+  var key = ol.getUid(layer).toString();
+  if (!(key in this.context_)) {
+    this.context_[key] = ol.dom.createCanvasContext2D();
   }
-  return this.context_;
+  return this.context_[key];
 };
 
 
 /**
  * Get the Canvas for this tile.
+ * @param {ol.layer.Layer} layer Layer.
  * @return {HTMLCanvasElement} Canvas.
  */
-ol.VectorImageTile.prototype.getImage = function() {
-  return this.replayState_.renderedTileRevision == -1 ?
-      null : this.context_.canvas;
+ol.VectorImageTile.prototype.getImage = function(layer) {
+  return this.getReplayState(layer).renderedTileRevision == -1 ?
+    null : this.getContext(layer).canvas;
 };
 
 
 /**
+ * @param {ol.layer.Layer} layer Layer.
  * @return {ol.TileReplayState} The replay state.
  */
-ol.VectorImageTile.prototype.getReplayState = function() {
-  return this.replayState_;
+ol.VectorImageTile.prototype.getReplayState = function(layer) {
+  var key = ol.getUid(layer).toString();
+  if (!(key in this.replayState_)) {
+    this.replayState_[key] = {
+      dirty: false,
+      renderedRenderOrder: null,
+      renderedRevision: -1,
+      renderedTileRevision: -1
+    };
+  }
+  return this.replayState_[key];
 };
 
 
@@ -77473,7 +80502,7 @@ ol.VectorImageTile.prototype.getReplayState = function() {
  * @inheritDoc
  */
 ol.VectorImageTile.prototype.getKey = function() {
-  return this.tileKeys.join('/') + '/' + this.src_;
+  return this.tileKeys.join('/') + '-' + this.sourceRevision_;
 };
 
 
@@ -77490,8 +80519,13 @@ ol.VectorImageTile.prototype.getTile = function(tileKey) {
  * @inheritDoc
  */
 ol.VectorImageTile.prototype.load = function() {
+  // Source tiles with LOADED state - we just count them because once they are
+  // loaded, we're no longer listening to state changes.
   var leftToLoad = 0;
-  var errors = false;
+  // Source tiles with ERROR state - we track them because they can still have
+  // an ERROR state after another load attempt.
+  var errorSourceTiles = {};
+
   if (this.state == ol.TileState.IDLE) {
     this.setState(ol.TileState.LOADING);
   }
@@ -77501,26 +80535,21 @@ ol.VectorImageTile.prototype.load = function() {
       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);
+            var uid = ol.getUid(sourceTile);
             if (state == ol.TileState.ERROR) {
-              ol.array.remove(this.tileKeys, sourceTileKey);
-              errors = true;
+              errorSourceTiles[uid] = true;
+            } else {
+              --leftToLoad;
+              delete errorSourceTiles[uid];
             }
-            if (leftToLoad == 0) {
-              this.setState(this.tileKeys.length > 0 ?
-                  ol.TileState.LOADED : ol.TileState.ERROR);
+            if (leftToLoad - Object.keys(errorSourceTiles).length == 0) {
+              this.finishLoading_();
             }
           }
         }.bind(this));
@@ -77529,12 +80558,33 @@ ol.VectorImageTile.prototype.load = function() {
       }
     }.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);
+  if (leftToLoad - Object.keys(errorSourceTiles).length == 0) {
+    setTimeout(this.finishLoading_.bind(this), 0);
+  }
+};
+
+
+/**
+ * @private
+ */
+ol.VectorImageTile.prototype.finishLoading_ = function() {
+  var loaded = this.tileKeys.length;
+  var empty = 0;
+  for (var i = loaded - 1; i >= 0; --i) {
+    var state = this.getTile(this.tileKeys[i]).getState();
+    if (state != ol.TileState.LOADED) {
+      --loaded;
+    }
+    if (state == ol.TileState.EMPTY) {
+      ++empty;
+    }
+  }
+  if (loaded == this.tileKeys.length) {
+    this.loadListenerKeys_.forEach(ol.events.unlistenByKey);
+    this.loadListenerKeys_.length = 0;
+    this.setState(ol.TileState.LOADED);
+  } else {
+    this.setState(empty == this.tileKeys.length ? ol.TileState.EMPTY : ol.TileState.ERROR);
   }
 };
 
@@ -77546,7 +80596,7 @@ ol.VectorImageTile.prototype.load = function() {
  */
 ol.VectorImageTile.defaultLoadFunction = function(tile, url) {
   var loader = ol.featureloader.loadFeaturesXhr(
-      url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile));
+      url, tile.getFormat(), tile.onLoad.bind(tile), tile.onError.bind(tile));
 
   tile.setLoader(loader);
 };
@@ -77566,16 +80616,23 @@ goog.require('ol.TileState');
  * @param {string} src Data source url.
  * @param {ol.format.Feature} format Feature format.
  * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
+ * @param {olx.TileOptions=} opt_options Tile options.
  */
-ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
+ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction, opt_options) {
 
-  ol.Tile.call(this, tileCoord, state);
+  ol.Tile.call(this, tileCoord, state, opt_options);
 
   /**
    * @type {number}
    */
   this.consumers = 0;
 
+  /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.extent_ = null;
+
   /**
    * @private
    * @type {ol.format.Feature}
@@ -77601,6 +80658,10 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
    */
   this.projection_;
 
+  /**
+   * @private
+   * @type {Object.<string, ol.render.ReplayGroup>}
+   */
   this.replayGroups_ = {};
 
   /**
@@ -77631,6 +80692,16 @@ ol.VectorTile.prototype.disposeInternal = function() {
 };
 
 
+/**
+ * Gets the extent of the vector tile.
+ * @return {ol.Extent} The extent.
+ * @api
+ */
+ol.VectorTile.prototype.getExtent = function() {
+  return this.extent_ || ol.VectorTile.DEFAULT_EXTENT;
+};
+
+
 /**
  * Get the feature format assigned for reading this tile's features.
  * @return {ol.format.Feature} Feature format.
@@ -77643,7 +80714,7 @@ ol.VectorTile.prototype.getFormat = function() {
 
 /**
  * Get the features for this tile. Geometries will be in the projection returned
- * by {@link #getProjection}.
+ * by {@link ol.VectorTile#getProjection}.
  * @return {Array.<ol.Feature|ol.render.Feature>} Features.
  * @api
  */
@@ -77661,7 +80732,8 @@ ol.VectorTile.prototype.getKey = function() {
 
 
 /**
- * Get the feature projection of features returned by {@link #getFeatures}.
+ * Get the feature projection of features returned by
+ * {@link ol.VectorTile#getFeatures}.
  * @return {ol.proj.Projection} Feature projection.
  * @api
  */
@@ -77670,8 +80742,13 @@ ol.VectorTile.prototype.getProjection = function() {
 };
 
 
-ol.VectorTile.prototype.getReplayGroup = function(key) {
-  return this.replayGroups_[key];
+/**
+ * @param {ol.layer.Layer} layer Layer.
+ * @param {string} key Key.
+ * @return {ol.render.ReplayGroup} Replay group.
+ */
+ol.VectorTile.prototype.getReplayGroup = function(layer, key) {
+  return this.replayGroups_[ol.getUid(layer) + ',' + key];
 };
 
 
@@ -77691,22 +80768,43 @@ ol.VectorTile.prototype.load = function() {
  * Handler for successful tile load.
  * @param {Array.<ol.Feature>} features The loaded features.
  * @param {ol.proj.Projection} dataProjection Data projection.
+ * @param {ol.Extent} extent Extent.
  */
-ol.VectorTile.prototype.onLoad_ = function(features, dataProjection) {
+ol.VectorTile.prototype.onLoad = function(features, dataProjection, extent) {
   this.setProjection(dataProjection);
   this.setFeatures(features);
+  this.setExtent(extent);
 };
 
 
 /**
  * Handler for tile load errors.
  */
-ol.VectorTile.prototype.onError_ = function() {
+ol.VectorTile.prototype.onError = function() {
   this.setState(ol.TileState.ERROR);
 };
 
 
 /**
+ * Function for use in an {@link ol.source.VectorTile}'s `tileLoadFunction`.
+ * Sets the extent of the vector tile. This is only required for tiles in
+ * projections with `tile-pixels` as units. The extent should be set to
+ * `[0, 0, tilePixelSize, tilePixelSize]`, where `tilePixelSize` is calculated
+ * by multiplying the tile size with the tile pixel ratio. For sources using
+ * {@link ol.format.MVT} as feature format, the
+ * {@link ol.format.MVT#getLastExtent} method will return the correct extent.
+ * The default is `[0, 0, 4096, 4096]`.
+ * @param {ol.Extent} extent The extent.
+ * @api
+ */
+ol.VectorTile.prototype.setExtent = function(extent) {
+  this.extent_ = extent;
+};
+
+
+/**
+ * Function for use in an {@link ol.source.VectorTile}'s `tileLoadFunction`.
+ * Sets the features for the tile.
  * @param {Array.<ol.Feature>} features Features.
  * @api
  */
@@ -77717,7 +80815,9 @@ ol.VectorTile.prototype.setFeatures = function(features) {
 
 
 /**
- * Set the projection of the features that were added with {@link #setFeatures}.
+ * Function for use in an {@link ol.source.VectorTile}'s `tileLoadFunction`.
+ * Sets the projection of the features that were added with
+ * {@link ol.VectorTile#setFeatures}.
  * @param {ol.proj.Projection} projection Feature projection.
  * @api
  */
@@ -77726,8 +80826,13 @@ ol.VectorTile.prototype.setProjection = function(projection) {
 };
 
 
-ol.VectorTile.prototype.setReplayGroup = function(key, replayGroup) {
-  this.replayGroups_[key] = replayGroup;
+/**
+ * @param {ol.layer.Layer} layer Layer.
+ * @param {string} key Key.
+ * @param {ol.render.ReplayGroup} replayGroup Replay group.
+ */
+ol.VectorTile.prototype.setReplayGroup = function(layer, key, replayGroup) {
+  this.replayGroups_[ol.getUid(layer) + ',' + key] = replayGroup;
 };
 
 
@@ -77740,16 +80845,23 @@ ol.VectorTile.prototype.setLoader = function(loader) {
   this.loader_ = loader;
 };
 
+
+/**
+ * @const
+ * @type {ol.Extent}
+ */
+ol.VectorTile.DEFAULT_EXTENT = [0, 0, 4096, 4096];
+
 goog.provide('ol.source.VectorTile');
 
 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');
+goog.require('ol.tilecoord');
+goog.require('ol.tilegrid');
 
 
 /**
@@ -77769,23 +80881,33 @@ goog.require('ol.source.UrlTile');
  * @api
  */
 ol.source.VectorTile = function(options) {
+  var projection = options.projection || 'EPSG:3857';
+
+  var extent = options.extent || ol.tilegrid.extentFromProjection(projection);
+
+  var tileGrid = options.tileGrid || ol.tilegrid.createXYZ({
+    extent: extent,
+    maxZoom: options.maxZoom || 22,
+    minZoom: options.minZoom,
+    tileSize: options.tileSize || 512
+  });
 
   ol.source.UrlTile.call(this, {
     attributions: options.attributions,
     cacheSize: options.cacheSize !== undefined ? options.cacheSize : 128,
-    extent: options.extent,
+    extent: extent,
     logo: options.logo,
     opaque: false,
-    projection: options.projection,
+    projection: projection,
     state: options.state,
-    tileGrid: options.tileGrid,
+    tileGrid: tileGrid,
     tileLoadFunction: options.tileLoadFunction ?
-        options.tileLoadFunction : ol.VectorImageTile.defaultLoadFunction,
+      options.tileLoadFunction : ol.VectorImageTile.defaultLoadFunction,
     tileUrlFunction: options.tileUrlFunction,
-    tilePixelRatio: options.tilePixelRatio,
     url: options.url,
     urls: options.urls,
-    wrapX: options.wrapX === undefined ? true : options.wrapX
+    wrapX: options.wrapX === undefined ? true : options.wrapX,
+    transition: options.transition
   });
 
   /**
@@ -77819,10 +80941,6 @@ ol.source.VectorTile = function(options) {
    */
   this.tileGrids_ = {};
 
-  if (!this.tileGrid) {
-    this.tileGrid = this.getTileGridForProjection(ol.proj.get(options.projection || 'EPSG:3857'));
-  }
-
 };
 ol.inherits(ol.source.VectorTile, ol.source.UrlTile);
 
@@ -77834,28 +80952,35 @@ ol.source.VectorTile.prototype.getOverlaps = function() {
   return this.overlaps_;
 };
 
+/**
+ * clear {@link ol.TileCache} and delete all source tiles
+ * @api
+ */
+ol.source.VectorTile.prototype.clear = function() {
+  this.tileCache.clear();
+  this.sourceTiles_ = {};
+};
 
 /**
  * @inheritDoc
  */
 ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projection) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
+  var tileCoordKey = ol.tilecoord.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 : '',
+        urlTileCoord !== null ? ol.TileState.IDLE : ol.TileState.EMPTY,
+        this.getRevision(),
         this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction,
         this.tileGrid, this.getTileGridForProjection(projection),
         this.sourceTiles_, pixelRatio, projection, this.tileClass,
-        this.handleTileChange.bind(this));
+        this.handleTileChange.bind(this),
+        this.tileOptions);
 
     this.tileCache.set(tileCoordKey, tile);
     return tile;
@@ -77883,10 +81008,8 @@ ol.source.VectorTile.prototype.getTileGridForProjection = function(projection) {
 /**
  * @inheritDoc
  */
-ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
-  return opt_pixelRatio == undefined ?
-      ol.source.UrlTile.prototype.getTilePixelRatio.call(this, opt_pixelRatio) :
-      opt_pixelRatio;
+ol.source.VectorTile.prototype.getTilePixelRatio = function(pixelRatio) {
+  return pixelRatio;
 };
 
 
@@ -77980,7 +81103,7 @@ ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
  * @api
  */
 ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_extent,
- opt_matrixLimits) {
+    opt_matrixLimits) {
 
   /** @type {!Array.<number>} */
   var resolutions = [];
@@ -78003,9 +81126,9 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_exten
   var tileWidthPropName = 'TileWidth';
   var tileHeightPropName = 'TileHeight';
 
-  var projection;
-  projection = ol.proj.get(matrixSet[supportedCRSPropName].replace(
-      /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'));
+  var code = matrixSet[supportedCRSPropName];
+  var projection = ol.proj.get(code.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')) ||
+      ol.proj.get(code);
   var metersPerUnit = projection.getMetersPerUnit();
   // swap origin x and y coordinates if axis orientation is lat/long
   var switchOriginXY = projection.getAxisOrientation().substr(0, 2) == 'ne';
@@ -78041,7 +81164,7 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_exten
       }
       resolutions.push(resolution);
       tileSizes.push(tileWidth == tileHeight ?
-          tileWidth : [tileWidth, tileHeight]);
+        tileWidth : [tileWidth, tileHeight]);
       // top-left origin, so height is negative
       sizes.push([elt['MatrixWidth'], -elt['MatrixHeight']]);
     }
@@ -78133,8 +81256,8 @@ ol.source.WMTS = function(options) {
    * @type {ol.source.WMTSRequestEncoding}
    */
   this.requestEncoding_ = options.requestEncoding !== undefined ?
-      /** @type {ol.source.WMTSRequestEncoding} */ (options.requestEncoding) :
-      ol.source.WMTSRequestEncoding.KVP;
+    /** @type {ol.source.WMTSRequestEncoding} */ (options.requestEncoding) :
+    ol.source.WMTSRequestEncoding.KVP;
 
   var requestEncoding = this.requestEncoding_;
 
@@ -78164,53 +81287,54 @@ ol.source.WMTS = function(options) {
   /**
    * @param {string} template Template.
    * @return {ol.TileUrlFunctionType} Tile URL function.
+   * @private
    */
-  function createFromWMTSTemplate(template) {
+  this.createFromWMTSTemplate_ = function(template) {
 
     // TODO: we may want to create our own appendParams function so that params
     // order conforms to wmts spec guidance, and so that we can avoid to escape
     // special template params
 
     template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ?
-        ol.uri.appendParams(template, context) :
-        template.replace(/\{(\w+?)\}/g, function(m, p) {
-          return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m;
-        });
+      ol.uri.appendParams(template, context) :
+      template.replace(/\{(\w+?)\}/g, function(m, p) {
+        return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m;
+      });
 
     return (
-        /**
-         * @param {ol.TileCoord} tileCoord Tile coordinate.
-         * @param {number} pixelRatio Pixel ratio.
-         * @param {ol.proj.Projection} projection Projection.
-         * @return {string|undefined} Tile URL.
-         */
-        function(tileCoord, pixelRatio, projection) {
-          if (!tileCoord) {
-            return undefined;
+      /**
+       * @param {ol.TileCoord} tileCoord Tile coordinate.
+       * @param {number} pixelRatio Pixel ratio.
+       * @param {ol.proj.Projection} projection Projection.
+       * @return {string|undefined} Tile URL.
+       */
+      function(tileCoord, pixelRatio, projection) {
+        if (!tileCoord) {
+          return undefined;
+        } else {
+          var localContext = {
+            'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
+            'TileCol': tileCoord[1],
+            'TileRow': -tileCoord[2] - 1
+          };
+          ol.obj.assign(localContext, dimensions);
+          var url = template;
+          if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
+            url = ol.uri.appendParams(url, localContext);
           } else {
-            var localContext = {
-              'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
-              'TileCol': tileCoord[1],
-              'TileRow': -tileCoord[2] - 1
-            };
-            ol.obj.assign(localContext, dimensions);
-            var url = template;
-            if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
-              url = ol.uri.appendParams(url, localContext);
-            } else {
-              url = url.replace(/\{(\w+?)\}/g, function(m, p) {
-                return localContext[p];
-              });
-            }
-            return url;
+            url = url.replace(/\{(\w+?)\}/g, function(m, p) {
+              return localContext[p];
+            });
           }
-        });
-  }
+          return url;
+        }
+      });
+  };
 
   var tileUrlFunction = (urls && urls.length > 0) ?
-      ol.TileUrlFunction.createFromTileUrlFunctions(
-          urls.map(createFromWMTSTemplate)) :
-      ol.TileUrlFunction.nullTileUrlFunction;
+    ol.TileUrlFunction.createFromTileUrlFunctions(
+        urls.map(this.createFromWMTSTemplate_)) :
+    ol.TileUrlFunction.nullTileUrlFunction;
 
   ol.source.TileImage.call(this, {
     attributions: options.attributions,
@@ -78225,7 +81349,8 @@ ol.source.WMTS = function(options) {
     tilePixelRatio: options.tilePixelRatio,
     tileUrlFunction: tileUrlFunction,
     urls: urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : false
+    wrapX: options.wrapX !== undefined ? options.wrapX : false,
+    transition: options.transition
   });
 
   this.setKey(this.getKeyForDimensions_());
@@ -78233,6 +81358,18 @@ ol.source.WMTS = function(options) {
 };
 ol.inherits(ol.source.WMTS, ol.source.TileImage);
 
+/**
+ * Set the URLs to use for requests.
+ * URLs may contain OCG conform URL Template Variables: {TileMatrix}, {TileRow}, {TileCol}.
+ * @override
+ */
+ol.source.WMTS.prototype.setUrls = function(urls) {
+  this.urls = urls;
+  var key = urls.join('\n');
+  this.setTileUrlFunction(this.fixedTileUrlFunction ?
+    this.fixedTileUrlFunction.bind(this) :
+    ol.TileUrlFunction.createFromTileUrlFunctions(urls.map(this.createFromWMTSTemplate_.bind(this))), key);
+};
 
 /**
  * Get the dimensions, i.e. those passed to the constructor through the
@@ -78372,8 +81509,9 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
             var tileMatrixSet = ol.array.find(tileMatrixSets, function(el) {
               return el['Identifier'] == elt['TileMatrixSet'];
             });
-            var supportedCRS = tileMatrixSet['SupportedCRS'].replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3');
-            var proj1 = ol.proj.get(supportedCRS);
+            var supportedCRS = tileMatrixSet['SupportedCRS'];
+            var proj1 = ol.proj.get(supportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')) ||
+                ol.proj.get(supportedCRS);
             var proj2 = ol.proj.get(config['projection']);
             if (proj1 && proj2) {
               return ol.proj.equivalent(proj1, proj2);
@@ -78394,9 +81532,9 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
     idx = 0;
   }
   matrixSet = /** @type {string} */
-      (l['TileMatrixSetLink'][idx]['TileMatrixSet']);
+    (l['TileMatrixSetLink'][idx]['TileMatrixSet']);
   matrixLimits = /** @type {Array.<Object>} */
-      (l['TileMatrixSetLink'][idx]['TileMatrixSetLimits']);
+    (l['TileMatrixSetLink'][idx]['TileMatrixSetLimits']);
 
   var format = /** @type {string} */ (l['Format'][0]);
   if ('format' in config) {
@@ -78432,11 +81570,18 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
   });
 
   var projection;
+  var code = matrixSetObj['SupportedCRS'];
+  if (code) {
+    projection = ol.proj.get(code.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')) ||
+        ol.proj.get(code);
+  }
   if ('projection' in config) {
-    projection = ol.proj.get(config['projection']);
-  } else {
-    projection = ol.proj.get(matrixSetObj['SupportedCRS'].replace(
-        /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'));
+    var projConfig = ol.proj.get(config['projection']);
+    if (projConfig) {
+      if (!projection || ol.proj.equivalent(projConfig, projection)) {
+        projection = projConfig;
+      }
+    }
   }
 
   var wgs84BoundingBox = l['WGS84BoundingBox'];
@@ -78469,21 +81614,26 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
     var gets = wmtsCap['OperationsMetadata']['GetTile']['DCP']['HTTP']['Get'];
 
     for (var i = 0, ii = gets.length; i < ii; ++i) {
-      var constraint = ol.array.find(gets[i]['Constraint'], function(element) {
-        return element['name'] == 'GetEncoding';
-      });
-      var encodings = constraint['AllowedValues']['Value'];
+      if (gets[i]['Constraint']) {
+        var constraint = ol.array.find(gets[i]['Constraint'], function(element) {
+          return element['name'] == 'GetEncoding';
+        });
+        var encodings = constraint['AllowedValues']['Value'];
 
-      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']));
+        if (requestEncoding === '') {
+          // requestEncoding not provided, use the first encoding from the list
+          requestEncoding = encodings[0];
         }
-      } else {
-        break;
+        if (requestEncoding === ol.source.WMTSRequestEncoding.KVP) {
+          if (ol.array.includes(encodings, ol.source.WMTSRequestEncoding.KVP)) {
+            urls.push(/** @type {string} */ (gets[i]['href']));
+          }
+        } else {
+          break;
+        }
+      } else if (gets[i]['href']) {
+        requestEncoding = ol.source.WMTSRequestEncoding.KVP;
+        urls.push(/** @type {string} */ (gets[i]['href']));
       }
     }
   }
@@ -78521,13 +81671,15 @@ goog.require('ol.TileUrlFunction');
 goog.require('ol.asserts');
 goog.require('ol.dom');
 goog.require('ol.extent');
+goog.require('ol.size');
 goog.require('ol.source.TileImage');
 goog.require('ol.tilegrid.TileGrid');
 
 
 /**
  * @classdesc
- * Layer source for tile data in Zoomify format.
+ * Layer source for tile data in Zoomify format (both Zoomify and Internet
+ * Imaging Protocol are supported).
  *
  * @constructor
  * @extends {ol.source.TileImage}
@@ -78540,31 +81692,33 @@ ol.source.Zoomify = function(opt_options) {
 
   var size = options.size;
   var tierSizeCalculation = options.tierSizeCalculation !== undefined ?
-      options.tierSizeCalculation :
-      ol.source.Zoomify.TierSizeCalculation_.DEFAULT;
+    options.tierSizeCalculation :
+    ol.source.Zoomify.TierSizeCalculation_.DEFAULT;
 
   var imageWidth = size[0];
   var imageHeight = size[1];
+  var extent = options.extent || [0, -size[1], size[0], 0];
   var tierSizeInTiles = [];
-  var tileSize = ol.DEFAULT_TILE_SIZE;
+  var tileSize = options.tileSize || ol.DEFAULT_TILE_SIZE;
+  var tileSizeForTierSizeCalculation = tileSize;
 
   switch (tierSizeCalculation) {
     case ol.source.Zoomify.TierSizeCalculation_.DEFAULT:
-      while (imageWidth > tileSize || imageHeight > tileSize) {
+      while (imageWidth > tileSizeForTierSizeCalculation || imageHeight > tileSizeForTierSizeCalculation) {
         tierSizeInTiles.push([
-          Math.ceil(imageWidth / tileSize),
-          Math.ceil(imageHeight / tileSize)
+          Math.ceil(imageWidth / tileSizeForTierSizeCalculation),
+          Math.ceil(imageHeight / tileSizeForTierSizeCalculation)
         ]);
-        tileSize += tileSize;
+        tileSizeForTierSizeCalculation += tileSizeForTierSizeCalculation;
       }
       break;
     case ol.source.Zoomify.TierSizeCalculation_.TRUNCATED:
       var width = imageWidth;
       var height = imageHeight;
-      while (width > tileSize || height > tileSize) {
+      while (width > tileSizeForTierSizeCalculation || height > tileSizeForTierSizeCalculation) {
         tierSizeInTiles.push([
-          Math.ceil(width / tileSize),
-          Math.ceil(height / tileSize)
+          Math.ceil(width / tileSizeForTierSizeCalculation),
+          Math.ceil(height / tileSizeForTierSizeCalculation)
         ]);
         width >>= 1;
         height >>= 1;
@@ -78590,15 +81744,15 @@ ol.source.Zoomify = function(opt_options) {
   }
   resolutions.reverse();
 
-  var extent = [0, -size[1], size[0], 0];
   var tileGrid = new ol.tilegrid.TileGrid({
+    tileSize: tileSize,
     extent: extent,
     origin: ol.extent.getTopLeft(extent),
     resolutions: resolutions
   });
 
   var url = options.url;
-  if (url && url.indexOf('{TileGroup}') == -1) {
+  if (url && url.indexOf('{TileGroup}') == -1 && url.indexOf('{tileIndex}') == -1) {
     url += '{TileGroup}/{z}-{x}-{y}.jpg';
   }
   var urls = ol.TileUrlFunction.expandUrl(url);
@@ -78625,13 +81779,14 @@ ol.source.Zoomify = function(opt_options) {
           var tileCoordY = -tileCoord[2] - 1;
           var tileIndex =
               tileCoordX +
-              tileCoordY * tierSizeInTiles[tileCoordZ][0] +
-              tileCountUpToTier[tileCoordZ];
-          var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0;
+              tileCoordY * tierSizeInTiles[tileCoordZ][0];
+          var tileSize = tileGrid.getTileSize(tileCoordZ);
+          var tileGroup = ((tileIndex + tileCountUpToTier[tileCoordZ]) / tileSize) | 0;
           var localContext = {
             'z': tileCoordZ,
             'x': tileCoordX,
             'y': tileCoordY,
+            'tileIndex': tileIndex,
             'TileGroup': 'TileGroup' + tileGroup
           };
           return template.replace(/\{(\w+?)\}/g, function(m, p) {
@@ -78643,6 +81798,8 @@ ol.source.Zoomify = function(opt_options) {
 
   var tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions(urls.map(createFromTemplate));
 
+  var ZoomifyTileClass = ol.source.Zoomify.Tile_.bind(null, tileGrid);
+
   ol.source.TileImage.call(this, {
     attributions: options.attributions,
     cacheSize: options.cacheSize,
@@ -78650,29 +81807,31 @@ ol.source.Zoomify = function(opt_options) {
     logo: options.logo,
     projection: options.projection,
     reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileClass: ol.source.Zoomify.Tile_,
+    tileClass: ZoomifyTileClass,
     tileGrid: tileGrid,
-    tileUrlFunction: tileUrlFunction
+    tileUrlFunction: tileUrlFunction,
+    transition: options.transition
   });
 
 };
 ol.inherits(ol.source.Zoomify, ol.source.TileImage);
 
-
 /**
  * @constructor
  * @extends {ol.ImageTile}
+ * @param {ol.tilegrid.TileGrid} tileGrid TileGrid that the tile belongs to.
  * @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 {olx.TileOptions=} opt_options Tile options.
  * @private
  */
 ol.source.Zoomify.Tile_ = function(
-    tileCoord, state, src, crossOrigin, tileLoadFunction) {
+    tileGrid, tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options) {
 
-  ol.ImageTile.call(this, tileCoord, state, src, crossOrigin, tileLoadFunction);
+  ol.ImageTile.call(this, tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options);
 
   /**
    * @private
@@ -78680,6 +81839,11 @@ ol.source.Zoomify.Tile_ = function(
    */
   this.zoomifyImage_ = null;
 
+  /**
+   * @private
+   * @type {ol.Size}
+   */
+  this.tileSize_ = ol.size.toSize(tileGrid.getTileSize(tileCoord[0]));
 };
 ol.inherits(ol.source.Zoomify.Tile_, ol.ImageTile);
 
@@ -78691,14 +81855,14 @@ ol.source.Zoomify.Tile_.prototype.getImage = function() {
   if (this.zoomifyImage_) {
     return this.zoomifyImage_;
   }
-  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) {
+    var tileSize = this.tileSize_;
+    if (image.width == tileSize[0] && image.height == tileSize[1]) {
       this.zoomifyImage_ = image;
       return image;
     } else {
-      var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
+      var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]);
       context.drawImage(image, 0, 0);
       this.zoomifyImage_ = context.canvas;
       return context.canvas;
@@ -78708,7 +81872,6 @@ ol.source.Zoomify.Tile_.prototype.getImage = function() {
   }
 };
 
-
 /**
  * @enum {string}
  * @private
@@ -78718,407 +81881,6 @@ ol.source.Zoomify.TierSizeCalculation_ = {
   TRUNCATED: 'truncated'
 };
 
-goog.provide('ol.style.Atlas');
-
-goog.require('ol.dom');
-
-
-/**
- * This class facilitates the creation of image atlases.
- *
- * Images added to an atlas will be rendered onto a single
- * atlas canvas. The distribution of images on the canvas is
- * managed with the bin packing algorithm described in:
- * http://www.blackpawn.com/texts/lightmaps/
- *
- * @constructor
- * @struct
- * @param {number} size The size in pixels of the sprite image.
- * @param {number} space The space in pixels between images.
- *    Because texture coordinates are float values, the edges of
- *    images might not be completely correct (in a way that the
- *    edges overlap when being rendered). To avoid this we add a
- *    padding around each image.
- */
-ol.style.Atlas = function(size, space) {
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.space_ = space;
-
-  /**
-   * @private
-   * @type {Array.<ol.AtlasBlock>}
-   */
-  this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
-
-  /**
-   * @private
-   * @type {Object.<string, ol.AtlasInfo>}
-   */
-  this.entries_ = {};
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D(size, size);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = this.context_.canvas;
-};
-
-
-/**
- * @param {string} id The identifier of the entry to check.
- * @return {?ol.AtlasInfo} The atlas info.
- */
-ol.style.Atlas.prototype.get = function(id) {
-  return this.entries_[id] || null;
-};
-
-
-/**
- * @param {string} id The identifier of the entry to add.
- * @param {number} width The width.
- * @param {number} height The height.
- * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
- *    Called to render the new image onto an atlas image.
- * @param {Object=} opt_this Value to use as `this` when executing
- *    `renderCallback`.
- * @return {?ol.AtlasInfo} The position and atlas image for the entry.
- */
-ol.style.Atlas.prototype.add = function(id, width, height, renderCallback, opt_this) {
-  var block, i, ii;
-  for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) {
-    block = this.emptyBlocks_[i];
-    if (block.width >= width + this.space_ &&
-        block.height >= height + this.space_) {
-      // we found a block that is big enough for our entry
-      var entry = {
-        offsetX: block.x + this.space_,
-        offsetY: block.y + this.space_,
-        image: this.canvas_
-      };
-      this.entries_[id] = entry;
-
-      // render the image on the atlas image
-      renderCallback.call(opt_this, this.context_,
-          block.x + this.space_, block.y + this.space_);
-
-      // split the block after the insertion, either horizontally or vertically
-      this.split_(i, block, width + this.space_, height + this.space_);
-
-      return entry;
-    }
-  }
-
-  // there is no space for the new entry in this atlas
-  return null;
-};
-
-
-/**
- * @private
- * @param {number} index The index of the block.
- * @param {ol.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.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;
-
-  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.AtlasBlock} newBlock1 The 1st block to add.
- * @param {ol.AtlasBlock} 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);
-};
-
-goog.provide('ol.style.AtlasManager');
-
-goog.require('ol');
-goog.require('ol.style.Atlas');
-
-
-/**
- * Manages the creation of image atlases.
- *
- * Images added to this manager will be inserted into an atlas, which
- * will be used for rendering.
- * The `size` given in the constructor is the size for the first
- * atlas. After that, when new atlases are created, they will have
- * twice the size as the latest atlas (until `maxSize` is reached).
- *
- * If an application uses many images or very large images, it is recommended
- * to set a higher `size` value to avoid the creation of too many atlases.
- *
- * @constructor
- * @struct
- * @api
- * @param {olx.style.AtlasManagerOptions=} opt_options Options.
- */
-ol.style.AtlasManager = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * The size in pixels of the latest atlas image.
-   * @private
-   * @type {number}
-   */
-  this.currentSize_ = options.initialSize !== undefined ?
-      options.initialSize : ol.INITIAL_ATLAS_SIZE;
-
-  /**
-   * The maximum size in pixels of atlas images.
-   * @private
-   * @type {number}
-   */
-  this.maxSize_ = options.maxSize !== undefined ?
-      options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
-          ol.MAX_ATLAS_SIZE : ol.WEBGL_MAX_TEXTURE_SIZE !== undefined ?
-              ol.WEBGL_MAX_TEXTURE_SIZE : 2048;
-
-  /**
-   * The size in pixels between images.
-   * @private
-   * @type {number}
-   */
-  this.space_ = options.space !== undefined ? options.space : 1;
-
-  /**
-   * @private
-   * @type {Array.<ol.style.Atlas>}
-   */
-  this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
-
-  /**
-   * The size in pixels of the latest atlas image for hit-detection images.
-   * @private
-   * @type {number}
-   */
-  this.currentHitSize_ = this.currentSize_;
-
-  /**
-   * @private
-   * @type {Array.<ol.style.Atlas>}
-   */
-  this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
-};
-
-
-/**
- * @param {string} id The identifier of the entry to check.
- * @return {?ol.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.AtlasInfo} */
-  var info = this.getInfo_(this.atlases_, id);
-
-  if (!info) {
-    return null;
-  }
-  var hitInfo = /** @type {ol.AtlasInfo} */ (this.getInfo_(this.hitAtlases_, id));
-
-  return this.mergeInfos_(info, hitInfo);
-};
-
-
-/**
- * @private
- * @param {Array.<ol.style.Atlas>} atlases The atlases to search.
- * @param {string} id The identifier of the entry to check.
- * @return {?ol.AtlasInfo} The position and atlas image for the entry,
- *    or `null` if the entry is not part of the atlases.
- */
-ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
-  var atlas, info, i, ii;
-  for (i = 0, ii = atlases.length; i < ii; ++i) {
-    atlas = atlases[i];
-    info = atlas.get(id);
-    if (info) {
-      return info;
-    }
-  }
-  return null;
-};
-
-
-/**
- * @private
- * @param {ol.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.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
-  return /** @type {ol.AtlasManagerInfo} */ ({
-    offsetX: info.offsetX,
-    offsetY: info.offsetY,
-    image: info.image,
-    hitImage: hitInfo.image
-  });
-};
-
-
-/**
- * Add an image to the atlas manager.
- *
- * If an entry for the given id already exists, the entry will
- * be overridden (but the space on the atlas graphic will not be freed).
- *
- * If `renderHitCallback` is provided, the image (or the hit-detection version
- * of the image) will be rendered into a separate hit-detection atlas image.
- *
- * @param {string} id The identifier of the entry to add.
- * @param {number} width The width.
- * @param {number} height The height.
- * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
- *    Called to render the new image onto an atlas image.
- * @param {function(CanvasRenderingContext2D, number, number)=}
- *    opt_renderHitCallback Called to render a hit-detection image onto a hit
- *    detection atlas image.
- * @param {Object=} opt_this Value to use as `this` when executing
- *    `renderCallback` and `renderHitCallback`.
- * @return {?ol.AtlasManagerInfo}  The position and atlas image for the
- *    entry, or `null` if the image is too big.
- */
-ol.style.AtlasManager.prototype.add = function(id, width, height,
-        renderCallback, opt_renderHitCallback, opt_this) {
-  if (width + this.space_ > this.maxSize_ ||
-      height + this.space_ > this.maxSize_) {
-    return null;
-  }
-
-  /** @type {?ol.AtlasInfo} */
-  var info = this.add_(false,
-      id, width, height, renderCallback, opt_this);
-  if (!info) {
-    return null;
-  }
-
-  // even if no hit-detection entry is requested, we insert a fake entry into
-  // the hit-detection atlas, to make sure that the offset is the same for
-  // the original image and the hit-detection image.
-  var renderHitCallback = opt_renderHitCallback !== undefined ?
-      opt_renderHitCallback : ol.nullFunction;
-
-  var hitInfo = /** @type {ol.AtlasInfo} */ (this.add_(true,
-      id, width, height, renderHitCallback, opt_this));
-
-  return this.mergeInfos_(info, hitInfo);
-};
-
-
-/**
- * @private
- * @param {boolean} isHitAtlas If the hit-detection atlases are used.
- * @param {string} id The identifier of the entry to add.
- * @param {number} width The width.
- * @param {number} height The height.
- * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
- *    Called to render the new image onto an atlas image.
- * @param {Object=} opt_this Value to use as `this` when executing
- *    `renderCallback` and `renderHitCallback`.
- * @return {?ol.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;
-    }
-  }
-  return null;
-};
-
 // Copyright 2009 The Closure Library Authors.
 // All Rights Reserved.
 //
@@ -79173,6 +81935,7 @@ goog.addDependency(
 goog.require('ol');
 goog.require('ol.AssertionError');
 goog.require('ol.Attribution');
+goog.require('ol.CanvasMap');
 goog.require('ol.Collection');
 goog.require('ol.DeviceOrientation');
 goog.require('ol.Feature');
@@ -79187,6 +81950,7 @@ goog.require('ol.MapEvent');
 goog.require('ol.Object');
 goog.require('ol.Observable');
 goog.require('ol.Overlay');
+goog.require('ol.PluggableMap');
 goog.require('ol.Sphere');
 goog.require('ol.Tile');
 goog.require('ol.VectorTile');
@@ -79234,6 +81998,7 @@ 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.Contains');
 goog.require('ol.format.filter.During');
 goog.require('ol.format.filter.EqualTo');
 goog.require('ol.format.filter.Filter');
@@ -79302,6 +82067,15 @@ 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.renderer.canvas.ImageLayer');
+goog.require('ol.renderer.canvas.Map');
+goog.require('ol.renderer.canvas.TileLayer');
+goog.require('ol.renderer.canvas.VectorLayer');
+goog.require('ol.renderer.canvas.VectorTileLayer');
+goog.require('ol.renderer.webgl.ImageLayer');
+goog.require('ol.renderer.webgl.Map');
+goog.require('ol.renderer.webgl.TileLayer');
+goog.require('ol.renderer.webgl.VectorLayer');
 goog.require('ol.size');
 goog.require('ol.source.BingMaps');
 goog.require('ol.source.CartoDB');
@@ -79330,10 +82104,12 @@ goog.require('ol.source.VectorTile');
 goog.require('ol.source.WMTS');
 goog.require('ol.source.XYZ');
 goog.require('ol.source.Zoomify');
+goog.require('ol.style');
 goog.require('ol.style.AtlasManager');
 goog.require('ol.style.Circle');
 goog.require('ol.style.Fill');
 goog.require('ol.style.Icon');
+goog.require('ol.style.IconImageCache');
 goog.require('ol.style.Image');
 goog.require('ol.style.RegularShape');
 goog.require('ol.style.Stroke');
@@ -79362,6 +82138,11 @@ goog.exportProperty(
     'getHTML',
     ol.Attribution.prototype.getHTML);
 
+goog.exportSymbol(
+    'ol.CanvasMap',
+    ol.CanvasMap,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.Collection',
     ol.Collection,
@@ -79882,171 +82663,6 @@ goog.exportSymbol(
     ol.Map,
     OPENLAYERS);
 
-goog.exportProperty(
-    ol.Map.prototype,
-    'addControl',
-    ol.Map.prototype.addControl);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'addInteraction',
-    ol.Map.prototype.addInteraction);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'addLayer',
-    ol.Map.prototype.addLayer);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'addOverlay',
-    ol.Map.prototype.addOverlay);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'forEachFeatureAtPixel',
-    ol.Map.prototype.forEachFeatureAtPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'forEachLayerAtPixel',
-    ol.Map.prototype.forEachLayerAtPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'hasFeatureAtPixel',
-    ol.Map.prototype.hasFeatureAtPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getEventCoordinate',
-    ol.Map.prototype.getEventCoordinate);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getEventPixel',
-    ol.Map.prototype.getEventPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getTarget',
-    ol.Map.prototype.getTarget);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getTargetElement',
-    ol.Map.prototype.getTargetElement);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getCoordinateFromPixel',
-    ol.Map.prototype.getCoordinateFromPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getControls',
-    ol.Map.prototype.getControls);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getOverlays',
-    ol.Map.prototype.getOverlays);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getOverlayById',
-    ol.Map.prototype.getOverlayById);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getInteractions',
-    ol.Map.prototype.getInteractions);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getLayerGroup',
-    ol.Map.prototype.getLayerGroup);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getLayers',
-    ol.Map.prototype.getLayers);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getPixelFromCoordinate',
-    ol.Map.prototype.getPixelFromCoordinate);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getSize',
-    ol.Map.prototype.getSize);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getView',
-    ol.Map.prototype.getView);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getViewport',
-    ol.Map.prototype.getViewport);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'renderSync',
-    ol.Map.prototype.renderSync);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'render',
-    ol.Map.prototype.render);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeControl',
-    ol.Map.prototype.removeControl);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeInteraction',
-    ol.Map.prototype.removeInteraction);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeLayer',
-    ol.Map.prototype.removeLayer);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeOverlay',
-    ol.Map.prototype.removeOverlay);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setLayerGroup',
-    ol.Map.prototype.setLayerGroup);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setSize',
-    ol.Map.prototype.setSize);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setTarget',
-    ol.Map.prototype.setTarget);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setView',
-    ol.Map.prototype.setView);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'updateSize',
-    ol.Map.prototype.updateSize);
-
 goog.exportProperty(
     ol.MapBrowserEvent.prototype,
     'originalEvent',
@@ -80222,6 +82838,181 @@ goog.exportProperty(
     'setPositioning',
     ol.Overlay.prototype.setPositioning);
 
+goog.exportSymbol(
+    'ol.PluggableMap',
+    ol.PluggableMap,
+    OPENLAYERS);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'addControl',
+    ol.PluggableMap.prototype.addControl);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'addInteraction',
+    ol.PluggableMap.prototype.addInteraction);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'addLayer',
+    ol.PluggableMap.prototype.addLayer);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'addOverlay',
+    ol.PluggableMap.prototype.addOverlay);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'forEachFeatureAtPixel',
+    ol.PluggableMap.prototype.forEachFeatureAtPixel);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getFeaturesAtPixel',
+    ol.PluggableMap.prototype.getFeaturesAtPixel);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'forEachLayerAtPixel',
+    ol.PluggableMap.prototype.forEachLayerAtPixel);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'hasFeatureAtPixel',
+    ol.PluggableMap.prototype.hasFeatureAtPixel);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getEventCoordinate',
+    ol.PluggableMap.prototype.getEventCoordinate);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getEventPixel',
+    ol.PluggableMap.prototype.getEventPixel);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getTarget',
+    ol.PluggableMap.prototype.getTarget);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getTargetElement',
+    ol.PluggableMap.prototype.getTargetElement);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getCoordinateFromPixel',
+    ol.PluggableMap.prototype.getCoordinateFromPixel);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getControls',
+    ol.PluggableMap.prototype.getControls);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getOverlays',
+    ol.PluggableMap.prototype.getOverlays);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getOverlayById',
+    ol.PluggableMap.prototype.getOverlayById);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getInteractions',
+    ol.PluggableMap.prototype.getInteractions);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getLayerGroup',
+    ol.PluggableMap.prototype.getLayerGroup);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getLayers',
+    ol.PluggableMap.prototype.getLayers);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getPixelFromCoordinate',
+    ol.PluggableMap.prototype.getPixelFromCoordinate);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getSize',
+    ol.PluggableMap.prototype.getSize);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getView',
+    ol.PluggableMap.prototype.getView);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getViewport',
+    ol.PluggableMap.prototype.getViewport);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'renderSync',
+    ol.PluggableMap.prototype.renderSync);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'render',
+    ol.PluggableMap.prototype.render);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'removeControl',
+    ol.PluggableMap.prototype.removeControl);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'removeInteraction',
+    ol.PluggableMap.prototype.removeInteraction);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'removeLayer',
+    ol.PluggableMap.prototype.removeLayer);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'removeOverlay',
+    ol.PluggableMap.prototype.removeOverlay);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'setLayerGroup',
+    ol.PluggableMap.prototype.setLayerGroup);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'setSize',
+    ol.PluggableMap.prototype.setSize);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'setTarget',
+    ol.PluggableMap.prototype.setTarget);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'setView',
+    ol.PluggableMap.prototype.setView);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'updateSize',
+    ol.PluggableMap.prototype.updateSize);
+
 goog.exportSymbol(
     'ol.proj.METERS_PER_UNIT',
     ol.proj.METERS_PER_UNIT,
@@ -80312,6 +83103,21 @@ goog.exportProperty(
     'haversineDistance',
     ol.Sphere.prototype.haversineDistance);
 
+goog.exportSymbol(
+    'ol.Sphere.getLength',
+    ol.Sphere.getLength,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.Sphere.getArea',
+    ol.Sphere.getArea,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.style.iconImageCache',
+    ol.style.iconImageCache,
+    OPENLAYERS);
+
 goog.exportProperty(
     ol.Tile.prototype,
     'getTileCoord',
@@ -80327,6 +83133,11 @@ goog.exportSymbol(
     ol.tilegrid.createXYZ,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'getExtent',
+    ol.VectorTile.prototype.getExtent);
+
 goog.exportProperty(
     ol.VectorTile.prototype,
     'getFormat',
@@ -80342,6 +83153,11 @@ goog.exportProperty(
     'getProjection',
     ol.VectorTile.prototype.getProjection);
 
+goog.exportProperty(
+    ol.VectorTile.prototype,
+    'setExtent',
+    ol.VectorTile.prototype.setExtent);
+
 goog.exportProperty(
     ol.VectorTile.prototype,
     'setFeatures',
@@ -80472,6 +83288,11 @@ goog.exportProperty(
     'getZoomForResolution',
     ol.View.prototype.getZoomForResolution);
 
+goog.exportProperty(
+    ol.View.prototype,
+    'getResolutionForZoom',
+    ol.View.prototype.getResolutionForZoom);
+
 goog.exportProperty(
     ol.View.prototype,
     'fit',
@@ -80682,6 +83503,11 @@ goog.exportProperty(
     'load',
     ol.style.Icon.prototype.load);
 
+goog.exportProperty(
+    ol.style.IconImageCache.prototype,
+    'setSize',
+    ol.style.IconImageCache.prototype.setSize);
+
 goog.exportSymbol(
     'ol.style.Image',
     ol.style.Image,
@@ -80877,6 +83703,16 @@ goog.exportProperty(
     'clone',
     ol.style.Style.prototype.clone);
 
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'getRenderer',
+    ol.style.Style.prototype.getRenderer);
+
+goog.exportProperty(
+    ol.style.Style.prototype,
+    'setRenderer',
+    ol.style.Style.prototype.setRenderer);
+
 goog.exportProperty(
     ol.style.Style.prototype,
     'getGeometry',
@@ -80952,11 +83788,26 @@ goog.exportProperty(
     'clone',
     ol.style.Text.prototype.clone);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'getOverflow',
+    ol.style.Text.prototype.getOverflow);
+
 goog.exportProperty(
     ol.style.Text.prototype,
     'getFont',
     ol.style.Text.prototype.getFont);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'getMaxAngle',
+    ol.style.Text.prototype.getMaxAngle);
+
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'getPlacement',
+    ol.style.Text.prototype.getPlacement);
+
 goog.exportProperty(
     ol.style.Text.prototype,
     'getOffsetX',
@@ -81007,11 +83858,36 @@ goog.exportProperty(
     'getTextBaseline',
     ol.style.Text.prototype.getTextBaseline);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'getBackgroundFill',
+    ol.style.Text.prototype.getBackgroundFill);
+
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'getBackgroundStroke',
+    ol.style.Text.prototype.getBackgroundStroke);
+
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'getPadding',
+    ol.style.Text.prototype.getPadding);
+
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'setOverflow',
+    ol.style.Text.prototype.setOverflow);
+
 goog.exportProperty(
     ol.style.Text.prototype,
     'setFont',
     ol.style.Text.prototype.setFont);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'setMaxAngle',
+    ol.style.Text.prototype.setMaxAngle);
+
 goog.exportProperty(
     ol.style.Text.prototype,
     'setOffsetX',
@@ -81022,6 +83898,11 @@ goog.exportProperty(
     'setOffsetY',
     ol.style.Text.prototype.setOffsetY);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'setPlacement',
+    ol.style.Text.prototype.setPlacement);
+
 goog.exportProperty(
     ol.style.Text.prototype,
     'setFill',
@@ -81057,6 +83938,21 @@ goog.exportProperty(
     'setTextBaseline',
     ol.style.Text.prototype.setTextBaseline);
 
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'setBackgroundFill',
+    ol.style.Text.prototype.setBackgroundFill);
+
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'setBackgroundStroke',
+    ol.style.Text.prototype.setBackgroundStroke);
+
+goog.exportProperty(
+    ol.style.Text.prototype,
+    'setPadding',
+    ol.style.Text.prototype.setPadding);
+
 goog.exportSymbol(
     'ol.source.BingMaps',
     ol.source.BingMaps,
@@ -81547,11 +84443,21 @@ goog.exportProperty(
     'getUrl',
     ol.source.Vector.prototype.getUrl);
 
+goog.exportProperty(
+    ol.source.Vector.prototype,
+    'removeLoadedExtent',
+    ol.source.Vector.prototype.removeLoadedExtent);
+
 goog.exportProperty(
     ol.source.Vector.prototype,
     'removeFeature',
     ol.source.Vector.prototype.removeFeature);
 
+goog.exportProperty(
+    ol.source.Vector.prototype,
+    'setLoader',
+    ol.source.Vector.prototype.setLoader);
+
 goog.exportProperty(
     ol.source.Vector.Event.prototype,
     'feature',
@@ -81562,6 +84468,11 @@ goog.exportSymbol(
     ol.source.VectorTile,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.source.VectorTile.prototype,
+    'clear',
+    ol.source.VectorTile.prototype.clear);
+
 goog.exportSymbol(
     'ol.source.WMTS',
     ol.source.WMTS,
@@ -81622,6 +84533,51 @@ goog.exportSymbol(
     ol.source.Zoomify,
     OPENLAYERS);
 
+goog.exportSymbol(
+    'ol.renderer.webgl.ImageLayer',
+    ol.renderer.webgl.ImageLayer,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.webgl.Map',
+    ol.renderer.webgl.Map,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.webgl.TileLayer',
+    ol.renderer.webgl.TileLayer,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.webgl.VectorLayer',
+    ol.renderer.webgl.VectorLayer,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.canvas.ImageLayer',
+    ol.renderer.canvas.ImageLayer,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.canvas.Map',
+    ol.renderer.canvas.Map,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.canvas.TileLayer',
+    ol.renderer.canvas.TileLayer,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.canvas.VectorLayer',
+    ol.renderer.canvas.VectorLayer,
+    OPENLAYERS);
+
+goog.exportSymbol(
+    'ol.renderer.canvas.VectorTileLayer',
+    ol.renderer.canvas.VectorTileLayer,
+    OPENLAYERS);
+
 goog.exportProperty(
     ol.render.Event.prototype,
     'vectorContext',
@@ -81747,6 +84703,11 @@ goog.exportProperty(
     'getWorldExtent',
     ol.proj.Projection.prototype.getWorldExtent);
 
+goog.exportProperty(
+    ol.proj.Projection.prototype,
+    'getAxisOrientation',
+    ol.proj.Projection.prototype.getAxisOrientation);
+
 goog.exportProperty(
     ol.proj.Projection.prototype,
     'isGlobal',
@@ -82002,6 +84963,11 @@ goog.exportProperty(
     'setUseInterimTilesOnError',
     ol.layer.VectorTile.prototype.setUseInterimTilesOnError);
 
+goog.exportProperty(
+    ol.layer.VectorTile.prototype,
+    'getSource',
+    ol.layer.VectorTile.prototype.getSource);
+
 goog.exportSymbol(
     'ol.interaction.DoubleClickZoom',
     ol.interaction.DoubleClickZoom,
@@ -82134,8 +85100,8 @@ goog.exportProperty(
 
 goog.exportProperty(
     ol.interaction.Extent.Event.prototype,
-    'extent_',
-    ol.interaction.Extent.Event.prototype.extent_);
+    'extent',
+    ol.interaction.Extent.Event.prototype.extent);
 
 goog.exportSymbol(
     'ol.interaction.Interaction',
@@ -82907,6 +85873,11 @@ goog.exportSymbol(
     ol.format.filter.bbox,
     OPENLAYERS);
 
+goog.exportSymbol(
+    'ol.format.filter.contains',
+    ol.format.filter.contains,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.format.filter.intersects',
     ol.format.filter.intersects,
@@ -83172,6 +86143,11 @@ goog.exportSymbol(
     ol.format.MVT,
     OPENLAYERS);
 
+goog.exportProperty(
+    ol.format.MVT.prototype,
+    'getLastExtent',
+    ol.format.MVT.prototype.getLastExtent);
+
 goog.exportProperty(
     ol.format.MVT.prototype,
     'readFeatures',
@@ -83392,6 +86368,11 @@ goog.exportSymbol(
     ol.format.filter.ComparisonBinary,
     OPENLAYERS);
 
+goog.exportSymbol(
+    'ol.format.filter.Contains',
+    ol.format.filter.Contains,
+    OPENLAYERS);
+
 goog.exportSymbol(
     'ol.format.filter.During',
     ol.format.filter.During,
@@ -83762,6 +86743,296 @@ goog.exportProperty(
     'un',
     ol.Object.prototype.un);
 
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'get',
+    ol.PluggableMap.prototype.get);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getKeys',
+    ol.PluggableMap.prototype.getKeys);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getProperties',
+    ol.PluggableMap.prototype.getProperties);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'set',
+    ol.PluggableMap.prototype.set);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'setProperties',
+    ol.PluggableMap.prototype.setProperties);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'unset',
+    ol.PluggableMap.prototype.unset);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'changed',
+    ol.PluggableMap.prototype.changed);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'dispatchEvent',
+    ol.PluggableMap.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'getRevision',
+    ol.PluggableMap.prototype.getRevision);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'on',
+    ol.PluggableMap.prototype.on);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'once',
+    ol.PluggableMap.prototype.once);
+
+goog.exportProperty(
+    ol.PluggableMap.prototype,
+    'un',
+    ol.PluggableMap.prototype.un);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'addControl',
+    ol.CanvasMap.prototype.addControl);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'addInteraction',
+    ol.CanvasMap.prototype.addInteraction);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'addLayer',
+    ol.CanvasMap.prototype.addLayer);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'addOverlay',
+    ol.CanvasMap.prototype.addOverlay);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'forEachFeatureAtPixel',
+    ol.CanvasMap.prototype.forEachFeatureAtPixel);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getFeaturesAtPixel',
+    ol.CanvasMap.prototype.getFeaturesAtPixel);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'forEachLayerAtPixel',
+    ol.CanvasMap.prototype.forEachLayerAtPixel);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'hasFeatureAtPixel',
+    ol.CanvasMap.prototype.hasFeatureAtPixel);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getEventCoordinate',
+    ol.CanvasMap.prototype.getEventCoordinate);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getEventPixel',
+    ol.CanvasMap.prototype.getEventPixel);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getTarget',
+    ol.CanvasMap.prototype.getTarget);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getTargetElement',
+    ol.CanvasMap.prototype.getTargetElement);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getCoordinateFromPixel',
+    ol.CanvasMap.prototype.getCoordinateFromPixel);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getControls',
+    ol.CanvasMap.prototype.getControls);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getOverlays',
+    ol.CanvasMap.prototype.getOverlays);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getOverlayById',
+    ol.CanvasMap.prototype.getOverlayById);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getInteractions',
+    ol.CanvasMap.prototype.getInteractions);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getLayerGroup',
+    ol.CanvasMap.prototype.getLayerGroup);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getLayers',
+    ol.CanvasMap.prototype.getLayers);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getPixelFromCoordinate',
+    ol.CanvasMap.prototype.getPixelFromCoordinate);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getSize',
+    ol.CanvasMap.prototype.getSize);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getView',
+    ol.CanvasMap.prototype.getView);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getViewport',
+    ol.CanvasMap.prototype.getViewport);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'renderSync',
+    ol.CanvasMap.prototype.renderSync);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'render',
+    ol.CanvasMap.prototype.render);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'removeControl',
+    ol.CanvasMap.prototype.removeControl);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'removeInteraction',
+    ol.CanvasMap.prototype.removeInteraction);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'removeLayer',
+    ol.CanvasMap.prototype.removeLayer);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'removeOverlay',
+    ol.CanvasMap.prototype.removeOverlay);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'setLayerGroup',
+    ol.CanvasMap.prototype.setLayerGroup);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'setSize',
+    ol.CanvasMap.prototype.setSize);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'setTarget',
+    ol.CanvasMap.prototype.setTarget);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'setView',
+    ol.CanvasMap.prototype.setView);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'updateSize',
+    ol.CanvasMap.prototype.updateSize);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'get',
+    ol.CanvasMap.prototype.get);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getKeys',
+    ol.CanvasMap.prototype.getKeys);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getProperties',
+    ol.CanvasMap.prototype.getProperties);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'set',
+    ol.CanvasMap.prototype.set);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'setProperties',
+    ol.CanvasMap.prototype.setProperties);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'unset',
+    ol.CanvasMap.prototype.unset);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'changed',
+    ol.CanvasMap.prototype.changed);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'dispatchEvent',
+    ol.CanvasMap.prototype.dispatchEvent);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'getRevision',
+    ol.CanvasMap.prototype.getRevision);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'on',
+    ol.CanvasMap.prototype.on);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'once',
+    ol.CanvasMap.prototype.once);
+
+goog.exportProperty(
+    ol.CanvasMap.prototype,
+    'un',
+    ol.CanvasMap.prototype.un);
+
 goog.exportProperty(
     ol.Collection.prototype,
     'get',
@@ -84032,6 +87303,176 @@ goog.exportProperty(
     'load',
     ol.ImageTile.prototype.load);
 
+goog.exportProperty(
+    ol.Map.prototype,
+    'addControl',
+    ol.Map.prototype.addControl);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'addInteraction',
+    ol.Map.prototype.addInteraction);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'addLayer',
+    ol.Map.prototype.addLayer);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'addOverlay',
+    ol.Map.prototype.addOverlay);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'forEachFeatureAtPixel',
+    ol.Map.prototype.forEachFeatureAtPixel);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getFeaturesAtPixel',
+    ol.Map.prototype.getFeaturesAtPixel);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'forEachLayerAtPixel',
+    ol.Map.prototype.forEachLayerAtPixel);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'hasFeatureAtPixel',
+    ol.Map.prototype.hasFeatureAtPixel);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getEventCoordinate',
+    ol.Map.prototype.getEventCoordinate);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getEventPixel',
+    ol.Map.prototype.getEventPixel);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getTarget',
+    ol.Map.prototype.getTarget);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getTargetElement',
+    ol.Map.prototype.getTargetElement);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getCoordinateFromPixel',
+    ol.Map.prototype.getCoordinateFromPixel);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getControls',
+    ol.Map.prototype.getControls);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getOverlays',
+    ol.Map.prototype.getOverlays);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getOverlayById',
+    ol.Map.prototype.getOverlayById);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getInteractions',
+    ol.Map.prototype.getInteractions);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getLayerGroup',
+    ol.Map.prototype.getLayerGroup);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getLayers',
+    ol.Map.prototype.getLayers);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getPixelFromCoordinate',
+    ol.Map.prototype.getPixelFromCoordinate);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getSize',
+    ol.Map.prototype.getSize);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getView',
+    ol.Map.prototype.getView);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'getViewport',
+    ol.Map.prototype.getViewport);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'renderSync',
+    ol.Map.prototype.renderSync);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'render',
+    ol.Map.prototype.render);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'removeControl',
+    ol.Map.prototype.removeControl);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'removeInteraction',
+    ol.Map.prototype.removeInteraction);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'removeLayer',
+    ol.Map.prototype.removeLayer);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'removeOverlay',
+    ol.Map.prototype.removeOverlay);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'setLayerGroup',
+    ol.Map.prototype.setLayerGroup);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'setSize',
+    ol.Map.prototype.setSize);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'setTarget',
+    ol.Map.prototype.setTarget);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'setView',
+    ol.Map.prototype.setView);
+
+goog.exportProperty(
+    ol.Map.prototype,
+    'updateSize',
+    ol.Map.prototype.updateSize);
+
 goog.exportProperty(
     ol.Map.prototype,
     'get',
@@ -85522,11 +88963,21 @@ goog.exportProperty(
     'getUrl',
     ol.source.Cluster.prototype.getUrl);
 
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'removeLoadedExtent',
+    ol.source.Cluster.prototype.removeLoadedExtent);
+
 goog.exportProperty(
     ol.source.Cluster.prototype,
     'removeFeature',
     ol.source.Cluster.prototype.removeFeature);
 
+goog.exportProperty(
+    ol.source.Cluster.prototype,
+    'setLoader',
+    ol.source.Cluster.prototype.setLoader);
+
 goog.exportProperty(
     ol.source.Cluster.prototype,
     'getAttributions',
@@ -88937,11 +92388,6 @@ goog.exportProperty(
     'un',
     ol.layer.Tile.prototype.un);
 
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getSource',
-    ol.layer.VectorTile.prototype.getSource);
-
 goog.exportProperty(
     ol.layer.VectorTile.prototype,
     'getStyle',
@@ -92591,7 +96037,7 @@ goog.exportProperty(
     ol.control.ZoomToExtent.prototype,
     'un',
     ol.control.ZoomToExtent.prototype.un);
-ol.VERSION = 'v4.2.0';
+ol.VERSION = 'v4.6.5';
 OPENLAYERS.ol = ol;
 
   return OPENLAYERS.ol;
diff --git a/VIPSWeb/static/js/3rdparty/ol.js b/VIPSWeb/static/js/3rdparty/ol.js
index cca3486371f018addbab0d767ea9b421c812ae77..90c3d055c4ad851f9812733f7f62c42fe92a68c2 100644
--- a/VIPSWeb/static/js/3rdparty/ol.js
+++ b/VIPSWeb/static/js/3rdparty/ol.js
@@ -1,6 +1,6 @@
 // OpenLayers. See https://openlayers.org/
 // License: https://raw.githubusercontent.com/openlayers/openlayers/master/LICENSE.md
-// Version: v4.2.0
+// Version: v4.6.5
 ;(function (root, factory) {
   if (typeof exports === "object") {
     module.exports = factory();
@@ -11,1029 +11,1063 @@
   }
 }(this, function () {
   var OPENLAYERS = {};
-  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};/*
+  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 ba,da;function w(a,b){a.prototype=Object.create(b.prototype);a.prototype.constructor=a}function ea(){}function x(a){return a.xp||(a.xp=++fa)}var fa=0;function ha(a){this.message="Assertion failed. See https://openlayers.org/en/v4.6.5/doc/errors/#"+a+" for details.";this.code=a;this.name="AssertionError"}w(ha,Error);function ja(a,b,c,d){this.fa=a;this.la=b;this.ea=c;this.ka=d}function ka(a,b,c,d,e){return void 0!==e?(e.fa=a,e.la=b,e.ea=c,e.ka=d,e):new ja(a,b,c,d)}function ma(a,b,c){return a.fa<=b&&b<=a.la&&a.ea<=c&&c<=a.ka}function na(a,b){return a.fa==b.fa&&a.ea==b.ea&&a.la==b.la&&a.ka==b.ka};function oa(a,b){if(!a)throw new ha(b);};function pa(a,b,c){return Math.min(Math.max(a,b),c)}var qa=function(){var a;"cosh"in Math?a=Math.cosh:a=function(a){a=Math.exp(a);return(a+1/a)/2};return a}();function ra(a){oa(0<a,29);return Math.pow(2,Math.ceil(Math.log(a)/Math.LN2))}function sa(a,b,c,d,e,f){var g=e-c,h=f-d;if(0!==g||0!==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 ua(a,b,c,d)}function ua(a,b,c,d){a=c-a;b=d-b;return a*a+b*b}function va(a){return a*Math.PI/180}
+function wa(a,b){a%=b;return 0>a*b?a+b:a}function ya(a,b,c){return a+c*(b-a)};function za(a,b,c){void 0===c&&(c=[0,0]);c[0]=a[0]+2*b;c[1]=a[1]+2*b;return c}function Aa(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 Ba(a,b){if(Array.isArray(a))return a;void 0===b?b=[a,a]:b[0]=b[1]=a;return b};function Ca(a){for(var b=Da(),c=0,d=a.length;c<d;++c)Ea(b,a[c]);return b}function Fa(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 Ga(a,b){return b?(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b):a.slice()}function Ha(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 Ja(a,b){return Ka(a,b[0],b[1])}function La(a,b){return a[0]<=b[0]&&b[2]<=a[2]&&a[1]<=b[1]&&b[3]<=a[3]}
+function Ka(a,b,c){return a[0]<=b&&b<=a[2]&&a[1]<=c&&c<=a[3]}function Ma(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);0===g&&(g=1);return g}function Da(){return[Infinity,Infinity,-Infinity,-Infinity]}function Na(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 Oa(a){return Na(Infinity,Infinity,-Infinity,-Infinity,a)}function Pa(a,b){var c=a[0];a=a[1];return Na(c,a,c,a,b)}
+function Qa(a,b,c,d,e){e=Oa(e);return Ra(e,a,b,c,d)}function Sa(a,b){return a[0]==b[0]&&a[2]==b[2]&&a[1]==b[1]&&a[3]==b[3]}function Ta(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 Ea(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 Ra(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 Ua(a,b,c){var d;return(d=b.call(c,Wa(a)))||(d=b.call(c,Ya(a)))||(d=b.call(c,Za(a)))?d:(d=b.call(c,$a(a)))?d:!1}function ab(a){var b=0;bb(a)||(b=cb(a)*db(a));return b}function Wa(a){return[a[0],a[1]]}function Ya(a){return[a[2],a[1]]}function eb(a){return[(a[0]+a[2])/2,(a[1]+a[3])/2]}
+function fb(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;h=m-f-b;l=m-f+b;var n=m+f+b;f=m+f-b;return Na(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 db(a){return a[3]-a[1]}function gb(a,b,c){c=c?c:Da();hb(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 $a(a){return[a[0],a[3]]}
+function Za(a){return[a[2],a[3]]}function cb(a){return a[2]-a[0]}function hb(a,b){return a[0]<=b[2]&&a[2]>=b[0]&&a[1]<=b[3]&&a[3]>=b[1]}function bb(a){return a[2]<a[0]||a[3]<a[1]}function ib(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 jb(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 Na(b,a,d,e,c)};var kb="function"===typeof Object.assign?Object.assign:function(a,b){if(void 0===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 lb(a){for(var b in a)delete a[b]}function mb(a){var b=[],c;for(c in a)b.push(a[c]);return b}function nb(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 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=
+function ob(a){this.radius=a}ob.prototype.a=function(a){return pb(a,this.radius)};ob.prototype.b=function(a,b){return qb(a,b,this.radius)};ob.prototype.offset=function(a,b,c){var d=va(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*(va(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]};
+function rb(a,b){var c=b||{},d=c.radius||6371008.8;c=c.projection||"EPSG:3857";a=a.clone().mb(c,"EPSG:4326");var e=a.S();c=0;var f;switch(e){case "Point":case "MultiPoint":break;case "LineString":case "LinearRing":b=a.W();c=sb(b,d);break;case "MultiLineString":case "Polygon":b=a.W();a=0;for(e=b.length;a<e;++a)c+=sb(b[a],d);break;case "MultiPolygon":b=a.W();a=0;for(e=b.length;a<e;++a){var g=b[a];var h=0;for(f=g.length;h<f;++h)c+=sb(g[h],d)}break;case "GeometryCollection":d=a.vd();a=0;for(e=d.length;a<
+e;++a)c+=rb(d[a],b);break;default:throw Error("Unsupported geometry type: "+e);}return c}function sb(a,b){for(var c=0,d=0,e=a.length;d<e-1;++d)c+=qb(a[d],a[d+1],b);return c}function qb(a,b,c){var d=va(a[1]),e=va(b[1]),f=(e-d)/2;a=va(b[0]-a[0])/2;d=Math.sin(f)*Math.sin(f)+Math.sin(a)*Math.sin(a)*Math.cos(d)*Math.cos(e);return 2*c*Math.atan2(Math.sqrt(d),Math.sqrt(1-d))}
+function tb(a,b){var c=b||{},d=c.radius||6371008.8;c=c.projection||"EPSG:3857";a=a.clone().mb(c,"EPSG:4326");var e=a.S();c=0;var f;switch(e){case "Point":case "MultiPoint":case "LineString":case "MultiLineString":case "LinearRing":break;case "Polygon":b=a.W();c=Math.abs(pb(b[0],d));a=1;for(e=b.length;a<e;++a)c-=Math.abs(pb(b[a],d));break;case "MultiPolygon":b=a.W();a=0;for(e=b.length;a<e;++a){var g=b[a];c+=Math.abs(pb(g[0],d));var h=1;for(f=g.length;h<f;++h)c-=Math.abs(pb(g[h],d))}break;case "GeometryCollection":d=
+a.vd();a=0;for(e=d.length;a<e;++a)c+=tb(d[a],b);break;default:throw Error("Unsupported geometry type: "+e);}return c}function pb(a,b){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],l=a[g][1];c+=va(h-e)*(2+Math.sin(va(f))+Math.sin(va(l)));e=h;f=l}return c*b*b/2};var ub={};ub.degrees=12741994*Math.PI/360;ub.ft=.3048;ub.m=1;ub["us-ft"]=1200/3937;var vb=null;function wb(a){this.wb=a.code;this.a=a.units;this.i=void 0!==a.extent?a.extent:null;this.oe=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.g=!(!this.c||!this.i);this.j=a.getPointResolution;this.f=null;this.l=a.metersPerUnit;var b=a.code,c=vb||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=wb.prototype;k.ml=function(){return this.wb};k.G=function(){return this.i};k.zo=function(){return this.a};k.Bc=function(){return this.l||ub[this.a]};k.Vl=function(){return this.oe};k.il=function(){return this.b};k.Gm=function(){return this.c};k.xq=function(a){this.c=a;this.g=!(!a||!this.i)};k.Si=function(a){this.i=a;this.g=!(!this.c||!a)};k.Sj=function(a){this.oe=a};k.wq=function(a){this.j=a};function xb(a){wb.call(this,{code:a,units:"m",extent:yb,global:!0,worldExtent:zb,getPointResolution:function(a,c){return a/qa(c[1]/6378137)}})}w(xb,wb);var Ab=6378137*Math.PI,yb=[-Ab,-Ab,Ab,Ab],zb=[-180,-85,180,85],Bb=[new xb("EPSG:3857"),new xb("EPSG:102100"),new xb("EPSG:102113"),new xb("EPSG:900913"),new xb("urn:ogc:def:crs:EPSG:6.18:3:3857"),new xb("urn:ogc:def:crs:EPSG::3857"),new xb("http://www.opengis.net/gml/srs/epsg.xml#3857")];
+function Cb(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]=Ab*a[e]/180;var f=6378137*Math.log(Math.tan(Math.PI*(a[e+1]+90)/360));f>Ab?f=Ab:f<-Ab&&(f=-Ab);b[e+1]=f}return b}function Db(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]/Ab,b[e+1]=360*Math.atan(Math.exp(a[e+1]/6378137))/Math.PI-90;return b};function Eb(a,b){wb.call(this,{code:a,units:"degrees",extent:Fb,axisOrientation:b,global:!0,metersPerUnit:Gb,worldExtent:Fb})}w(Eb,wb);var Fb=[-180,-90,180,90],Gb=6378137*Math.PI/180,Hb=[new Eb("CRS:84"),new Eb("EPSG:4326","neu"),new Eb("urn:ogc:def:crs:EPSG::4326","neu"),new Eb("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new Eb("urn:ogc:def:crs:OGC:1.3:CRS84"),new Eb("urn:ogc:def:crs:OGC:2:84"),new Eb("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new Eb("urn:x-ogc:def:crs:EPSG:4326","neu")];var Ib={};var Jb={};function Kb(a,b,c){a=a.wb;b=b.wb;a in Jb||(Jb[a]={});Jb[a][b]=c}function Lb(a,b){var c;a in Jb&&b in Jb[a]&&(c=Jb[a][b]);return c};var Mb=new ob(6371008.8);function Nb(a,b,c,d){a=Ob(a);var e=a.j;e?b=e(b,c):"degrees"==a.a&&!d||"degrees"==d||(e=Pb(a,Ob("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=e(b,b,2),b=(Mb.b(b.slice(0,2),b.slice(2,4))+Mb.b(b.slice(4,6),b.slice(6,8)))/2,a=d?ub[d]:a.Bc(),void 0!==a&&(b/=a));return b}function Qb(a){a.forEach(Rb);a.forEach(function(b){a.forEach(function(a){b!==a&&Kb(b,a,Sb)})})}
+function Tb(){Hb.forEach(function(a){Bb.forEach(function(b){Kb(a,b,Cb);Kb(b,a,Db)})})}function Rb(a){Ib[a.wb]=a;Kb(a,a,Sb)}function Ub(a){return a?"string"===typeof a?Ob(a):a:Ob("EPSG:3857")}function Vb(a,b,c,d){a=Ob(a);b=Ob(b);Kb(a,b,Wb(c));Kb(b,a,Wb(d))}function Wb(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 Ob(a){var b=null;if(a instanceof wb)b=a;else if("string"===typeof a&&(b=Ib[a]||null,!b)){var c=vb||window.proj4;"function"==typeof c&&void 0!==c.defs(a)&&(b=new wb({code:a}),Rb(b))}return b}function Xb(a,b){if(a===b)return!0;var c=a.a===b.a;return a.wb===b.wb?c:Pb(a,b)===Sb&&c}function Yb(a,b){a=Ob(a);b=Ob(b);return Pb(a,b)}
+function Pb(a,b){var c=a.wb,d=b.wb,e=Lb(c,d);if(!e){var f=vb||window.proj4;if("function"==typeof f){var g=f.defs(c),h=f.defs(d);void 0!==g&&void 0!==h&&(g===h?Qb([b,a]):(e=f(d,c),Vb(b,a,e.forward,e.inverse)),e=Lb(c,d))}}e||(e=$b);return e}function $b(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 Sb(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 ac(a,b,c){return Yb(b,c)(a,void 0,a.length)}
+function bc(a,b,c){b=Yb(b,c);return jb(a,b)}function cc(){Qb(Bb);Qb(Hb);Tb()}cc();function dc(a,b){return a>b?1:a<b?-1:0}function ec(a,b){return 0<=a.indexOf(b)}function fc(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 gc(a,b){var c=Array.isArray(b)?b:[b],d=c.length;for(b=0;b<d;b++)a[a.length]=c[b]}
+function hc(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 jc(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 kc(a){var b=lc,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 mc(a,b){var c;return a.every(function(d,e){c=e;return!b(d,e,a)})?-1:c}
+function nc(a,b){var c=b||dc;return a.every(function(b,e){if(0===e)return!0;b=c(a[e-1],b);return!(0<b||0===b)})};function oc(a,b,c,d){return void 0!==d?(d[0]=a,d[1]=b,d[2]=c,d):[a,b,c]}function pc(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 qc(a){this.minZoom=void 0!==a.minZoom?a.minZoom:0;this.b=a.resolutions;oa(nc(this.b,function(a,b){return b-a}),17);if(!a.origins)for(var b=0,c=this.b.length-1;b<c;++b)if(!d)var d=this.b[b]/this.b[b+1];else if(this.b[b]/this.b[b+1]!==d){d=void 0;break}this.l=d;this.maxZoom=this.b.length-1;this.g=void 0!==a.origin?a.origin:null;this.c=null;void 0!==a.origins&&(this.c=a.origins,oa(this.c.length==this.b.length,20));d=a.extent;void 0===d||this.g||this.c||(this.g=$a(d));oa(!this.g&&this.c||this.g&&
+!this.c,18);this.i=null;void 0!==a.tileSizes&&(this.i=a.tileSizes,oa(this.i.length==this.b.length,19));this.j=void 0!==a.tileSize?a.tileSize:this.i?null:256;oa(!this.j&&this.i||this.j&&!this.i,22);this.o=void 0!==d?d:null;this.a=null;this.f=[0,0];void 0!==a.sizes?this.a=a.sizes.map(function(a){return new ja(Math.min(0,a[0]),Math.max(a[0]-1,-1),Math.min(0,a[1]),Math.max(a[1]-1,-1))},this):d&&rc(this,d)}var sc=[0,0,0];k=qc.prototype;
+k.Vf=function(a,b,c){a=tc(this,a,b);for(var d=a.fa,e=a.la;d<=e;++d)for(var f=a.ea,g=a.ka;f<=g;++f)c([b,d,f])};function uc(a,b,c,d,e){var f=null,g=b[0]-1;if(2===a.l){var h=b[1];var l=b[2]}else f=a.Ma(b,e);for(;g>=a.minZoom;){2===a.l?(h=Math.floor(h/2),l=Math.floor(l/2),b=ka(h,h,l,l,d)):b=tc(a,f,g,d);if(c.call(null,g,b))return!0;--g}return!1}k.G=function(){return this.o};k.mj=function(){return this.maxZoom};k.nj=function(){return this.minZoom};k.Ic=function(a){return this.g?this.g:this.c[a]};k.Ta=function(a){return this.b[a]};
+k.oj=function(){return this.b};function vc(a,b,c,d){if(b[0]<a.maxZoom){if(2===a.l)return a=2*b[1],b=2*b[2],ka(a,a+1,b,b+1,c);d=a.Ma(b,d);return tc(a,d,b[0]+1,c)}return null}function wc(a,b,c){var d=a.Ic(b),e=a.Ta(b);a=Ba(a.Za(b),a.f);return Na(d[0]+c.fa*a[0]*e,d[1]+c.ea*a[1]*e,d[0]+(c.la+1)*a[0]*e,d[1]+(c.ka+1)*a[1]*e,void 0)}function tc(a,b,c,d){xc(a,b[0],b[1],c,!1,sc);var e=sc[1],f=sc[2];xc(a,b[2],b[3],c,!0,sc);return ka(e,sc[1],f,sc[2],d)}
+function yc(a,b){var c=a.Ic(b[0]),d=a.Ta(b[0]);a=Ba(a.Za(b[0]),a.f);return[c[0]+(b[1]+.5)*a[0]*d,c[1]+(b[2]+.5)*a[1]*d]}k.Ma=function(a,b){var c=this.Ic(a[0]),d=this.Ta(a[0]),e=Ba(this.Za(a[0]),this.f),f=c[0]+a[1]*e[0]*d;a=c[1]+a[2]*e[1]*d;return Na(f,a,f+e[0]*d,a+e[1]*d,b)};
+k.Le=function(a,b,c){var d=a[0],e=a[1];a=this.Dc(b);var f=b/this.Ta(a),g=this.Ic(a),h=Ba(this.Za(a),this.f);d=f*Math.floor((d-g[0])/b+0)/h[0];b=f*Math.floor((e-g[1])/b+.5)/h[1];d=Math.floor(d);b=Math.floor(b);return oc(a,d,b,c)};function xc(a,b,c,d,e,f){var g=a.Ic(d),h=a.Ta(d);a=Ba(a.Za(d),a.f);b=Math.floor((b-g[0])/h+(e?.5:0))/a[0];c=Math.floor((c-g[1])/h+(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 oc(d,b,c,f)}
+k.jg=function(a,b,c){return xc(this,a[0],a[1],b,!1,c)};k.Za=function(a){return this.j?this.j:this.i[a]};k.Dc=function(a,b){return pa(fc(this.b,a,b||0),this.minZoom,this.maxZoom)};function rc(a,b){for(var c=a.b.length,d=Array(c),e=a.minZoom;e<c;++e)d[e]=tc(a,b,e);a.a=d};function zc(a){var b=a.f;b||(b=Ac(a),a.f=b);return b}function Bc(a){var b={};kb(b,void 0!==a?a:{});void 0===b.extent&&(b.extent=Ob("EPSG:3857").G());b.resolutions=Cc(b.extent,b.maxZoom,b.tileSize);delete b.maxZoom;return new qc(b)}function Cc(a,b,c){b=void 0!==b?b:42;var d=db(a);a=cb(a);c=Ba(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 Ac(a,b,c){a=Dc(a);b=Cc(a,b,c);return new qc({extent:a,origin:$a(a),resolutions:b,tileSize:c})}function Dc(a){a=Ob(a);var b=a.G();b||(a=180*ub.degrees/a.Bc(),b=Na(-a,-a,a,a));return b};function Ec(a){this.og=a.html}Ec.prototype.b=function(){return this.og};function Fc(a){function b(b){var c=a.listener,e=a.Ch||a.target;a.Eh&&Gc(a);return c.call(e,b)}return a.Dh=b}function Hc(a,b,c,d){for(var e,f=0,g=a.length;f<g;++f)if(e=a[f],e.listener===b&&e.Ch===c)return d&&(e.deleteIndex=f),e}function Ic(a,b){return(a=a.ab)?a[b]:void 0}function Jc(a){var b=a.ab;b||(b=a.ab={});return b}
+function Kc(a,b){var c=Ic(a,b);if(c){for(var d=0,e=c.length;d<e;++d)a.removeEventListener(b,c[d].Dh),lb(c[d]);c.length=0;if(c=a.ab)delete c[b],0===Object.keys(c).length&&delete a.ab}}function y(a,b,c,d,e){var f=Jc(a),g=f[b];g||(g=f[b]=[]);(f=Hc(g,c,d,!1))?e||(f.Eh=!1):(f={Ch:d,Eh:!!e,listener:c,target:a,type:b},a.addEventListener(b,Fc(f)),g.push(f));return f}function Lc(a,b,c,d){return y(a,b,c,d,!0)}function Mc(a,b,c,d){(a=Ic(a,b))&&(c=Hc(a,c,d,!0))&&Gc(c)}
+function Gc(a){if(a&&a.target){a.target.removeEventListener(a.type,a.Dh);var b=Ic(a.target,a.type);if(b){var c="deleteIndex"in a?a.deleteIndex:b.indexOf(a);-1!==c&&b.splice(c,1);0===b.length&&Kc(a.target,a.type)}lb(a)}}function Nc(a){var b=Jc(a),c;for(c in b)Kc(a,c)};function Oc(){}Oc.prototype.Ub=!1;function Pc(a){a.Ub||(a.Ub=!0,a.ia())}Oc.prototype.ia=ea;function Qc(a){this.type=a;this.target=null}Qc.prototype.preventDefault=Qc.prototype.stopPropagation=function(){this.sj=!0};function Rc(a){a.stopPropagation()};function Sc(){this.Wa={};this.qa={};this.oa={}}w(Sc,Oc);Sc.prototype.addEventListener=function(a,b){var c=this.oa[a];c||(c=this.oa[a]=[]);-1===c.indexOf(b)&&c.push(b)};
+Sc.prototype.b=function(a){var b="string"===typeof a?new Qc(a):a;a=b.type;b.target=this;var c=this.oa[a];if(c){a in this.qa||(this.qa[a]=0,this.Wa[a]=0);++this.qa[a];for(var d=0,e=c.length;d<e;++d)if(!1===c[d].call(this,b)||b.sj){var f=!1;break}--this.qa[a];if(0===this.qa[a]){b=this.Wa[a];for(delete this.Wa[a];b--;)this.removeEventListener(a,ea);delete this.qa[a]}return f}};Sc.prototype.ia=function(){Nc(this)};function Tc(a,b){return b?b in a.oa:0<Object.keys(a.oa).length}
+Sc.prototype.removeEventListener=function(a,b){var c=this.oa[a];c&&(b=c.indexOf(b),a in this.Wa?(c[b]=ea,++this.Wa[a]):(c.splice(b,1),0===c.length&&delete this.oa[a]))};function Uc(){Sc.call(this);this.g=0}w(Uc,Sc);k=Uc.prototype;k.u=function(){++this.g;this.b("change")};k.K=function(){return this.g};k.I=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]=Lc(this,a[f],b,c);return e}return Lc(this,a,b,c)};
+k.J=function(a,b,c){if(Array.isArray(a))for(var d=0,e=a.length;d<e;++d)Mc(this,a[d],b,c);else Mc(this,a,b,c)};function Vc(a){Uc.call(this);x(this);this.N={};void 0!==a&&this.H(a)}w(Vc,Uc);var Wc={};function Xc(a){return Wc.hasOwnProperty(a)?Wc[a]:Wc[a]="change:"+a}k=Vc.prototype;k.get=function(a){var b;this.N.hasOwnProperty(a)&&(b=this.N[a]);return b};k.P=function(){return Object.keys(this.N)};k.L=function(){return kb({},this.N)};function Yc(a,b,c){var d=Xc(b);a.b(new Zc(d,b,c));a.b(new Zc("propertychange",b,c))}k.set=function(a,b,c){c?this.N[a]=b:(c=this.N[a],this.N[a]=b,c!==b&&Yc(this,a,c))};
+k.H=function(a,b){for(var c in a)this.set(c,a[c],b)};k.R=function(a,b){if(a in this.N){var c=this.N[a];delete this.N[a];b||Yc(this,a,c)}};function Zc(a,b,c){Qc.call(this,a);this.key=b;this.oldValue=c}w(Zc,Qc);function B(a,b){Vc.call(this);this.c=!!(b||{}).unique;this.a=a?a:[];if(this.c)for(a=0,b=this.a.length;a<b;++a)$c(this,this.a[a],a);ad(this)}w(B,Vc);k=B.prototype;k.clear=function(){for(;0<this.kc();)this.pop()};k.qg=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){a=b?a.bind(b):a;b=this.a;for(var c=0,d=b.length;c<d;++c)a(b[c],c,b)};k.Xm=function(){return this.a};k.item=function(a){return this.a[a]};k.kc=function(){return this.get(bd)};
+k.Re=function(a,b){this.c&&$c(this,b);this.a.splice(a,0,b);ad(this);this.b(new cd("add",b))};k.pop=function(){return this.Wg(this.kc()-1)};k.push=function(a){this.c&&$c(this,a);var b=this.kc();this.Re(b,a);return this.kc()};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.Wg(d)};k.Wg=function(a){var b=this.a[a];this.a.splice(a,1);ad(this);this.b(new cd("remove",b));return b};
+k.rq=function(a,b){var c=this.kc();if(a<c)this.c&&$c(this,b,a),c=this.a[a],this.a[a]=b,this.b(new cd("remove",c)),this.b(new cd("add",b));else{for(;c<a;++c)this.Re(c,void 0);this.Re(a,b)}};function ad(a){a.set(bd,a.a.length)}function $c(a,b,c){for(var d=0,e=a.a.length;d<e;++d)if(a.a[d]===b&&d!==c)throw new ha(58);}var bd="length";function cd(a,b){Qc.call(this,a);this.element=b}w(cd,Qc);function dd(a,b,c){Qc.call(this,a);this.map=b;this.frameState=void 0!==c?c:null}w(dd,Qc);function ed(a,b,c,d,e){dd.call(this,a,b,e);this.originalEvent=c;this.pixel=b.ud(c);this.coordinate=b.Ra(this.pixel);this.dragging=void 0!==d?d:!1}w(ed,dd);ed.prototype.preventDefault=function(){dd.prototype.preventDefault.call(this);this.originalEvent.preventDefault()};ed.prototype.stopPropagation=function(){dd.prototype.stopPropagation.call(this);this.originalEvent.stopPropagation()};var fd=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function gd(a,b){var c,d,e=fd.length;for(d=0;d<e;++d)try{if(c=a.getContext(fd[d],b))return c}catch(f){}return null};var hd,id="undefined"!==typeof navigator?navigator.userAgent.toLowerCase():"",jd=-1!==id.indexOf("firefox"),kd=-1!==id.indexOf("safari")&&-1==id.indexOf("chrom"),ld=-1!==id.indexOf("webkit")&&-1==id.indexOf("edge"),md=-1!==id.indexOf("macintosh"),nd=window.devicePixelRatio||1,od=!1,pd=function(){if(!("HTMLCanvasElement"in window))return!1;try{var a=document.createElement("CANVAS").getContext("2d");return a?(void 0!==a.setLineDash&&(od=!0),!0):!1}catch(b){return!1}}(),qd="DeviceOrientationEvent"in
+window,rd="geolocation"in navigator,sd="ontouchstart"in window,td="PointerEvent"in window,ud=!!navigator.msPointerEnabled,vd=!1,wd,xd=[];if("WebGLRenderingContext"in window)try{var yd=gd(document.createElement("CANVAS"),{failIfMajorPerformanceCaveat:!0});yd&&(vd=!0,wd=yd.getParameter(yd.MAX_TEXTURE_SIZE),xd=yd.getSupportedExtensions())}catch(a){}hd=vd;da=xd;ba=wd;var zd={gr:"singleclick",Wq:"click",Xq:"dblclick",$q:"pointerdrag",cr:"pointermove",Zq:"pointerdown",fr:"pointerup",er:"pointerover",dr:"pointerout",ar:"pointerenter",br:"pointerleave",Yq:"pointercancel"};function Ad(a,b,c,d,e){ed.call(this,a,b,c.b,d,e);this.b=c}w(Ad,ed);function Bd(a,b){this.b=a;this.i=b};function Cd(a){Bd.call(this,a,{mousedown:this.Jm,mousemove:this.Km,mouseup:this.Nm,mouseover:this.Mm,mouseout:this.Lm});this.a=a.g;this.g=[]}w(Cd,Bd);function Dd(a,b){a=a.g;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 Ed(a){var b=Fd(a,a),c=b.preventDefault;b.preventDefault=function(){a.preventDefault();c()};b.pointerId=1;b.isPrimary=!0;b.pointerType="mouse";return b}k=Cd.prototype;
+k.Jm=function(a){if(!Dd(this,a)){(1).toString()in this.a&&this.cancel(a);var b=Ed(a);this.a[(1).toString()]=a;Gd(this.b,"pointerdown",b,a)}};k.Km=function(a){if(!Dd(this,a)){var b=Ed(a);Gd(this.b,"pointermove",b,a)}};k.Nm=function(a){if(!Dd(this,a)){var b=this.a[(1).toString()];b&&b.button===a.button&&(b=Ed(a),Gd(this.b,"pointerup",b,a),delete this.a[(1).toString()])}};k.Mm=function(a){if(!Dd(this,a)){var b=Ed(a);Hd(this.b,b,a)}};k.Lm=function(a){if(!Dd(this,a)){var b=Ed(a);Jd(this.b,b,a)}};
+k.cancel=function(a){var b=Ed(a);this.b.cancel(b,a);delete this.a[(1).toString()]};function Kd(a){Bd.call(this,a,{MSPointerDown:this.Sm,MSPointerMove:this.Tm,MSPointerUp:this.Wm,MSPointerOut:this.Um,MSPointerOver:this.Vm,MSPointerCancel:this.Rm,MSGotPointerCapture:this.Pm,MSLostPointerCapture:this.Qm});this.a=a.g;this.g=["","unavailable","touch","pen","mouse"]}w(Kd,Bd);function Ld(a,b){var c=b;"number"===typeof b.pointerType&&(c=Fd(b,b),c.pointerType=a.g[b.pointerType]);return c}k=Kd.prototype;
+k.Sm=function(a){this.a[a.pointerId.toString()]=a;var b=Ld(this,a);Gd(this.b,"pointerdown",b,a)};k.Tm=function(a){var b=Ld(this,a);Gd(this.b,"pointermove",b,a)};k.Wm=function(a){var b=Ld(this,a);Gd(this.b,"pointerup",b,a);delete this.a[a.pointerId.toString()]};k.Um=function(a){var b=Ld(this,a);Jd(this.b,b,a)};k.Vm=function(a){var b=Ld(this,a);Hd(this.b,b,a)};k.Rm=function(a){var b=Ld(this,a);this.b.cancel(b,a);delete this.a[a.pointerId.toString()]};
+k.Qm=function(a){this.b.b(new Md("lostpointercapture",a,a))};k.Pm=function(a){this.b.b(new Md("gotpointercapture",a,a))};function Nd(a){Bd.call(this,a,{pointerdown:this.Kp,pointermove:this.Lp,pointerup:this.Op,pointerout:this.Mp,pointerover:this.Np,pointercancel:this.Jp,gotpointercapture:this.Wl,lostpointercapture:this.Hm})}w(Nd,Bd);k=Nd.prototype;k.Kp=function(a){Od(this.b,a)};k.Lp=function(a){Od(this.b,a)};k.Op=function(a){Od(this.b,a)};k.Mp=function(a){Od(this.b,a)};k.Np=function(a){Od(this.b,a)};k.Jp=function(a){Od(this.b,a)};k.Hm=function(a){Od(this.b,a)};k.Wl=function(a){Od(this.b,a)};function Md(a,b,c){Qc.call(this,a);this.b=b;a=c?c:{};this.buttons=Pd(a);this.pressure=Qd(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],
+w(Md,Qc);function Pd(a){if(a.buttons||Rd)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 Qd(a,b){var c=0;a.pressure?c=a.pressure:c=b?.5:0;return c}var Rd=!1;try{Rd=1===(new MouseEvent("click",{buttons:1})).buttons}catch(a){};function Sd(a,b){Bd.call(this,a,{touchstart:this.Qq,touchmove:this.Pq,touchend:this.Oq,touchcancel:this.Nq});this.a=a.g;this.j=b;this.g=void 0;this.f=0;this.c=void 0}w(Sd,Bd);k=Sd.prototype;k.Ej=function(){this.f=0;this.c=void 0};
+function Td(a,b,c){b=Fd(b,c);b.pointerId=c.identifier+2;b.bubbles=!0;b.cancelable=!0;b.detail=a.f;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.g===c.identifier;b.pointerType="touch";b.clientX=c.clientX;b.clientY=c.clientY;b.screenX=c.screenX;b.screenY=c.screenY;return b}
+function Ud(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=Td(a,b,e[g]);h.preventDefault=d;c.call(a,b,h)}}
+k.Qq=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.Of(a,e[f])}b=a.changedTouches[0];c=Object.keys(this.a).length;if(0===c||1===c&&(1).toString()in this.a)this.g=b.identifier,void 0!==this.c&&clearTimeout(this.c);Vd(this,a);this.f++;Ud(this,a,this.Fp)};
+k.Fp=function(a,b){this.a[b.pointerId]={target:b.target,out:b,pj:b.target};var c=this.b;b.bubbles=!0;Gd(c,"pointerover",b,a);c=this.b;b.bubbles=!1;Gd(c,"pointerenter",b,a);Gd(this.b,"pointerdown",b,a)};k.Pq=function(a){a.preventDefault();Ud(this,a,this.Om)};
+k.Om=function(a,b){var c=this.a[b.pointerId];if(c){var d=c.out,e=c.pj;Gd(this.b,"pointermove",b,a);d&&e!==b.target&&(d.relatedTarget=b.target,b.relatedTarget=e,d.target=e,b.target?(Jd(this.b,d,a),Hd(this.b,b,a)):(b.target=e,b.relatedTarget=null,this.Of(a,b)));c.out=b;c.pj=b.target}};k.Oq=function(a){Vd(this,a);Ud(this,a,this.Rq)};
+k.Rq=function(a,b){Gd(this.b,"pointerup",b,a);this.b.out(b,a);Wd(this.b,b,a);delete this.a[b.pointerId];b.isPrimary&&(this.g=void 0,this.c=setTimeout(this.Ej.bind(this),200))};k.Nq=function(a){Ud(this,a,this.Of)};k.Of=function(a,b){this.b.cancel(b,a);this.b.out(b,a);Wd(this.b,b,a);delete this.a[b.pointerId];b.isPrimary&&(this.g=void 0,this.c=setTimeout(this.Ej.bind(this),200))};
+function Vd(a,b){var c=a.j.g;b=b.changedTouches[0];if(a.g===b.identifier){var d=[b.clientX,b.clientY];c.push(d);setTimeout(function(){var a=c.indexOf(d);-1<a&&c.splice(a,1)},2500)}};function Xd(a){Sc.call(this);this.f=a;this.g={};this.i={};this.a=[];td?Yd(this,new Nd(this)):ud?Yd(this,new Kd(this)):(a=new Cd(this),Yd(this,a),sd&&Yd(this,new Sd(this,a)));a=this.a.length;for(var b,c=0;c<a;c++)b=this.a[c],Zd(this,Object.keys(b.i))}w(Xd,Sc);function Yd(a,b){var c=Object.keys(b.i);c&&(c.forEach(function(a){var c=b.i[a];c&&(this.i[a]=c.bind(b))},a),a.a.push(b))}Xd.prototype.c=function(a){var b=this.i[a.type];b&&b(a)};
+function Zd(a,b){b.forEach(function(a){y(this.f,a,this.c,this)},a)}function $d(a,b){b.forEach(function(a){Mc(this.f,a,this.c,this)},a)}function Fd(a,b){for(var c={},d,e=0,f=ae.length;e<f;e++)d=ae[e][0],c[d]=a[d]||b[d]||ae[e][1];return c}function Wd(a,b,c){b.bubbles=!1;Gd(a,"pointerleave",b,c)}Xd.prototype.out=function(a,b){a.bubbles=!0;Gd(this,"pointerout",a,b)};Xd.prototype.cancel=function(a,b){Gd(this,"pointercancel",a,b)};
+function Jd(a,b,c){a.out(b,c);var d=b.target,e=b.relatedTarget;d&&e&&d.contains(e)||Wd(a,b,c)}function Hd(a,b,c){b.bubbles=!0;Gd(a,"pointerover",b,c);var d=b.target,e=b.relatedTarget;d&&e&&d.contains(e)||(b.bubbles=!1,Gd(a,"pointerenter",b,c))}function Gd(a,b,c,d){a.b(new Md(b,d,c))}function Od(a,b){a.b(new Md(b.type,b,b))}Xd.prototype.ia=function(){for(var a=this.a.length,b,c=0;c<a;c++)b=this.a[c],$d(this,Object.keys(b.i));Sc.prototype.ia.call(this)};
+var ae=[["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 be(a,b){Sc.call(this);this.g=a;this.j=0;this.l=!1;this.i=[];this.D=b?b*nd:nd;this.c=null;a=this.g.a;this.N=0;this.o={};this.f=new Xd(a);this.a=null;this.s=y(this.f,"pointerdown",this.pm,this);this.v=y(this.f,"pointermove",this.mq,this)}w(be,Sc);function ce(a,b){var c=new Ad("click",a.g,b);a.b(c);0!==a.j?(clearTimeout(a.j),a.j=0,c=new Ad("dblclick",a.g,b),a.b(c)):a.j=setTimeout(function(){this.j=0;var a=new Ad("singleclick",this.g,b);this.b(a)}.bind(a),250)}
+function de(a,b){"pointerup"==b.type||"pointercancel"==b.type?delete a.o[b.pointerId]:"pointerdown"==b.type&&(a.o[b.pointerId]=!0);a.N=Object.keys(a.o).length}k=be.prototype;k.ci=function(a){de(this,a);var b=new Ad("pointerup",this.g,a);this.b(b);b.sj||this.l||0!==a.button||ce(this,this.c);0===this.N&&(this.i.forEach(Gc),this.i.length=0,this.l=!1,this.c=null,Pc(this.a),this.a=null)};
+k.pm=function(a){de(this,a);var b=new Ad("pointerdown",this.g,a);this.b(b);this.c=a;0===this.i.length&&(this.a=new Xd(document),this.i.push(y(this.a,"pointermove",this.mn,this),y(this.a,"pointerup",this.ci,this),y(this.f,"pointercancel",this.ci,this)))};k.mn=function(a){if(fe(this,a)){this.l=!0;var b=new Ad("pointerdrag",this.g,a,this.l);this.b(b)}a.preventDefault()};k.mq=function(a){this.b(new Ad(a.type,this.g,a,!(!this.c||!fe(this,a))))};
+function fe(a,b){return Math.abs(b.clientX-a.c.clientX)>a.D||Math.abs(b.clientY-a.c.clientY)>a.D}k.ia=function(){this.v&&(Gc(this.v),this.v=null);this.s&&(Gc(this.s),this.s=null);this.i.forEach(Gc);this.i.length=0;this.a&&(Pc(this.a),this.a=null);this.f&&(Pc(this.f),this.f=null);Sc.prototype.ia.call(this)};function ge(a,b){this.s=a;this.c=b;this.b=[];this.g=[];this.a={}}ge.prototype.clear=function(){this.b.length=0;this.g.length=0;lb(this.a)};function he(a){var b=a.b,c=a.g,d=b[0];1==b.length?(b.length=0,c.length=0):(b[0]=b.pop(),c[0]=c.pop(),ie(a,0));b=a.c(d);delete a.a[b];return d}ge.prototype.i=function(a){oa(!(this.c(a)in this.a),31);var b=this.s(a);return Infinity!=b?(this.b.push(a),this.g.push(b),this.a[this.c(a)]=!0,je(this,0,this.b.length-1),!0):!1};
+function ie(a,b){for(var c=a.b,d=a.g,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;je(a,h,b)}function je(a,b,c){var d=a.b;a=a.g;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 ke(a){var b=a.s,c=a.b,d=a.g,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--)ie(a,b)};function le(a,b){ge.call(this,function(b){return a.apply(null,b)},function(a){return a[0].lb()});this.v=b;this.j=0;this.f={}}w(le,ge);le.prototype.i=function(a){var b=ge.prototype.i.call(this,a);b&&y(a[0],"change",this.l,this);return b};le.prototype.l=function(a){a=a.target;var b=a.getState();if(2===b||3===b||4===b||5===b)Mc(a,"change",this.l,this),a=a.lb(),a in this.f&&(delete this.f[a],--this.j),this.v()};
+function me(a,b,c){for(var d=0,e=!1,f,g,h;a.j<b&&d<c&&0<a.b.length;)g=he(a)[0],h=g.lb(),f=g.getState(),5===f?e=!0:0!==f||h in a.f||(a.f[h]=!0,++a.j,++d,g.load());0===d&&e&&a.v()};function ne(a){return function(b){if(b)return[pa(b[0],a[0],a[2]),pa(b[1],a[1],a[3])]}}function oe(a){return a};function pe(a){return function(b,c,d){if(void 0!==b)return b=fc(a,b,d),b=pa(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 qe(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 re(a){if(void 0!==a)return 0}function se(a,b){if(void 0!==a)return a+b}function ue(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=va(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 Ae(a,b){var c=b.Bd(),d=b.xa();b=d[0];d=d[1];var e=a[0]-b;a=a[1]-d;0===e&&0===a&&(e=1);var f=Math.sqrt(e*e+a*a);return[b+c*e/f,d+c*a/f]}function Be(a,b){var c=a[0];a=a[1];var d=b[0],e=b[1];b=d[0];d=d[1];var f=e[0];e=e[1];var g=f-b,h=e-d;c=0===g&&0===h?0:(g*(c-b)+h*(a-d))/(g*g+h*h||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 Ce(a,b,c){b=wa(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"+(0==b?"":" "+a.charAt(0>b?1:0))}function De(a,b,c){return a?b.replace("{x}",a[0].toFixed(c)).replace("{y}",a[1].toFixed(c)):""}function Ee(a,b){for(var c=!0,d=a.length-1;0<=d;--d)if(a[d]!=b[d]){c=!1;break}return c}
+function Fe(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 Ge(a,b){a[0]*=b;a[1]*=b}function He(a,b){var c=a[0]-b[0];a=a[1]-b[1];return c*c+a*a}function Ie(a,b){return Math.sqrt(He(a,b))}function Je(a,b){return He(a,Be(a,b))}function Ke(a,b){return De(a,"{x}, {y}",b)};function Me(a){return Math.pow(a,3)}function Oe(a){return 1-Me(1-a)}function Pe(a){return 3*a*a-2*a*a*a}function Qe(a){return a};function Re(){return!0}function Se(){return!1};function Te(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 Ue(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};var Ve=Array(6);function We(){return[1,0,0,1,0,0]}function Xe(a){return Ye(a,1,0,0,1,0,0)}function Ze(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 Ye(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 $e(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 af(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 bf(a,b){var c=Math.cos(b);b=Math.sin(b);Ze(a,Ye(Ve,c,b,-b,c,0,0))}function cf(a,b,c){return Ze(a,Ye(Ve,b,0,0,c,0,0))}function df(a,b,c){Ze(a,Ye(Ve,1,0,0,1,b,c))}function ef(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 ff(a){var b=a[0]*a[3]-a[1]*a[2];oa(0!==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 gf(){Vc.call(this);this.s=Da();this.v=-1;this.i={};this.l=this.f=0;this.O=We()}w(gf,Vc);k=gf.prototype;k.Ib=function(a,b){b=b?b:[NaN,NaN];this.Nb(a[0],a[1],b,Infinity);return b};k.Bb=function(a){return this.Zc(a[0],a[1])};k.Zc=Se;k.G=function(a){this.v!=this.g&&(this.s=this.Ae(this.s),this.v=this.g);var b=this.s;a?(a[0]=b[0],a[1]=b[1],a[2]=b[2],a[3]=b[3]):a=b;return a};k.Sb=function(a){return this.Wd(a*a)};
+k.mb=function(a,b){var c=this.O;a=Ob(a);var d="tile-pixels"==a.a?function(d,f,g){var e=a.G(),l=a.oe;e=db(l)/db(e);ef(c,l[0],l[3],e,-e,0,0,0);Te(d,0,d.length,g,c,f);return Yb(a,b)(d,f,g)}:Yb(a,b);this.Rc(d);return this};function hf(){gf.call(this);this.ja="XY";this.a=2;this.A=null}w(hf,gf);function jf(a){var b;"XY"==a?b=2:"XYZ"==a||"XYM"==a?b=3:"XYZM"==a&&(b=4);return b}k=hf.prototype;k.Zc=Se;k.Ae=function(a){return Qa(this.A,0,this.A.length,this.a,a)};k.fc=function(){return this.A.slice(0,this.a)};k.da=function(){return this.A};k.gc=function(){return this.A.slice(this.A.length-this.a)};k.ic=function(){return this.ja};
+k.Wd=function(a){this.l!=this.g&&(lb(this.i),this.f=0,this.l=this.g);if(0>a||0!==this.f&&a<=this.f)return this;var b=a.toString();if(this.i.hasOwnProperty(b))return this.i[b];var c=this.xd(a);if(c.da().length<this.A.length)return this.i[b]=c;this.f=a;return this};k.xd=function(){return this};k.pa=function(){return this.a};function kf(a,b,c){a.a=jf(b);a.ja=b;a.A=c}
+function lf(a,b,c,d){if(b)c=jf(b);else{for(b=0;b<d;++b){if(0===c.length){a.ja="XY";a.a=2;return}c=c[0]}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.Rc=function(a){this.A&&(a(this.A,this.A,this.a),this.u())};
+k.rotate=function(a,b){var c=this.da();if(c){var d=c.length,e=this.pa(),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.u()}};
+k.scale=function(a,b,c){var d=b;void 0===d&&(d=a);var e=c;e||(e=eb(this.G()));if(c=this.da()){b=c.length;var f=this.pa(),g=c?c:[],h=e[0];e=e[1];for(var 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.u()}};k.translate=function(a,b){var c=this.da();c&&(Ue(c,0,c.length,this.pa(),a,b,c),this.u())};function mf(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+=g*h-f*l;f=h;g=l}return e/2}function nf(a,b,c,d){var e=0,f;var g=0;for(f=c.length;g<f;++g){var h=c[g];e+=mf(a,b,h,d);b=h}return e};function of(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(0!==m||0!==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]=ya(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 pf(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=ua(f,g,h,l);f>e&&(e=f);f=h;g=l}return e}function qf(a,b,c,d,e){var f;var g=0;for(f=c.length;g<f;++g){var h=c[g];e=pf(a,b,h,d,e);b=h}return e}
+function tf(a,b,c,d,e,f,g,h,l,m,n){if(b==c)return m;if(0===e){var p=ua(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(of(a,r-d,r,d,g,h,q),p=ua(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&&(of(a,c-d,b,d,g,h,q),p=ua(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 uf(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=tf(a,b,r,d,e,f,g,h,l,m,n);b=r}return m};function vf(a,b){var c=0,d;var e=0;for(d=b.length;e<d;++e)a[c++]=b[e];return c}function wf(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 xf(a,b,c,d,e){e=e?e:[];var f=0,g;var h=0;for(g=c.length;h<g;++h)b=wf(a,b,c[h],d),e[f++]=b;e.length=f;return e};function yf(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 zf(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++]=yf(a,b,l,d,e[f]);b=l}e.length=f;return e}function Af(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++]=zf(a,b,l,d,e[f]);b=l[l.length-1]}e.length=f;return e};function Bf(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],v=a[q+1],z=a[p],A=a[p+1];for(n=q+d;n<p;n+=d){var E=sa(a[n],a[n+1],u,v,z,A);E>r&&(m=n,r=E)}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 Cf(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,v=f,z=g;if(b!=q){var A=u*Math.round(p[b]/u),E=u*Math.round(p[b+1]/u);b+=r;v[z++]=A;v[z++]=E;do{var S=u*Math.round(p[b]/u);g=u*Math.round(p[b+1]/u);b+=r;if(b==q){v[z++]=S;v[z++]=g;g=z;break a}}while(S==A&&g==E);for(;b<q;){var Ia=u*Math.round(p[b]/u);var ta=u*Math.round(p[b+1]/u);b+=r;if(Ia!=S||ta!=g){var la=S-A,ca=g-E,ia=Ia-A,xa=ta-E;la*xa==ca*ia&&(0>la&&ia<la||la==ia||0<la&&ia>la)&&(0>ca&&xa<ca||ca==
+xa||0<ca&&xa>ca)||(v[z++]=S,v[z++]=g,A=S,E=g);S=Ia;g=ta}}v[z++]=S;v[z++]=g}g=z}h.push(g);b=n}return g};function Df(a,b){hf.call(this);this.c=this.j=-1;this.na(a,b)}w(Df,hf);k=Df.prototype;k.clone=function(){var a=new Df(null);Ef(a,this.ja,this.A.slice());return a};k.Nb=function(a,b,c,d){if(d<Ha(this.G(),a,b))return d;this.c!=this.g&&(this.j=Math.sqrt(pf(this.A,0,this.A.length,this.a,0)),this.c=this.g);return tf(this.A,0,this.A.length,this.a,this.j,!0,a,b,c,d)};k.Vn=function(){return mf(this.A,0,this.A.length,this.a)};k.W=function(){return yf(this.A,0,this.A.length,this.a)};
+k.xd=function(a){var b=[];b.length=Bf(this.A,0,this.A.length,this.a,a,b,0);a=new Df(null);Ef(a,"XY",b);return a};k.S=function(){return"LinearRing"};k.$a=function(){};k.na=function(a,b){a?(lf(this,b,a,1),this.A||(this.A=[]),this.A.length=wf(this.A,0,a,this.a),this.u()):Ef(this,"XY",null)};function Ef(a,b,c){kf(a,b,c);a.u()};function C(a,b){hf.call(this);this.na(a,b)}w(C,hf);k=C.prototype;k.clone=function(){var a=new C(null);a.ba(this.ja,this.A.slice());return a};k.Nb=function(a,b,c,d){var e=this.A;a=ua(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.W=function(){return this.A?this.A.slice():[]};k.Ae=function(a){return Pa(this.A,a)};k.S=function(){return"Point"};k.$a=function(a){return Ka(a,this.A[0],this.A[1])};
+k.na=function(a,b){a?(lf(this,b,a,0),this.A||(this.A=[]),this.A.length=vf(this.A,a),this.u()):this.ba("XY",null)};k.ba=function(a,b){kf(this,a,b);this.u()};function Ff(a,b,c,d,e){return!Ua(e,function(e){return!Gf(a,b,c,d,e[0],e[1])})}function Gf(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 0!==g}function Hf(a,b,c,d,e,f){if(0===c.length||!Gf(a,b,c[0],d,e,f))return!1;var g;b=1;for(g=c.length;b<g;++b)if(Gf(a,c[b-1],c[b],d,e,f))return!1;return!0};function If(a,b,c,d,e,f,g){for(var h,l,m,n,p,q=e[f+1],r=[],u=0,v=c.length;u<v;++u){var z=c[u];m=a[z-d];p=a[z-d+1];for(h=b;h<z;h+=d){n=a[h];l=a[h+1];if(q<=p&&l<=q||p<=q&&q<=l)m=(q-p)/(l-p)*(n-m)+m,r.push(m);m=n;p=l}}u=NaN;v=-Infinity;r.sort(dc);m=r[0];h=1;for(l=r.length;h<l;++h)n=r[h],z=Math.abs(n-m),z>v&&(m=(m+n)/2,Hf(a,b,c,d,m,q)&&(u=m,v=z)),m=n;isNaN(u)&&(u=e[f]);return g?(g.push(u,q,v),g):[u,q,v]};function Jf(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 Kf(a,b,c,d,e){var f=Ra(Da(),a,b,c,d);return hb(e,f)?La(e,f)||f[0]>=e[0]&&f[2]<=e[2]||f[1]>=e[1]&&f[3]<=e[3]?!0:Jf(a,b,c,d,function(a,b){var c=!1,d=Ma(e,a),f=Ma(e,b);if(1===d||1===f)c=!0;else{var g=e[0],h=e[1],r=e[2],u=e[3],v=b[0];b=b[1];a=(b-a[1])/(v-a[0]);f&2&&!(d&2)&&(c=v-(b-u)/a,c=c>=g&&c<=r);c||!(f&4)||d&4||(c=b-(v-r)*a,c=c>=h&&c<=u);c||!(f&8)||d&8||(c=v-(b-h)/a,c=c>=g&&c<=r);c||!(f&16)||d&16||(c=b-(v-g)*a,c=c>=h&&c<=u)}return c}):!1}
+function Lf(a,b,c,d,e){var f=c[0];if(!(Kf(a,b,f,d,e)||Gf(a,b,f,d,e[0],e[1])||Gf(a,b,f,d,e[0],e[3])||Gf(a,b,f,d,e[2],e[1])||Gf(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(Ff(a,c[b-1],c[b],d,e))return!1;return!0};function Mf(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+=(h-f)*(l+g);f=h;g=l}return 0<e}function Nf(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=Mf(a,e,h,c);if(0===g){if(d&&e||!d&&!e)return!1}else if(d&&!e||!d&&e)return!1;e=h}return!0}
+function Of(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=Mf(a,b,h,d);if(0===g?e&&l||!e&&!l:e&&!l||!e&&l){l=a;for(var 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 Pf(a,b,c,d){var e=0,f;var g=0;for(f=b.length;g<f;++g)e=Of(a,e,b[g],c,d);return e};function D(a,b){hf.call(this);this.c=[];this.o=-1;this.D=null;this.T=this.C=this.B=-1;this.j=null;this.na(a,b)}w(D,hf);k=D.prototype;k.Hk=function(a){this.A?gc(this.A,a.da()):this.A=a.da().slice();this.c.push(this.A.length);this.u()};k.clone=function(){var a=new D(null);a.ba(this.ja,this.A.slice(),this.c.slice());return a};
+k.Nb=function(a,b,c,d){if(d<Ha(this.G(),a,b))return d;this.C!=this.g&&(this.B=Math.sqrt(qf(this.A,0,this.c,this.a,0)),this.C=this.g);return uf(this.A,0,this.c,this.a,this.B,!0,a,b,c,d)};k.Zc=function(a,b){return Hf(this.Xb(),0,this.c,this.a,a,b)};k.Yn=function(){return nf(this.Xb(),0,this.c,this.a)};k.W=function(a){if(void 0!==a){var b=this.Xb().slice();Of(b,0,this.c,this.a,a)}else b=this.A;return zf(b,0,this.c,this.a)};k.pb=function(){return this.c};
+k.Td=function(){if(this.o!=this.g){var a=eb(this.G());this.D=If(this.Xb(),0,this.c,this.a,a,0);this.o=this.g}return this.D};k.tl=function(){return new C(this.Td(),"XYM")};k.zl=function(){return this.c.length};k.Wh=function(a){if(0>a||this.c.length<=a)return null;var b=new Df(null);Ef(b,this.ja,this.A.slice(0===a?0:this.c[a-1],this.c[a]));return b};k.Ud=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 Df(null);Ef(l,a,b.slice(e,h));d.push(l);e=h}return d};
+k.Xb=function(){if(this.T!=this.g){var a=this.A;Nf(a,this.c,this.a)?this.j=a:(this.j=a.slice(),this.j.length=Of(this.j,0,this.c,this.a));this.T=this.g}return this.j};k.xd=function(a){var b=[],c=[];b.length=Cf(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.S=function(){return"Polygon"};k.$a=function(a){return Lf(this.Xb(),0,this.c,this.a,a)};
+k.na=function(a,b){a?(lf(this,b,a,2),this.A||(this.A=[]),a=xf(this.A,0,a,this.a,this.c),this.A.length=0===a.length?0:a[a.length-1],this.u()):this.ba("XY",null,this.c)};k.ba=function(a,b,c){kf(this,a,b);this.c=c;this.u()};function Qf(a,b,c,d){var e=d?d:32;d=[];var f;for(f=0;f<e;++f)gc(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 Rf(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 Sf(a,b,c){var d=b?b:32,e=a.pa();b=a.ja;var f=new D(null,b);d=e*(d+1);e=Array(d);for(var g=0;g<d;g++)e[g]=0;f.ba(b,e,[e.length]);Tf(f,a.xa(),a.Bd(),c);return f}function Tf(a,b,c,d){var e=a.da(),f=a.ja,g=a.pa(),h=a.pb(),l=e.length/g-1;d=d?d:0;for(var m,n,p=0;p<=l;++p)n=p*g,m=d+2*wa(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){Vc.call(this);a=kb({},a);this.f=[0,0];this.c=[];this.Ff=this.Ff.bind(this);this.v=Ub(a.projection);Vf(this,a)}w(F,Vc);
+function Vf(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){var g=b.resolutions;var h=g[d];var l=void 0!==g[e]?g[e]:g[g.length-1];e=pe(g)}else{h=Ub(b.projection);l=h.G();g=(l?Math.max(cb(l),db(l)):360*ub.degrees/h.Bc())/256/Math.pow(2,0);var m=g/Math.pow(2,28);h=b.maxResolution;void 0!==h?d=0:h=g/Math.pow(f,d);l=b.minResolution;void 0===l&&(l=void 0!==
+b.maxZoom?void 0!==b.maxResolution?h/Math.pow(f,e):g/Math.pow(f,e):m);e=d+Math.floor(Math.log(h/l)/Math.log(f));l=h/Math.pow(f,e-d);e=qe(f,h,e-d)}a.a=h;a.i=l;a.D=f;a.j=b.resolutions;a.s=d;(void 0!==b.enableRotation?b.enableRotation:1)?(d=b.constrainRotation,d=void 0===d||!0===d?we():!1===d?se:"number"===typeof d?ue(d):se):d=re;a.l={center:void 0!==b.extent?ne(b.extent):oe,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.s),a.j&&(c.resolution=pa(Number(a.Pa()||c.resolution),a.i,a.a)));c.rotation=void 0!==b.rotation?b.rotation:0;a.H(c);a.C=b}function $f(a,b){var c=kb({},a.C);void 0!==c.resolution?c.resolution=a.Pa():c.zoom=a.lg();c.center=a.xa();c.rotation=a.Sa();return kb({},c,b)}k=F.prototype;
+k.animate=function(a){var b=arguments.length;if(1<b&&"function"===typeof arguments[b-1]){var c=arguments[b-1];--b}if(ag(this)){for(var d=Date.now(),e=this.xa().slice(),f=this.Pa(),g=this.Sa(),h=[],l=0;l<b;++l){var m=arguments[l],n={start:d,complete:!1,anchor:m.anchor,duration:void 0!==m.duration?m.duration:1E3,easing:m.easing||Pe};m.center&&(n.ie=e,n.me=m.center,e=n.me);void 0!==m.zoom?(n.ke=f,n.kd=this.constrainResolution(this.a,m.zoom-this.s,0),f=n.kd):m.resolution&&(n.ke=f,n.kd=m.resolution,f=
+n.kd);void 0!==m.rotation&&(n.Df=g,n.ne=g+(wa(m.rotation-g+Math.PI,2*Math.PI)-Math.PI),g=n.ne);n.callback=c;n.ie&&n.me&&!Ee(n.ie,n.me)||n.ke!==n.kd||n.Df!==n.ne?d+=n.duration:n.complete=!0;h.push(n)}this.c.push(h);bg(this,0,1);this.Ff()}else b=arguments[b-1],b.center&&this.ub(b.center),void 0!==b.zoom&&this.Tj(b.zoom),void 0!==b.rotation&&this.ce(b.rotation),c&&c(!0)};k.Ac=function(){return 0<this.f[0]};k.Vh=function(){return 0<this.f[1]};
+k.rd=function(){bg(this,0,-this.f[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.Ff=function(){void 0!==this.o&&(cancelAnimationFrame(this.o),this.o=void 0);if(this.Ac()){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.ie){var l=h.ie[0],m=h.ie[1];this.set("center",[l+b*(h.me[0]-l),m+b*(h.me[1]-m)])}h.ke&&h.kd&&(l=1===b?h.kd:h.ke+b*(h.kd-h.ke),h.anchor&&this.set("center",cg(this,l,h.anchor)),this.set("resolution",
+l));void 0!==h.Df&&void 0!==h.ne&&(b=1===b?wa(h.ne+Math.PI,2*Math.PI)-Math.PI:h.Df+b*(h.ne-h.Df),h.anchor&&this.set("center",dg(this,b,h.anchor)),this.set("rotation",b));b=!0;if(!h.complete)break}}e&&(this.c[c]=null,bg(this,0,-1),(d=d[0].callback)&&d(!0))}this.c=this.c.filter(Boolean);b&&void 0===this.o&&(this.o=requestAnimationFrame(this.Ff))}};function dg(a,b,c){var d=a.xa();if(void 0!==d){var e=[d[0]-c[0],d[1]-c[1]];Fe(e,b-a.Sa());ze(e,c)}return e}
+function cg(a,b,c){var d,e=a.xa();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 eg(a){var b=[100,100];a='.ol-viewport[data-view="'+x(a)+'"]';if(a=document.querySelector(a))a=getComputedStyle(a),b[0]=parseInt(a.width,10),b[1]=parseInt(a.height,10);return b}k.Sc=function(a){return this.l.center(a)};k.constrainResolution=function(a,b,c){return this.l.resolution(a,b||0,c||0)};k.constrainRotation=function(a,b){return this.l.rotation(a,b||0)};k.xa=function(){return this.get("center")};
+k.qd=function(a){a=a||eg(this);var b=this.xa();oa(b,1);var c=this.Pa();oa(void 0!==c,2);var d=this.Sa();oa(void 0!==d,3);return fb(b,c,d,a)};k.sn=function(){return this.a};k.vn=function(){return this.i};k.tn=function(){return this.Me(this.i)};k.Cq=function(a){Vf(this,$f(this,{maxZoom:a}))};k.wn=function(){return this.Me(this.a)};k.Dq=function(a){Vf(this,$f(this,{minZoom:a}))};k.xn=function(){return this.v};k.Pa=function(){return this.get("resolution")};k.yn=function(){return this.j};
+k.Je=function(a,b){b=b||eg(this);return Math.max(cb(a)/b[0],db(a)/b[1])};function fg(a){var b=a.a,c=Math.log(b/a.i)/Math.log(2);return function(a){return b/Math.pow(2,a*c)}}k.Sa=function(){return this.get("rotation")};function gg(a){var b=a.a,c=Math.log(b/a.i)/Math.log(2);return function(a){return Math.log(b/a)/Math.log(2)/c}}k.getState=function(){var a=this.xa(),b=this.v,c=this.Pa(),d=this.Sa();return{center:a.slice(),projection:void 0!==b?b:null,resolution:c,rotation:d,zoom:this.lg()}};
+k.lg=function(){var a,b=this.Pa();void 0!==b&&(a=this.Me(b));return a};k.Me=function(a){var b=this.s||0,c;if(this.j){b=c=fc(this.j,a,1);var d=this.j[c];c=c==this.j.length-1?2:d/this.j[c+1]}else d=this.a,c=this.D;return b+Math.log(d/a)/Math.log(c)};k.$h=function(a){return this.constrainResolution(this.a,a-this.s,0)};
+k.Uf=function(a,b){b=b||{};var c=b.size;c||(c=eg(this));if(a instanceof hf)if("Circle"===a.S()){a=a.G();var d=Rf(a);d.rotate(this.Sa(),eb(a))}else d=a;else oa(Array.isArray(a),24),oa(!bb(a),25),d=Rf(a);var e=void 0!==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.s,0):h=0;var l=d.da(),m=this.Sa();a=Math.cos(-m);
+m=Math.sin(-m);var n=Infinity,p=Infinity,q=-Infinity,r=-Infinity;d=d.pa();for(var u=0,v=l.length;u<v;u+=d){var z=l[u]*a-l[u+1]*m,A=l[u]*m+l[u+1]*a;n=Math.min(n,z);p=Math.min(p,A);q=Math.max(q,z);r=Math.max(r,A)}c=this.Je([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:ea;void 0!==b.duration?
+this.animate({resolution:c,center:a,duration:b.duration,easing:b.easing},e):(this.gd(c),this.ub(a),setTimeout(e.bind(void 0,!0),0))};k.Nk=function(a,b,c){var d=this.Sa(),e=Math.cos(-d);d=Math.sin(-d);var f=a[0]*e-a[1]*d;a=a[1]*e+a[0]*d;var g=this.Pa();f+=(b[0]/2-c[0])*g;a+=(c[1]-b[1]/2)*g;d=-d;this.ub([f*e-a*d,a*e+f*d])};function ag(a){return!!a.xa()&&void 0!==a.Pa()}k.rotate=function(a,b){void 0!==b&&(b=dg(this,a,b),this.ub(b));this.ce(a)};k.ub=function(a){this.set("center",a);this.Ac()&&this.rd()};
+function bg(a,b,c){a.f[b]+=c;a.u()}k.gd=function(a){this.set("resolution",a);this.Ac()&&this.rd()};k.ce=function(a){this.set("rotation",a);this.Ac()&&this.rd()};k.Tj=function(a){this.gd(this.$h(a))};function hg(a,b){var c=document.createElement("CANVAS");a&&(c.width=a);b&&(c.height=b);return c.getContext("2d")}function ig(a,b){var c=b.parentNode;c&&c.replaceChild(a,b)}function jg(a){a&&a.parentNode&&a.parentNode.removeChild(a)};function kg(a){Vc.call(this);var b=kb({},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,Te:!0}}w(kg,Vc);k=kg.prototype;k.S=function(){return this.type};
+function lg(a){a.a.opacity=pa(a.nc(),0,1);a.a.Vj=a.hg();a.a.visible=a.Jb();a.a.extent=a.G();a.a.zIndex=a.Ba();a.a.maxResolution=a.lc();a.a.minResolution=Math.max(a.mc(),0);return a.a}k.G=function(){return this.get("extent")};k.lc=function(){return this.get("maxResolution")};k.mc=function(){return this.get("minResolution")};k.nc=function(){return this.get("opacity")};k.Jb=function(){return this.get("visible")};k.Ba=function(){return this.get("zIndex")};k.Fc=function(a){this.set("extent",a)};
+k.Mc=function(a){this.set("maxResolution",a)};k.Nc=function(a){this.set("minResolution",a)};k.Gc=function(a){this.set("opacity",a)};k.Hc=function(a){this.set("visible",a)};k.$b=function(a){this.set("zIndex",a)};function mg(a){var b=a||{};a=kb({},b);delete a.layers;b=b.layers;kg.call(this,a);this.i=[];this.c={};y(this,Xc(ng),this.im,this);b?Array.isArray(b)?b=new B(b.slice(),{unique:!0}):oa(b instanceof B,43):b=new B(void 0,{unique:!0});this.Qi(b)}w(mg,kg);k=mg.prototype;k.Pe=function(){this.u()};
+k.im=function(){this.i.forEach(Gc);this.i.length=0;var a=this.Cd();this.i.push(y(a,"add",this.hm,this),y(a,"remove",this.jm,this));for(var b in this.c)this.c[b].forEach(Gc);lb(this.c);a=a.a;var c;b=0;for(c=a.length;b<c;b++){var d=a[b];this.c[x(d).toString()]=[y(d,"propertychange",this.Pe,this),y(d,"change",this.Pe,this)]}this.u()};k.hm=function(a){a=a.element;var b=x(a).toString();this.c[b]=[y(a,"propertychange",this.Pe,this),y(a,"change",this.Pe,this)];this.u()};
+k.jm=function(a){a=x(a.element).toString();this.c[a].forEach(Gc);delete this.c[a];this.u()};k.Cd=function(){return this.get(ng)};k.Qi=function(a){this.set(ng,a)};
+k.dg=function(a){var b=void 0!==a?a:[],c=b.length;this.Cd().forEach(function(a){a.dg(b)});a=lg(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?gb(e.extent,a.extent):a.extent)}return b};k.hg=function(){return"ready"};var ng="layers";var og=[],pg=[];function qg(a,b){switch(a){case "MAP_RENDERER":a=og;a.push(b);break;case "LAYER_RENDERER":a=pg;a.push(b);break;default:throw Error("Unsupported plugin type: "+a);}}function rg(a){for(var b=0,c=a.length;b<c;++b)qg("LAYER_RENDERER",a[b])};function G(a){Vc.call(this);var b=sg(a);this.ob=void 0!==a.loadTilesWhileAnimating?a.loadTilesWhileAnimating:!1;this.sc=void 0!==a.loadTilesWhileInteracting?a.loadTilesWhileInteracting:!1;this.ra=void 0!==a.pixelRatio?a.pixelRatio:nd;this.Md=b.logos;this.V=function(){this.j=void 0;this.pq.call(this)}.bind(this);this.La=We();this.If=We();this.bb=0;this.D=this.C=this.B=this.f=this.c=null;this.a=document.createElement("DIV");this.a.className="ol-viewport"+(sd?" 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.o=document.createElement("DIV");this.o.className="ol-overlaycontainer";this.a.appendChild(this.o);this.v=document.createElement("DIV");this.v.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.v,c[d],Rc);this.a.appendChild(this.v);
+this.ca=new be(this,a.moveTolerance);for(var f in zd)y(this.ca,zd[f],this.bi,this);this.$=b.keyboardEventTarget;this.s=null;y(this.a,"wheel",this.yd,this);y(this.a,"mousewheel",this.yd,this);this.controls=b.controls||new B;this.interactions=b.interactions||new B;this.l=b.overlays;this.Fg={};this.pc=b.Im.create(this.a,this);this.T=null;this.Ea=[];this.ua=new le(this.Tl.bind(this),this.zm.bind(this));this.O={};y(this,Xc("layergroup"),this.fm,this);y(this,Xc("view"),this.Am,this);y(this,Xc("size"),this.um,
+this);y(this,Xc("target"),this.ym,this);this.H(b.values);this.controls.forEach(function(a){a.setMap(this)},this);y(this.controls,"add",function(a){a.element.setMap(this)},this);y(this.controls,"remove",function(a){a.element.setMap(null)},this);this.interactions.forEach(function(a){a.setMap(this)},this);y(this.interactions,"add",function(a){a.element.setMap(this)},this);y(this.interactions,"remove",function(a){a.element.setMap(null)},this);this.l.forEach(this.zh,this);y(this.l,"add",function(a){this.zh(a.element)},
+this);y(this.l,"remove",function(a){var b=a.element.id;void 0!==b&&delete this.Fg[b.toString()];a.element.setMap(null)},this)}w(G,Vc);k=G.prototype;k.Mf=function(a){this.controls.push(a)};k.Nf=function(a){this.interactions.push(a)};k.xe=function(a){this.hc().Cd().push(a)};k.ye=function(a){this.l.push(a)};k.zh=function(a){var b=a.id;void 0!==b&&(this.Fg[b.toString()]=a);a.setMap(this)};
+k.ia=function(){Pc(this.ca);Mc(this.a,"wheel",this.yd,this);Mc(this.a,"mousewheel",this.yd,this);void 0!==this.i&&(window.removeEventListener("resize",this.i,!1),this.i=void 0);this.j&&(cancelAnimationFrame(this.j),this.j=void 0);this.Ad(null);Vc.prototype.ia.call(this)};k.Tc=function(a,b,c){if(this.c)return a=this.Ra(a),c=void 0!==c?c:{},this.pc.wa(a,this.c,void 0!==c.hitTolerance?c.hitTolerance*this.c.pixelRatio:0,b,null,void 0!==c.layerFilter?c.layerFilter:Re,null)};
+k.Xf=function(a,b){var c=null;this.Tc(a,function(a){c||(c=[]);c.push(a)},b);return c};k.tg=function(a,b,c,d,e){if(this.c)return this.pc.Ti(a,this.c,b,void 0!==c?c:null,void 0!==d?d:Re,void 0!==e?e:null)};k.ng=function(a,b){if(!this.c)return!1;a=this.Ra(a);b=void 0!==b?b:{};return this.pc.Ui(a,this.c,void 0!==b.hitTolerance?b.hitTolerance*this.c.pixelRatio:0,void 0!==b.layerFilter?b.layerFilter:Re,null)};k.Sd=function(a){return this.Ra(this.ud(a))};
+k.ud=function(a){var b=this.a.getBoundingClientRect();a=a.changedTouches?a.changedTouches[0]:a;return[a.clientX-b.left,a.clientY-b.top]};k.Xd=function(){return this.get("target")};k.Cc=function(){var a=this.Xd();return void 0!==a?"string"===typeof a?document.getElementById(a):a:null};k.Ra=function(a){var b=this.c;return b?af(b.pixelToCoordinateTransform,a.slice()):null};k.Wf=function(){return this.controls};k.gg=function(){return this.l};
+k.fg=function(a){a=this.Fg[a.toString()];return void 0!==a?a:null};k.bg=function(){return this.interactions};k.hc=function(){return this.get("layergroup")};k.Xe=function(){return this.hc().Cd()};k.Ia=function(a){var b=this.c;return b?af(b.coordinateToPixelTransform,a.slice(0,2)):null};k.Ie=function(){return this.pc};k.Cb=function(){return this.get("size")};k.aa=function(){return this.get("view")};k.kg=function(){return this.a};
+k.Tl=function(a,b,c,d){var e=this.c;if(!(e&&b in e.wantedTiles&&e.wantedTiles[b][a.lb()]))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.yd=function(a,b){a=new ed(b||a.type,this,a);this.bi(a)};k.bi=function(a){if(this.c){this.T=a.coordinate;a.frameState=this.c;var b=this.interactions.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.sm=function(){var a=this.c,b=this.ua;if(0!==b.b.length){var c=16,d=c;if(a){var e=a.viewHints;e[0]&&(c=this.ob?8:0,d=2);e[1]&&(c=this.sc?8:0,d=2)}b.j<c&&(ke(b),me(b,c,d))}b=this.Ea;c=0;for(d=b.length;c<d;++c)b[c](this,a);b.length=0};k.um=function(){this.render()};
+k.ym=function(){var a;this.Xd()&&(a=this.Cc());if(this.s){for(var b=0,c=this.s.length;b<c;++b)Gc(this.s[b]);this.s=null}if(a){a.appendChild(this.a);var d=this.$?this.$:a;this.s=[y(d,"keydown",this.yd,this),y(d,"keypress",this.yd,this)];this.i||(this.i=this.Oc.bind(this),window.addEventListener("resize",this.i,!1))}else{a=this.pc;for(d in a.c)Pc(tg(a,d));jg(this.a);void 0!==this.i&&(window.removeEventListener("resize",this.i,!1),this.i=void 0)}this.Oc()};k.zm=function(){this.render()};k.ei=function(){this.render()};
+k.Am=function(){this.B&&(Gc(this.B),this.B=null);this.C&&(Gc(this.C),this.C=null);var a=this.aa();a&&(this.a.setAttribute("data-view",x(a)),this.B=y(a,"propertychange",this.ei,this),this.C=y(a,"change",this.ei,this));this.render()};k.fm=function(){this.D&&(this.D.forEach(Gc),this.D=null);var a=this.hc();a&&(this.D=[y(a,"propertychange",this.render,this),y(a,"change",this.render,this)]);this.render()};k.dh=function(){this.j&&cancelAnimationFrame(this.j);this.V()};
+k.render=function(){void 0===this.j&&(this.j=requestAnimationFrame(this.V))};k.Xg=function(a){return this.controls.remove(a)};k.Zg=function(a){return this.interactions.remove(a)};k.$g=function(a){return this.hc().Cd().remove(a)};k.ah=function(a){return this.l.remove(a)};
+k.pq=function(){var a=Date.now(),b,c=this.Cb(),d=this.aa(),e=Da(),f=this.c,g=null;if(void 0!==c&&0<c[0]&&0<c[1]&&d&&ag(d)){g=this.c?this.c.viewHints:void 0;void 0!==g?(g[0]=d.f[0],g[1]=d.f[1]):g=d.f.slice();var h=this.hc().dg(),l={};var m=0;for(b=h.length;m<b;++m)l[x(h[m].layer)]=h[m];m=d.getState();d=m.center;b=m.resolution/this.ra;d[0]=Math.round(d[0]/b)*b;d[1]=Math.round(d[1]/b)*b;g={animate:!1,coordinateToPixelTransform:this.La,extent:e,focus:this.T?this.T:d,index:this.bb++,layerStates:l,layerStatesArray:h,
+logos:kb({},this.Md),pixelRatio:this.ra,pixelToCoordinateTransform:this.If,postRenderFunctions:[],size:c,skippedFeatureUids:this.O,tileQueue:this.ua,time:a,usedTiles:{},viewState:m,viewHints:g,wantedTiles:{}}}g&&(g.extent=fb(m.center,m.resolution,m.rotation,g.size,e));this.c=g;this.pc.bh(g);g&&(g.animate&&this.render(),Array.prototype.push.apply(this.Ea,g.postRenderFunctions),!f||this.f&&(bb(this.f)||Sa(g.extent,this.f))||(this.b(new dd("movestart",this,f)),this.f=Oa(this.f)),!this.f||g.viewHints[0]||
+g.viewHints[1]||Sa(g.extent,this.f)||(this.b(new dd("moveend",this,g)),Ga(g.extent,this.f)));this.b(new dd("postrender",this,g));setTimeout(this.sm.bind(this),0)};k.zf=function(a){this.set("layergroup",a)};k.be=function(a){this.set("size",a)};k.Ad=function(a){this.set("target",a)};k.jh=function(a){this.set("view",a)};k.Uj=function(a){a=x(a).toString();this.O[a]=!0;this.render()};
+k.Oc=function(){var a=this.Cc();if(a){var b=getComputedStyle(a);this.be([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.be(void 0)};k.Zj=function(a){a=x(a).toString();delete this.O[a];this.render()};var ug=["canvas","webgl"];
+function sg(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[x(e).toString()]=e:e&&(oa("string"==typeof e.href,44),oa("string"==typeof e.src,45),d[e.src]=e.href)}e=a.layers instanceof mg?a.layers:new mg({layers:a.layers});c.layergroup=e;c.target=a.target;c.view=void 0!==a.view?a.view:new F;var f;void 0!==a.renderer?(Array.isArray(a.renderer)?f=a.renderer:"string"===typeof a.renderer?f=[a.renderer]:oa(!1,46),0<=f.indexOf("dom")&&(f=f.concat(ug))):f=ug;e=0;var g=
+f.length;a:for(;e<g;++e)for(var h=f[e],l=0,m=og.length;l<m;++l){var n=og[l];if(n.handles(h)){var p=n;break a}}if(!p)throw Error("Unable to create a map renderer for types: "+f.join(", "));if(void 0!==a.controls)if(Array.isArray(a.controls))var q=new B(a.controls.slice());else oa(a.controls instanceof B,47),q=a.controls;if(void 0!==a.interactions)if(Array.isArray(a.interactions))var r=new B(a.interactions.slice());else oa(a.interactions instanceof B,48),r=a.interactions;void 0!==a.overlays?Array.isArray(a.overlays)?
+a=new B(a.overlays.slice()):(oa(a.overlays instanceof B,49),a=a.overlays):a=new B;return{controls:q,interactions:r,keyboardEventTarget:b,logos:d,overlays:a,Im:p,values:c}};function vg(a){Vc.call(this);this.element=a.element?a.element:null;this.a=this.T=null;this.s=[];this.render=a.render?a.render:ea;a.target&&this.i(a.target)}w(vg,Vc);vg.prototype.ia=function(){jg(this.element);Vc.prototype.ia.call(this)};vg.prototype.f=function(){return this.a};
+vg.prototype.setMap=function(a){this.a&&jg(this.element);for(var b=0,c=this.s.length;b<c;++b)Gc(this.s[b]);this.s.length=0;if(this.a=a)(this.T?this.T:a.v).appendChild(this.element),this.render!==ea&&this.s.push(y(a,"postrender",this.render,this)),a.render()};vg.prototype.i=function(a){this.T="string"===typeof a?document.getElementById(a):a};var wg=function(){var a,b={};return function(c){a||(a=document.createElement("div").style);if(!(c in b)){a.font=c;var d=a.fontFamily;a.font="";if(!d)return null;b[c]=d.split(/,\s?/)}return b[c]}}();function xg(a){var b=kb({},a);delete b.source;kg.call(this,b);this.o=this.v=this.s=null;a.map&&this.setMap(a.map);y(this,Xc("source"),this.wm,this);this.hd(a.source?a.source:null)}w(xg,kg);function yg(a,b){return a.visible&&b>=a.minResolution&&b<a.maxResolution}k=xg.prototype;k.dg=function(a){a=a?a:[];a.push(lg(this));return a};k.ha=function(){return this.get("source")||null};k.hg=function(){var a=this.ha();return a?a.getState():"undefined"};k.yo=function(){this.u()};
+k.wm=function(){this.o&&(Gc(this.o),this.o=null);var a=this.ha();a&&(this.o=y(a,"change",this.yo,this));this.u()};k.setMap=function(a){this.s&&(Gc(this.s),this.s=null);a||this.u();this.v&&(Gc(this.v),this.v=null);a&&(this.s=y(a,"precompose",function(a){var b=lg(this);b.Te=!1;b.zIndex=Infinity;a.frameState.layerStatesArray.push(b);a.frameState.layerStates[x(this)]=b},this),this.v=y(this,"change",a.render,a),this.u())};k.hd=function(a){this.set("source",a)};function zg(a){a=a?a:{};this.v=document.createElement("UL");this.l=document.createElement("LI");this.v.appendChild(this.l);this.l.style.display="none";this.c=void 0!==a.collapsed?a.collapsed:!0;this.j=void 0!==a.collapsible?a.collapsible:!0;this.j||(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.o=document.createElement("span"),this.o.textContent=d):this.o=
+d;d=void 0!==a.label?a.label:"i";"string"===typeof d?(this.D=document.createElement("span"),this.D.textContent=d):this.D=d;var e=this.j&&!this.c?this.o:this.D;d=document.createElement("button");d.setAttribute("type","button");d.title=c;d.appendChild(e);y(d,"click",this.Bn,this);c=document.createElement("div");c.className=b+" ol-unselectable ol-control"+(this.c&&this.j?" ol-collapsed":"")+(this.j?"":" ol-uncollapsible");c.appendChild(this.v);c.appendChild(d);vg.call(this,{element:c,render:a.render?
+a.render:Ag,target:a.target});this.B=[];this.C=!0;this.O={}}w(zg,vg);
+function Ag(a){if(a=a.frameState){for(var b={},c=[],d=a.layerStatesArray,e=a.viewState.resolution,f=0,g=d.length;f<g;++f){var h=d[f];if(yg(h,e)&&(h=h.layer.ha())&&(h=h.C)&&(h=h(a)))if(Array.isArray(h))for(var l=0,m=h.length;l<m;++l)h[l]in b||(c.push(h[l]),b[h[l]]=!0);else h in b||(c.push(h),b[h]=!0)}if(!jc(c,this.B)){for(;this.v.lastChild!==this.l;)this.v.removeChild(this.v.lastChild);b=0;for(d=c.length;b<d;++b)e=document.createElement("LI"),e.innerHTML=c[b],this.v.appendChild(e);0===c.length&&0<
+this.B.length?this.element.classList.add("ol-logo-only"):0===this.B.length&&0<c.length&&this.element.classList.remove("ol-logo-only");b=0<c.length||!nb(a.logos);this.C!=b&&(this.element.style.display=b?"":"none",this.C=b);this.B=c;a=a.logos;c=this.O;for(p in c)p in a||(jg(c[p]),delete c[p]);for(var n in a)if(d=a[n],d instanceof HTMLElement&&(this.l.appendChild(d),c[n]=d),!(n in c)){var p=new Image;p.src=n;""===d?b=p:(b=document.createElement("a"),b.href=d,b.appendChild(p));this.l.appendChild(b);c[n]=
+b}this.l.style.display=nb(a)?"none":""}}else this.C&&(this.element.style.display="none",this.C=!1)}k=zg.prototype;k.Bn=function(a){a.preventDefault();Bg(this)};function Bg(a){a.element.classList.toggle("ol-collapsed");a.c?ig(a.o,a.D):ig(a.D,a.o);a.c=!a.c}k.An=function(){return this.j};k.Dn=function(a){this.j!==a&&(this.j=a,this.element.classList.toggle("ol-uncollapsible"),!a&&this.c&&Bg(this))};k.Cn=function(a){this.j&&this.c!==a&&Bg(this)};k.zn=function(){return this.c};function Cg(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",Cg.prototype.D,this);d=document.createElement("div");
+d.className=b+" ol-unselectable ol-control";d.appendChild(c);b=a.render?a.render:Dg;this.l=a.resetNorth?a.resetNorth:void 0;vg.call(this,{element:d,render:b,target:a.target});this.v=void 0!==a.duration?a.duration:250;this.j=void 0!==a.autoHide?a.autoHide:!0;this.o=void 0;this.j&&this.element.classList.add("ol-hidden")}w(Cg,vg);Cg.prototype.D=function(a){a.preventDefault();void 0!==this.l?this.l():(a=this.a.aa())&&void 0!==a.Sa()&&(0<this.v?a.animate({rotation:0,duration:this.v,easing:Oe}):a.ce(0))};
+function Dg(a){if(a=a.frameState){a=a.viewState.rotation;if(a!=this.o){var b="rotate("+a+"rad)";if(this.j){var c=this.element.classList.contains("ol-hidden");c||0!==a?c&&0!==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.o=a}};function Eg(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",Eg.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",Eg.prototype.j.bind(this,-c));c=document.createElement("div");c.className=b+" ol-unselectable ol-control";c.appendChild(h);c.appendChild(d);vg.call(this,{element:c,target:a.target});this.c=void 0!==a.duration?a.duration:250}w(Eg,vg);
+Eg.prototype.j=function(a,b){b.preventDefault();if(b=this.a.aa()){var c=b.Pa();c&&(a=b.constrainResolution(c,a),0<this.c?(b.Ac()&&b.rd(),b.animate({resolution:a,duration:this.c,easing:Oe})):b.gd(a))}};function Fg(a){a=a?a:{};var b=new B;(void 0!==a.zoom?a.zoom:1)&&b.push(new Eg(a.zoomOptions));(void 0!==a.rotate?a.rotate:1)&&b.push(new Cg(a.rotateOptions));(void 0!==a.attribution?a.attribution:1)&&b.push(new zg(a.attributionOptions));return b};function Gg(a,b,c){this.i=a;this.c=b;this.f=c;this.b=[];this.a=this.g=0}function Hg(a){a.b.length=0;a.g=0;a.a=0}function Ig(a){if(6>a.b.length)return!1;var b=Date.now()-a.f,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.g=Math.atan2(c,e);a.a=Math.sqrt(e*e+c*c)/b;return a.a>a.c};function Jg(a){Vc.call(this);this.v=null;this.Ha(!0);this.handleEvent=a.handleEvent}w(Jg,Vc);Jg.prototype.c=function(){return this.get("active")};Jg.prototype.i=function(){return this.v};Jg.prototype.Ha=function(a){this.set("active",a)};Jg.prototype.setMap=function(a){this.v=a};function Kg(a,b,c,d){if(void 0!==b){var e=a.Sa(),f=a.xa();void 0!==e&&f&&0<d?a.animate({rotation:b,anchor:c,duration:d,easing:Oe}):a.rotate(b,c)}}
+function Lg(a,b,c,d){var e=a.Pa();b=a.constrainResolution(e,b,0);if(void 0!==b){var f=a.j;b=pa(b,a.i||f[f.length-1],a.a||f[0])}c&&void 0!==b&&b!==e&&(f=a.xa(),c=cg(a,b,c),c=a.Sc(c),c=[(b*f[0]-e*c[0])/(b-e),(b*f[1]-e*c[1])/(b-e)]);Tg(a,b,c,d)}function Tg(a,b,c,d){if(b){var e=a.Pa(),f=a.xa();void 0!==e&&f&&b!==e&&d?a.animate({resolution:b,anchor:c,duration:d,easing:Oe}):(c&&(c=cg(a,b,c),a.ub(c)),a.gd(b))}};function Ug(a){a=a?a:{};this.a=a.delta?a.delta:1;Jg.call(this,{handleEvent:Vg});this.f=void 0!==a.duration?a.duration:250}w(Ug,Jg);function Vg(a){var b=!1,c=a.originalEvent;if("dblclick"==a.type){b=a.coordinate;c=c.shiftKey?-this.a:this.a;var d=a.map.aa();Lg(d,c,b,this.f);a.preventDefault();b=!0}return!b};function Wg(a){a=a.originalEvent;return a.altKey&&!(a.metaKey||a.ctrlKey)&&!a.shiftKey}function Xg(a){a=a.originalEvent;return a.altKey&&!(a.metaKey||a.ctrlKey)&&a.shiftKey}function Yg(a){a=a.originalEvent;return 0==a.button&&!(ld&&md&&a.ctrlKey)}function Zg(a){return"pointermove"==a.type}function $g(a){return"singleclick"==a.type}function ah(a){a=a.originalEvent;return!a.altKey&&!(a.metaKey||a.ctrlKey)&&!a.shiftKey}
+function bh(a){a=a.originalEvent;return!a.altKey&&!(a.metaKey||a.ctrlKey)&&a.shiftKey}function ch(a){a=a.originalEvent.target.tagName;return"INPUT"!==a&&"SELECT"!==a&&"TEXTAREA"!==a}function dh(a){oa(a.b,56);return"mouse"==a.b.pointerType}function eh(a){a=a.b;return a.isPrimary&&0===a.button};function fh(a){a=a?a:{};Jg.call(this,{handleEvent:a.handleEvent?a.handleEvent:gh});this.ck=a.handleDownEvent?a.handleDownEvent:Se;this.Ek=a.handleDragEvent?a.handleDragEvent:ea;this.Kk=a.handleMoveEvent?a.handleMoveEvent:ea;this.Lk=a.handleUpEvent?a.handleUpEvent:Se;this.D=!1;this.$={};this.l=[]}w(fh,Jg);function hh(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 gh(a){if(!(a instanceof Ad))return!0;var b=!1,c=a.type;if("pointerdown"===c||"pointerdrag"===c||"pointerup"===c){c=a.b;var d=c.pointerId.toString();"pointerup"==a.type?delete this.$[d]:"pointerdown"==a.type?this.$[d]=c:d in this.$&&(this.$[d]=c);this.l=mb(this.$)}this.D?"pointerdrag"==a.type?this.Ek(a):"pointerup"==a.type&&(this.D=this.Lk(a)&&0<this.l.length):"pointerdown"==a.type?(this.D=a=this.ck(a),b=this.jd(a)):"pointermove"==a.type&&this.Kk(a);return!b}fh.prototype.jd=function(a){return a};function ih(a){fh.call(this,{handleDownEvent:jh,handleDragEvent:kh,handleUpEvent:lh});a=a?a:{};this.a=a.kinetic;this.f=null;this.o=a.condition?a.condition:ah;this.j=!1}w(ih,fh);function kh(a){var b=this.l,c=hh(b);if(b.length==this.s){if(this.a&&this.a.b.push(c[0],c[1],Date.now()),this.f){var d=this.f[0]-c[0],e=c[1]-this.f[1];a=a.map.aa();var f=a.getState();d=[d,e];Ge(d,f.resolution);Fe(d,f.rotation);ze(d,f.center);d=a.Sc(d);a.ub(d)}}else this.a&&Hg(this.a);this.f=c;this.s=b.length}
+function lh(a){var b=a.map;a=b.aa();if(0===this.l.length){if(!this.j&&this.a&&Ig(this.a)){var c=this.a;c=(c.c-c.a)/c.i;var d=this.a.g,e=a.xa();e=b.Ia(e);b=b.Ra([e[0]-c*Math.cos(d),e[1]-c*Math.sin(d)]);a.animate({center:a.Sc(b),duration:500,easing:Oe})}bg(a,1,-1);return!1}this.a&&Hg(this.a);this.f=null;return!0}
+function jh(a){if(0<this.l.length&&this.o(a)){var b=a.map.aa();this.f=null;this.D||bg(b,1,1);b.Ac()&&b.ub(a.frameState.viewState.center);this.a&&Hg(this.a);this.j=1<this.l.length;return!0}return!1}ih.prototype.jd=Se;function mh(a){a=a?a:{};fh.call(this,{handleDownEvent:nh,handleDragEvent:oh,handleUpEvent:ph});this.f=a.condition?a.condition:Xg;this.a=void 0;this.j=void 0!==a.duration?a.duration:250}w(mh,fh);function oh(a){if(dh(a)){var b=a.map,c=b.aa();if(c.l.rotation!==re){b=b.Cb();a=a.pixel;a=Math.atan2(b[1]/2-a[1],a[0]-b[0]/2);if(void 0!==this.a){b=a-this.a;var d=c.Sa();Kg(c,d-b)}this.a=a}}}
+function ph(a){if(!dh(a))return!0;a=a.map.aa();bg(a,1,-1);var b=a.Sa(),c=this.j;b=a.constrainRotation(b,0);Kg(a,b,void 0,c);return!1}function nh(a){return dh(a)&&Yg(a)&&this.f(a)?(bg(a.map.aa(),1,1),this.a=void 0,!0):!1}mh.prototype.jd=Se;function qh(a){this.Uc=null;this.a=document.createElement("div");this.a.style.position="absolute";this.a.className="ol-box "+a;this.g=this.c=this.b=null}w(qh,Oc);qh.prototype.ia=function(){this.setMap(null)};function rh(a){var b=a.c,c=a.g;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"}
+qh.prototype.setMap=function(a){if(this.b){this.b.o.removeChild(this.a);var b=this.a.style;b.left=b.top=b.width=b.height="inherit"}(this.b=a)&&this.b.o.appendChild(this.a)};function sh(a){var b=a.c,c=a.g;b=[b,[b[0],c[1]],c,[c[0],b[1]]].map(a.b.Ra,a.b);b[4]=b[0].slice();a.Uc?a.Uc.na([b]):a.Uc=new D([b])}qh.prototype.U=function(){return this.Uc};function th(a){fh.call(this,{handleDownEvent:uh,handleDragEvent:vh,handleUpEvent:wh});a=a?a:{};this.a=new qh(a.className||"ol-dragbox");this.o=void 0!==a.minArea?a.minArea:64;this.f=null;this.C=a.condition?a.condition:Re;this.s=a.boxEndCondition?a.boxEndCondition:xh}w(th,fh);function xh(a,b,c){a=c[0]-b[0];b=c[1]-b[1];return a*a+b*b>=this.o}function vh(a){if(dh(a)){var b=this.a,c=a.pixel;b.c=this.f;b.g=c;sh(b);rh(b);this.b(new yh(zh,a.coordinate,a))}}th.prototype.U=function(){return this.a.U()};
+th.prototype.j=ea;function wh(a){if(!dh(a))return!0;this.a.setMap(null);this.s(a,this.f,a.pixel)&&(this.j(a),this.b(new yh(Ah,a.coordinate,a)));return!1}function uh(a){if(dh(a)&&Yg(a)&&this.C(a)){this.f=a.pixel;this.a.setMap(a.map);var b=this.a,c=this.f;b.c=this.f;b.g=c;sh(b);rh(b);this.b(new yh(Bh,a.coordinate,a));return!0}return!1}var Bh="boxstart",zh="boxdrag",Ah="boxend";function yh(a,b,c){Qc.call(this,a);this.coordinate=b;this.mapBrowserEvent=c}w(yh,Qc);function Ch(a){a=a?a:{};var b=a.condition?a.condition:bh;this.B=void 0!==a.duration?a.duration:200;this.T=void 0!==a.out?a.out:!1;th.call(this,{condition:b,className:a.className||"ol-dragzoom"})}w(Ch,th);
+Ch.prototype.j=function(){var a=this.v,b=a.aa(),c=a.Cb(),d=this.U().G();if(this.T){var e=b.qd(c);d=[a.Ia(Wa(d)),a.Ia(Za(d))];a=Oa(void 0);var f;var g=0;for(f=d.length;g<f;++g)Ea(a,d[g]);d=b.Je(a,c);ib(e,1/d);d=e}c=b.constrainResolution(b.Je(d,c));e=eb(d);e=b.Sc(e);b.animate({resolution:c,center:e,duration:this.B,easing:Oe})};function Dh(a){Jg.call(this,{handleEvent:Eh});a=a||{};this.a=function(a){return ah(a)&&ch(a)};this.f=void 0!==a.condition?a.condition:this.a;this.j=void 0!==a.duration?a.duration:100;this.l=void 0!==a.pixelDelta?a.pixelDelta:128}w(Dh,Jg);
+function Eh(a){var b=!1;if("keydown"==a.type){var c=a.originalEvent.keyCode;if(this.f(a)&&(40==c||37==c||39==c||38==c)){b=a.map.aa();var d=b.Pa()*this.l,e=0,f=0;40==c?f=-d:37==c?e=-d:39==c?e=d:f=d;d=[e,f];Fe(d,b.Sa());c=this.j;if(e=b.xa())d=b.Sc([e[0]+d[0],e[1]+d[1]]),c?b.animate({duration:c,easing:Qe,center:d}):b.ub(d);a.preventDefault();b=!0}}return!b};function Fh(a){Jg.call(this,{handleEvent:Gh});a=a?a:{};this.f=a.condition?a.condition:ch;this.a=a.delta?a.delta:1;this.j=void 0!==a.duration?a.duration:100}w(Fh,Jg);function Gh(a){var b=!1;if("keydown"==a.type||"keypress"==a.type){var c=a.originalEvent.charCode;!this.f(a)||43!=c&&45!=c||(b=43==c?this.a:-this.a,c=a.map.aa(),Lg(c,b,void 0,this.j),a.preventDefault(),b=!0)}return!b};function Hh(a){Jg.call(this,{handleEvent:Ih});a=a||{};this.j=0;this.D=void 0!==a.duration?a.duration:250;this.$=void 0!==a.timeout?a.timeout:80;this.C=void 0!==a.useAnchor?a.useAnchor:!0;this.O=a.constrainResolution||!1;this.a=null;this.s=this.l=this.o=this.f=void 0}w(Hh,Jg);
+function Ih(a){var b=a.type;if("wheel"!==b&&"mousewheel"!==b)return!0;a.preventDefault();b=a.map;var c=a.originalEvent;this.C&&(this.a=a.coordinate);if("wheel"==a.type){var d=c.deltaY;jd&&c.deltaMode===WheelEvent.DOM_DELTA_PIXEL&&(d/=nd);c.deltaMode===WheelEvent.DOM_DELTA_LINE&&(d*=40)}else"mousewheel"==a.type&&(d=-c.wheelDeltaY,kd&&(d/=3));if(0===d)return!1;a=Date.now();void 0===this.f&&(this.f=a);if(!this.l||400<a-this.f)this.l=4>Math.abs(d)?Ph:Qh;if(this.l===Ph){b=b.aa();this.s?clearTimeout(this.s):
+bg(b,1,1);this.s=setTimeout(this.B.bind(this),400);c=b.Pa()*Math.pow(2,d/300);var e=b.i,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=cg(b,c,this.a);b.ub(b.Sc(h))}b.gd(c);0===g&&this.O&&b.animate({resolution:b.constrainResolution(c,0<d?-1:1),easing:Oe,anchor:this.a,duration:this.D});0<g?b.animate({resolution:e,easing:Oe,anchor:this.a,duration:500}):0>g&&b.animate({resolution:f,easing:Oe,anchor:this.a,duration:500});this.f=a;return!1}this.j+=d;d=Math.max(this.$-
+(a-this.f),0);clearTimeout(this.o);this.o=setTimeout(this.T.bind(this,b),d);return!1}Hh.prototype.B=function(){this.s=void 0;bg(this.v.aa(),1,-1)};Hh.prototype.T=function(a){a=a.aa();a.Ac()&&a.rd();Lg(a,-pa(this.j,-1,1),this.a,this.D);this.l=void 0;this.j=0;this.a=null;this.o=this.f=void 0};Hh.prototype.V=function(a){this.C=a;a||(this.a=null)};var Ph="trackpad",Qh="wheel";function Rh(a){fh.call(this,{handleDownEvent:Sh,handleDragEvent:Th,handleUpEvent:Uh});a=a||{};this.f=null;this.j=void 0;this.a=!1;this.s=0;this.C=void 0!==a.threshold?a.threshold:.3;this.o=void 0!==a.duration?a.duration:250}w(Rh,fh);
+function Th(a){var b=0,c=this.l[0],d=this.l[1];c=Math.atan2(d.clientY-c.clientY,d.clientX-c.clientX);void 0!==this.j&&(b=c-this.j,this.s+=b,!this.a&&Math.abs(this.s)>this.C&&(this.a=!0));this.j=c;a=a.map;c=a.aa();if(c.l.rotation!==re){d=a.a.getBoundingClientRect();var e=hh(this.l);e[0]-=d.left;e[1]-=d.top;this.f=a.Ra(e);this.a&&(d=c.Sa(),a.render(),Kg(c,d+b,this.f))}}
+function Uh(a){if(2>this.l.length){a=a.map.aa();bg(a,1,-1);if(this.a){var b=a.Sa(),c=this.f,d=this.o;b=a.constrainRotation(b,0);Kg(a,b,c,d)}return!1}return!0}function Sh(a){return 2<=this.l.length?(a=a.map,this.f=null,this.j=void 0,this.a=!1,this.s=0,this.D||bg(a.aa(),1,1),!0):!1}Rh.prototype.jd=Se;function Vh(a){fh.call(this,{handleDownEvent:Wh,handleDragEvent:Xh,handleUpEvent:Yh});a=a?a:{};this.s=a.constrainResolution||!1;this.f=null;this.o=void 0!==a.duration?a.duration:400;this.a=void 0;this.j=1}w(Vh,fh);
+function Xh(a){var b=1,c=this.l[0],d=this.l[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;e=a.aa();d=e.Pa();var f=e.a,g=e.i;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=hh(this.l);d[0]-=b.left;d[1]-=b.top;this.f=a.Ra(d);a.render();Tg(e,c,this.f)}
+function Yh(a){if(2>this.l.length){a=a.map.aa();bg(a,1,-1);var b=a.Pa();if(this.s||b<a.i||b>a.a){var c=this.f,d=this.o;b=a.constrainResolution(b,0,this.j-1);Tg(a,b,c,d)}return!1}return!0}function Wh(a){return 2<=this.l.length?(a=a.map,this.f=null,this.a=void 0,this.j=1,this.D||bg(a.aa(),1,1),!0):!1}Vh.prototype.jd=Se;function Zh(a){a=a?a:{};var b=new B,c=new Gg(-.005,.05,100);(void 0!==a.altShiftDragRotate?a.altShiftDragRotate:1)&&b.push(new mh);(void 0!==a.doubleClickZoom?a.doubleClickZoom:1)&&b.push(new Ug({delta:a.zoomDelta,duration:a.zoomDuration}));(void 0!==a.dragPan?a.dragPan:1)&&b.push(new ih({kinetic:c}));(void 0!==a.pinchRotate?a.pinchRotate:1)&&b.push(new Rh);(void 0!==a.pinchZoom?a.pinchZoom:1)&&b.push(new Vh({constrainResolution:a.constrainResolution,duration:a.zoomDuration}));if(void 0!==a.keyboard?
+a.keyboard:1)b.push(new Dh),b.push(new Fh({delta:a.zoomDelta,duration:a.zoomDuration}));(void 0!==a.mouseWheelZoom?a.mouseWheelZoom:1)&&b.push(new Hh({constrainResolution:a.constrainResolution,duration:a.zoomDuration}));(void 0!==a.shiftDragZoom?a.shiftDragZoom:1)&&b.push(new Ch({duration:a.zoomDuration}));return b};function $h(a,b,c,d){Sc.call(this);this.extent=a;this.a=c;this.resolution=b;this.state=d}w($h,Sc);$h.prototype.u=function(){this.b("change")};$h.prototype.G=function(){return this.extent};$h.prototype.getState=function(){return this.state};function ai(a,b,c,d,e){this.c=void 0!==e?e:null;$h.call(this,a,b,c,void 0!==e?0:2);this.g=d}w(ai,$h);ai.prototype.i=function(a){this.state=a?3:2;this.u()};ai.prototype.load=function(){0==this.state&&(this.state=1,this.u(),this.c(this.i.bind(this)))};ai.prototype.Y=function(){return this.g};function bi(a,b,c,d,e){Qc.call(this,a);this.vectorContext=b;this.frameState=c;this.context=d;this.glContext=e}w(bi,Qc);function ci(a){Sc.call(this);this.highWaterMark=void 0!==a?a:2048;this.i=0;this.a={};this.c=this.g=null}w(ci,Sc);function di(a){return a.i>a.highWaterMark}k=ci.prototype;k.clear=function(){this.i=0;this.a={};this.c=this.g=null;this.b("clear")};k.forEach=function(a,b){for(var c=this.g;c;)a.call(b,c.Pc,c.jc,this),c=c.kb};
+k.get=function(a){a=this.a[a];oa(void 0!==a,15);if(a===this.c)return a.Pc;a===this.g?(this.g=this.g.kb,this.g.Pb=null):(a.kb.Pb=a.Pb,a.Pb.kb=a.kb);a.kb=null;a.Pb=this.c;this.c=this.c.kb=a;return a.Pc};k.remove=function(a){var b=this.a[a];oa(void 0!==b,15);if(b===this.c){if(this.c=b.Pb)this.c.kb=null}else if(b===this.g){if(this.g=b.kb)this.g.Pb=null}else b.kb.Pb=b.Pb,b.Pb.kb=b.kb;delete this.a[a];--this.i;return b.Pc};
+k.pop=function(){var a=this.g;delete this.a[a.jc];a.kb&&(a.kb.Pb=null);this.g=a.kb;this.g||(this.c=null);--this.i;return a.Pc};k.replace=function(a,b){this.get(a);this.a[a].Pc=b};k.set=function(a,b){oa(!(a in this.a),16);b={jc:a,kb:null,Pb:this.c,Pc:b};this.c?this.c.kb=b:this.g=b;this.c=b;this.a[a]=b;++this.i};var ei=[0,0,0,1],fi=[],gi=[0,0,0,1],hi=[0,0,0,0],ii=new ci,ji={},ki=null,li={},ni=function(){function a(a){var b=mi();b.font="32px monospace";f=b.measureText("wmytzilWMYTZIL@#/&?$%10").width;var c=!0;"monospace"!=a&&(b.font="32px "+a+",monospace",c=b.measureText("wmytzilWMYTZIL@#/&?$%10").width!=f);return c}function b(){var b=!0,f;for(f in c)60>c[f]&&(a(f)?(c[f]=60,lb(li),ki=null,d.clear()):(++c[f],b=!1));b&&(window.clearInterval(e),e=void 0)}var c=ji,d=ii,e,f;return function(d){if(d=wg(d))for(var f=
+0,g=d.length;f<g;++f){var m=d[f];m in c||(c[m]=60,a(m)||(c[m]=0,void 0===e&&(e=window.setInterval(b,32))))}}}();function mi(){var a=ki;a||(a=ki=hg(1,1));return a}
+var oi=function(){var a;return function(b){var c=li[b];void 0==c&&(a||(a=document.createElement("span"),a.textContent="M",a.style.margin=a.style.padding="0 !important",a.style.position="absolute !important",a.style.left="-99999px !important"),a.style.font=b,document.body.appendChild(a),c=li[b]=a.offsetHeight,document.body.removeChild(a));return c}}();function pi(a,b){var c=mi();a!=c.font&&(c.font=a);return c.measureText(b).width}
+function qi(a,b,c,d){0!==b&&(a.translate(c,d),a.rotate(b),a.translate(-c,-d))}var ri=We();function si(a,b,c,d,e,f,g,h,l,m,n){if(1!=c){var p=a.globalAlpha;a.globalAlpha=p*c}b&&a.setTransform.apply(a,b);a.drawImage(d,e,f,g,h,l,m,g*n,h*n);p&&(a.globalAlpha=p);b&&a.setTransform.apply(a,ri)};var ti=/^#(?:[0-9a-f]{3,4}){1,2}$/i,ui=/^([a-z]*)$/i;function vi(a){return Array.isArray(a)?a:wi(a)}function xi(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 wi=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)0===(d++&3)&&(delete a[e],--b)}d=c;ui.exec(d)&&(e=document.createElement("div"),e.style.color=d,document.body.appendChild(e),d=getComputedStyle(e).color,document.body.removeChild(e));if(ti.exec(d)){e=d.length-1;var f=4>=e?1:2;var g=4===e||8===e;e=parseInt(d.substr(1+0*f,f),16);var h=parseInt(d.substr(1+1*f,f),16);var l=parseInt(d.substr(1+2*f,f),16);d=g?parseInt(d.substr(1+3*f,f),
+16):255;1==f&&(e=(e<<4)+e,h=(h<<4)+h,l=(l<<4)+l,g&&(d=(d<<4)+d));f=[e,h,l,d/255]}else 0==d.indexOf("rgba(")?(d=d.slice(5,-1).split(",").map(Number),f=yi(d)):0==d.indexOf("rgb(")?(d=d.slice(4,-1).split(",").map(Number),d.push(1),f=yi(d)):oa(!1,14);d=f;a[c]=d;++b}return d}}();function yi(a){var b=[];b[0]=pa(a[0]+.5|0,0,255);b[1]=pa(a[1]+.5|0,0,255);b[2]=pa(a[2]+.5|0,0,255);b[3]=pa(a[3],0,1);return b};function zi(a){return"string"===typeof a||a instanceof CanvasPattern||a instanceof CanvasGradient?a:xi(a)};function Ai(){}k=Ai.prototype;k.Hh=function(){};k.Hb=function(){};k.Dd=function(){};k.cc=function(){};k.Ce=function(){};k.De=function(){};k.uc=function(){};k.vc=function(){};k.wc=function(){};k.xc=function(){};k.yc=function(){};k.zc=function(){};k.Wb=function(){};k.Oa=function(){};k.Zb=function(){};k.nb=function(){};function Bi(a,b,c,d,e){this.g=a;this.f=b;this.c=c;this.N=d;this.ob=e;this.M=this.b=this.a=this.Wa=this.O=this.T=null;this.$=this.V=this.v=this.B=this.C=this.D=0;this.ca=!1;this.i=this.ab=0;this.ra=!1;this.oa=0;this.ta="";this.Ub=this.ua=0;this.Ea=!1;this.s=this.La=0;this.qa=this.l=this.j=null;this.o=[];this.bb=We()}w(Bi,Ai);
+function Ci(a,b,c){if(a.M){b=Te(b,0,c,2,a.N,a.o);c=a.g;var d=a.bb,e=c.globalAlpha;1!=a.v&&(c.globalAlpha=e*a.v);var f=a.ab;a.ca&&(f+=a.ob);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.ra&&(l=Math.round(l),m=Math.round(m));if(0!==f||1!=a.i){var n=l+a.D,p=m+a.C;ef(d,n,p,a.i,a.i,f,-n,-p);c.setTransform.apply(c,d)}c.drawImage(a.M,a.V,a.$,a.oa,a.B,l,m,a.oa,a.B)}0===f&&1==a.i||c.setTransform(1,0,0,1,0,0);1!=a.v&&(c.globalAlpha=e)}}
+function Di(a,b,c,d){var e=0;if(a.qa&&""!==a.ta){a.j&&Ei(a,a.j);a.l&&Fi(a,a.l);var f=a.qa,g=a.g,h=a.Wa,l=f.textAlign?f.textAlign:"center";h?(h.font!=f.font&&(h.font=g.font=f.font),h.textAlign!=l&&(h.textAlign=l),h.textBaseline!=f.textBaseline&&(h.textBaseline=g.textBaseline=f.textBaseline)):(g.font=f.font,g.textAlign=l,g.textBaseline=f.textBaseline,a.Wa={font:f.font,textAlign:l,textBaseline:f.textBaseline});b=Te(b,e,c,d,a.N,a.o);f=a.g;g=a.La;for(a.Ea&&(g+=a.ob);e<c;e+=d){h=b[e]+a.ua;l=b[e+1]+a.Ub;
+if(0!==g||1!=a.s){var m=ef(a.bb,h,l,a.s,a.s,g,-h,-l);f.setTransform.apply(f,m)}a.l&&f.strokeText(a.ta,h,l);a.j&&f.fillText(a.ta,h,l)}0===g&&1==a.s||f.setTransform(1,0,0,1,0,0)}}function Gi(a,b,c,d,e,f){var g=a.g;a=Te(b,c,d,e,a.N,a.o);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 Hi(a,b,c,d,e){var f;var g=0;for(f=d.length;g<f;++g)c=Gi(a,b,c,d[g],e,!0);return c}k=Bi.prototype;
+k.cc=function(a){if(hb(this.c,a.G())){if(this.a||this.b){this.a&&Ei(this,this.a);this.b&&Fi(this,this.b);var b=this.N;var c=this.o,d=a.da();b=d?Te(d,0,d.length,a.pa(),b,c):null;c=b[2]-b[0];d=b[3]-b[1];c=Math.sqrt(c*c+d*d);d=this.g;d.beginPath();d.arc(b[0],b[1],c,0,2*Math.PI);this.a&&d.fill();this.b&&d.stroke()}""!==this.ta&&Di(this,a.xa(),2,2)}};k.Dd=function(a){this.Oa(a.Fa(),a.Ga());this.Zb(a.Y());this.nb(a.Ka())};
+k.Hb=function(a){switch(a.S()){case "Point":this.yc(a);break;case "LineString":this.uc(a);break;case "Polygon":this.zc(a);break;case "MultiPoint":this.wc(a);break;case "MultiLineString":this.vc(a);break;case "MultiPolygon":this.xc(a);break;case "GeometryCollection":this.De(a);break;case "Circle":this.cc(a)}};k.Ce=function(a,b){(a=(0,b.cb)(a))&&hb(this.c,a.G())&&(this.Dd(b),this.Hb(a))};k.De=function(a){a=a.a;var b;var c=0;for(b=a.length;c<b;++c)this.Hb(a[c])};
+k.yc=function(a){var b=a.da();a=a.pa();this.M&&Ci(this,b,b.length);""!==this.ta&&Di(this,b,b.length,a)};k.wc=function(a){var b=a.da();a=a.pa();this.M&&Ci(this,b,b.length);""!==this.ta&&Di(this,b,b.length,a)};k.uc=function(a){if(hb(this.c,a.G())){if(this.b){Fi(this,this.b);var b=this.g,c=a.da();b.beginPath();Gi(this,c,0,c.length,a.pa(),!1);b.stroke()}""!==this.ta&&(a=a.Fe(),Di(this,a,2,2))}};
+k.vc=function(a){var b=a.G();if(hb(this.c,b)){if(this.b){Fi(this,this.b);b=this.g;var c=a.da(),d=0,e=a.pb(),f=a.pa();b.beginPath();var g;var h=0;for(g=e.length;h<g;++h)d=Gi(this,c,d,e[h],f,!1);b.stroke()}""!==this.ta&&(a=a.Ge(),Di(this,a,a.length,2))}};k.zc=function(a){if(hb(this.c,a.G())){if(this.b||this.a){this.a&&Ei(this,this.a);this.b&&Fi(this,this.b);var b=this.g;b.beginPath();Hi(this,a.Xb(),0,a.pb(),a.pa());this.a&&b.fill();this.b&&b.stroke()}""!==this.ta&&(a=a.Td(),Di(this,a,2,2))}};
+k.xc=function(a){if(hb(this.c,a.G())){if(this.b||this.a){this.a&&Ei(this,this.a);this.b&&Fi(this,this.b);var b=this.g,c=Ii(a),d=0,e=a.td(),f=a.pa(),g;b.beginPath();var h=0;for(g=e.length;h<g;++h)d=Hi(this,c,d,e[h],f);this.a&&b.fill();this.b&&b.stroke()}""!==this.ta&&(a=Ji(a),Di(this,a,a.length,2))}};function Ei(a,b){var c=a.g,d=a.T;d?d.fillStyle!=b.fillStyle&&(d.fillStyle=c.fillStyle=b.fillStyle):(c.fillStyle=b.fillStyle,a.T={fillStyle:b.fillStyle})}
+function Fi(a,b){var c=a.g,d=a.O;d?(d.lineCap!=b.lineCap&&(d.lineCap=c.lineCap=b.lineCap),od&&(jc(d.lineDash,b.lineDash)||c.setLineDash(d.lineDash=b.lineDash),d.lineDashOffset!=b.lineDashOffset&&(d.lineDashOffset=c.lineDashOffset=b.lineDashOffset)),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,od&&(c.setLineDash(b.lineDash),c.lineDashOffset=b.lineDashOffset),c.lineJoin=b.lineJoin,c.lineWidth=b.lineWidth,c.miterLimit=b.miterLimit,c.strokeStyle=b.strokeStyle,a.O={lineCap:b.lineCap,lineDash:b.lineDash,lineDashOffset:b.lineDashOffset,lineJoin:b.lineJoin,lineWidth:b.lineWidth,miterLimit:b.miterLimit,strokeStyle:b.strokeStyle})}
+k.Oa=function(a,b){a?(a=a.b,this.a={fillStyle:zi(a?a:ei)}):this.a=null;if(b){a=b.a;var c=b.f,d=b.g,e=b.i,f=b.j,g=b.c;b=b.l;this.b={lineCap:void 0!==c?c:"round",lineDash:d?d:fi,lineDashOffset:e?e:0,lineJoin:void 0!==f?f:"round",lineWidth:this.f*(void 0!==g?g:1),miterLimit:void 0!==b?b:10,strokeStyle:zi(a?a:gi)}}else this.b=null};
+k.Zb=function(a){if(a){var b=a.Vc(),c=a.Y(1),d=a.bd(),e=a.oc();this.D=b[0];this.C=b[1];this.B=e[1];this.M=c;this.v=a.i;this.V=d[0];this.$=d[1];this.ca=a.s;this.ab=a.f;this.i=a.a*this.f;this.ra=a.v;this.oa=e[0]}else this.M=null};
+k.nb=function(a){if(a){var b=a.Fa();b?(b=b.b,this.j={fillStyle:zi(b?b:ei)}):this.j=null;var c=a.Ga();if(c){b=c.a;var d=c.f,e=c.g,f=c.i,g=c.j,h=c.c;c=c.l;this.l={lineCap:void 0!==d?d:"round",lineDash:e?e:fi,lineDashOffset:f?f:0,lineJoin:void 0!==g?g:"round",lineWidth:void 0!==h?h:1,miterLimit:void 0!==c?c:10,strokeStyle:zi(b?b:gi)}}else this.l=null;b=a.a;d=a.g;e=a.c;f=a.l;g=a.i;h=a.b;c=a.Ka();var l=a.f;a=a.j;this.qa={font:void 0!==b?b:"10px sans-serif",textAlign:void 0!==l?l:"center",textBaseline:void 0!==
+a?a:"middle"};this.ta=void 0!==c?c:"";this.ua=void 0!==d?this.f*d:0;this.Ub=void 0!==e?this.f*e:0;this.Ea=void 0!==f?f:!1;this.La=void 0!==g?g:0;this.s=this.f*(void 0!==h?h:1)}else this.ta=""};function Ki(a){Uc.call(this);this.a=a}w(Ki,Uc);Ki.prototype.wa=ea;Ki.prototype.cf=Se;Ki.prototype.Rf=function(a,b,c){return function(d,e){return Li(a,b,d,e,function(a){c[d]||(c[d]={});c[d][a.ya.toString()]=a})}};Ki.prototype.$=function(a){2===a.target.getState()&&Mi(this)};function Si(a,b){var c=b.getState();2!=c&&3!=c&&y(b,"change",a.$,a);0==c&&(b.load(),c=b.getState());return 2==c}function Mi(a){var b=a.a;b.Jb()&&"ready"==b.hg()&&a.u()}
+function Ti(a,b){b.cj()&&a.postRenderFunctions.push(function(a,b,e){b=x(a).toString();b in e.usedTiles&&a.sd(e.viewState.projection,e.usedTiles[b])}.bind(null,b))}function Ui(a,b){b=b.T;void 0!==b&&("string"===typeof b?a.logos[b]="":b&&(oa("string"==typeof b.href,44),oa("string"==typeof b.src,45),a.logos[b.src]=b.href))}
+function Vi(a,b,c,d){b=x(b).toString();c=c.toString();b in a?c in a[b]?(a=a[b][c],d.fa<a.fa&&(a.fa=d.fa),d.la>a.la&&(a.la=d.la),d.ea<a.ea&&(a.ea=d.ea),d.ka>a.ka&&(a.ka=d.ka)):a[b][c]=d:(a[b]={},a[b][c]=d)}
+function Wi(a,b,c,d,e,f,g,h,l,m){var n=x(b).toString();n in a.wantedTiles||(a.wantedTiles[n]={});var p=a.wantedTiles[n];a=a.tileQueue;var q,r,u;for(u=c.minZoom;u<=g;++u){var v=tc(c,f,u,v);var z=c.Ta(u);for(q=v.fa;q<=v.la;++q)for(r=v.ea;r<=v.ka;++r)if(g-u<=h){var A=b.ad(u,q,r,d,e);0==A.getState()&&(p[A.lb()]=!0,A.lb()in a.a||a.i([A,n,yc(c,A.ya),z]));void 0!==l&&l.call(m,A)}else b.kh(u,q,r,e)}};function Xi(a){Ki.call(this,a);this.V=We()}w(Xi,Ki);function Yi(a,b,c){var d=b.pixelRatio,e=b.size[0]*d,f=b.size[1]*d,g=b.viewState.rotation,h=$a(c),l=Za(c),m=Ya(c);c=Wa(c);af(b.coordinateToPixelTransform,h);af(b.coordinateToPixelTransform,l);af(b.coordinateToPixelTransform,m);af(b.coordinateToPixelTransform,c);a.save();qi(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();qi(a,g,e/2,f/2)}
+function Zi(a,b,c,d,e){var f=a.a;if(Tc(f,b)){var g=d.size[0]*d.pixelRatio,h=d.size[1]*d.pixelRatio,l=d.viewState.rotation;qi(c,-l,g/2,h/2);a=void 0!==e?e:$i(a,d,0);f.b(new bi(b,new Bi(c,d.pixelRatio,d.extent,a,d.viewState.rotation),d,c,null));qi(c,l,g/2,h/2)}}Xi.prototype.s=function(a,b,c,d){if(this.wa(a,b,0,Re,this))return c.call(d,this.a,null)};Xi.prototype.pf=function(a,b,c,d){Zi(this,"postcompose",a,b,d)};
+function $i(a,b,c){var d=b.viewState,e=b.pixelRatio,f=e/d.resolution;return ef(a.V,e*b.size[0]/2,e*b.size[1]/2,f,-f,-d.rotation,-d.center[0]+c,-d.center[1])};function aj(a){Xi.call(this,a);this.l=We();this.j=null}w(aj,Xi);aj.prototype.df=function(a,b,c){Zi(this,"precompose",c,a,void 0);var d=this.Y();if(d){var e=b.extent,f=void 0!==e&&!La(e,a.extent)&&hb(e,a.extent);f&&Yi(c,a,e);e=this.v();var 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.pf(c,a,b)};
+aj.prototype.wa=function(a,b,c,d,e){var f=this.a;return f.ha().wa(a,b.viewState.resolution,b.viewState.rotation,c,b.skippedFeatureUids,function(a){return d.call(e,a,f)})};
+aj.prototype.s=function(a,b,c,d){if(this.Y()){if(this.a.ha().wa!==ea)return Xi.prototype.s.apply(this,arguments);var e=af(this.l,a.slice());Ge(e,b.viewState.resolution/this.i);this.j||(this.j=hg(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 bj(a){aj.call(this,a);this.M=null;this.f=We();this.o=[];this.c=null}w(bj,aj);bj.handles=function(a,b){return"canvas"===a&&("IMAGE"===b.S()||"VECTOR"===b.S()&&"image"===b.l)};bj.create=function(a,b){var c=new bj(b);if("VECTOR"===b.S())for(var d=0,e=pg.length;d<e;++d){var f=pg[d];f!==bj&&f.handles("canvas",b)&&(f=f.create(a,b),c.c=f)}return c};bj.prototype.Y=function(){return this.M?this.M.Y():null};bj.prototype.v=function(){return this.f};
+bj.prototype.$c=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=gb(m,b.extent));if(!l[0]&&!l[1]&&!bb(m))if(l=e.projection,e=this.c){l=e.context;var n=kb({},a,{size:[cb(m)/g,db(m)/g],viewState:kb({},a.viewState,{rotation:0})}),p=Object.keys(n.skippedFeatureUids).sort();!e.$c(n,b)||!e.j&&jc(p,this.o)||(l.canvas.width=n.size[0]*c,l.canvas.height=n.size[1]*c,e.df(n,b,l),this.M=new ai(m,g,c,l.canvas),this.o=
+p)}else(e=h.Y(m,g,c,l))&&Si(this,e)&&(this.M=e);this.M&&(e=this.M,m=e.G(),b=e.resolution,e=e.a,l=c*b/(g*e),m=ef(this.f,c*d[0]/2,c*d[1]/2,l,l,0,e*(m[0]-f[0])/b,e*(f[1]-m[3])/b),ef(this.l,c*d[0]/2-m[4],c*d[1]/2-m[5],c/g,-c/g,0,-f[0],-f[1]),Ui(a,h),this.i=b*c/e);return!!this.M};bj.prototype.wa=function(a,b,c,d,e){return this.c?this.c.wa(a,b,c,d,e):aj.prototype.wa.call(this,a,b,c,d,e)};function cj(){this.b={};this.a=0;this.g=32}cj.prototype.clear=function(){this.b={};this.a=0};function dj(a){if(a.a>a.g){var b=0,c;for(c in a.b){var d=a.b[c];0!==(b++&3)||Tc(d)||(delete a.b[c],--a.a)}}}cj.prototype.get=function(a,b,c){a=b+":"+a+":"+(c?xi(c):"null");return a in this.b?this.b[a]:null};cj.prototype.set=function(a,b,c,d){this.b[b+":"+a+":"+(c?xi(c):"null")]=d;++this.a};cj.prototype.c=function(a){this.g=a;dj(this)};var ej=new cj;function fj(a,b){this.l=b;this.c={};this.v={}}w(fj,Oc);function gj(a){var b=a.viewState,c=a.coordinateToPixelTransform,d=a.pixelToCoordinateTransform;ef(c,a.size[0]/2,a.size[1]/2,1/b.resolution,-1/b.resolution,-b.rotation,-b.center[0],-b.center[1]);ff($e(d,c))}function hj(){dj(ej)}k=fj.prototype;
+k.wa=function(a,b,c,d,e,f,g){function h(a,c){var f=x(a).toString(),g=b.layerStates[x(c)].Te;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.g){p=p.G();var q=cb(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(yg(u,n)&&f.call(g,r)&&(u=ij(this,r),r.ha()&&(l=u.wa(r.ha().D?m:a,b,c,h,e)),l))return l}};
+k.Ui=function(a,b,c,d,e){return void 0!==this.wa(a,b,c,Re,this,d,e)};function ij(a,b){var c=x(b).toString();if(c in a.c)return a.c[c];for(var d,e=a.S(),f=0,g=pg.length;f<g;++f){var h=pg[f];if(h.handles(e,b)){d=h.create(a,b);break}}if(d)a.c[c]=d,a.v[c]=y(d,"change",a.gm,a);else throw Error("Unable to create renderer for layer: "+b.S());return d}k.gm=function(){this.l.render()};function tg(a,b){var c=a.c[b];delete a.c[b];Gc(a.v[b]);delete a.v[b];return c}k.bh=ea;
+k.oq=function(a,b){for(var c in this.c)b&&c in b.layerStates||Pc(tg(this,c))};function jj(a,b){for(var c in a.c)if(!(c in b.layerStates)){b.postRenderFunctions.push(a.oq.bind(a));break}}function lc(a,b){return a.zIndex-b.zIndex};function kj(a,b){fj.call(this,a,b);this.g=hg();this.b=this.g.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.i=We()}w(kj,fj);kj.handles=function(a){return"canvas"===a};kj.create=function(a,b){return new kj(a,b)};
+function lj(a,b,c){var d=a.l,e=a.g;if(Tc(d,b)){var f=c.extent,g=c.pixelRatio,h=c.viewState.rotation,l=c.viewState,m=c.pixelRatio/l.resolution;a=ef(a.i,a.b.width/2,a.b.height/2,m,-m,-l.rotation,-l.center[0],-l.center[1]);d.b(new bi(b,new Bi(e,g,f,a,h),c,e,null))}}kj.prototype.S=function(){return"canvas"};
+kj.prototype.bh=function(a){if(a){var b=this.g,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;gj(a);lj(this,"precompose",a);var f=a.layerStatesArray;kc(f);c&&(b.save(),qi(b,c,d/2,e/2));d=a.viewState.resolution;var g;e=0;for(g=f.length;e<g;++e){var h=f[e];var l=h.layer;l=ij(this,l);yg(h,d)&&"ready"==h.Vj&&l.$c(a,h)&&l.df(a,h,b)}c&&b.restore();lj(this,"postcompose",a);this.a||
+(this.b.style.display="",this.a=!0);jj(this,a);a.postRenderFunctions.push(hj)}else this.a&&(this.b.style.display="none",this.a=!1)};kj.prototype.Ti=function(a,b,c,d,e,f){var g=b.viewState.resolution,h=b.layerStatesArray,l=h.length;a=af(b.pixelToCoordinateTransform,a.slice());for(--l;0<=l;--l){var m=h[l];var n=m.layer;if(yg(m,g)&&e.call(f,n)&&(m=ij(this,n).s(a,b,c,d)))return m}};function mj(a){aj.call(this,a);this.context=null===this.context?null:hg();this.c=null;this.f=[];this.T=Da();this.ra=new ja(0,0,0,0);this.o=We();this.O=0}w(mj,aj);mj.handles=function(a,b){return"canvas"===a&&"TILE"===b.S()};mj.create=function(a,b){return new mj(b)};function nj(a,b){b=b.getState();a=a.a.i();return 2==b||4==b||3==b&&!a}
+mj.prototype.$c=function(a,b){var c=a.pixelRatio,d=a.size,e=a.viewState,f=e.projection,g=e.resolution;e=e.center;var h=this.a,l=h.ha(),m=l.g,n=l.eb(f),p=n.Dc(g,this.O),q=n.Ta(p),r=Math.round(g/q)||1,u=a.extent;void 0!==b.extent&&(u=gb(u,b.extent));if(bb(u))return!1;var v=tc(n,u,p),z=wc(n,p,v),A=l.Xc(c),E={};E[p]={};var S=this.Rf(l,f,E),Ia=this.T,ta=this.ra,la=!1,ca,ia;for(ca=v.fa;ca<=v.la;++ca)for(ia=v.ea;ia<=v.ka;++ia){var xa=l.ad(p,ca,ia,c,f);3==xa.getState()&&(h.i()?0<h.c()&&(la=!0):oj(xa,2));
+nj(this,xa)||(xa=pj(xa));if(nj(this,xa)){var Va=x(this);if(2==xa.getState()){E[p][xa.ya.toString()]=xa;var ic=xa.j?-1!==xa.s[Va]:!1;la||!ic&&-1!==this.f.indexOf(xa)||(la=!0)}if(1===qj(xa,Va,a.time))continue}Va=vc(n,xa.ya,ta,Ia);ic=!1;Va&&(ic=S(p+1,Va));ic||uc(n,xa.ya,S,ta,Ia)}xa=a.viewHints;xa=xa[0]||xa[1];if(!(this.i&&16<Date.now()-a.time&&xa||!la&&this.c&&La(this.c,u)&&this.wf==m&&r==this.C&&(xa||q*c/A*r==this.i))){if(xa=this.context)ia=l.Zd(p,c,f),ca=Math.round((v.la-v.fa+1)*ia[0]/r),ia=Math.round((v.ka-
+v.ea+1)*ia[1]/r),la=xa.canvas,la.width!=ca||la.height!=ia?(this.C=r,la.width=ca,la.height=ia):(this.c&&!Sa(z,this.c)&&xa.clearRect(0,0,ca,ia),r=this.C);this.f.length=0;la=Object.keys(E).map(Number);la.sort(function(a,b){return a===p?1:b===p?-1:a>b?1:a<b?-1:0});Va=0;for(ic=la.length;Va<ic;++Va){ta=la[Va];S=l.Zd(ta,c,f);xa=n.Ta(ta);var Xa=xa/q;var Z=A*l.Zf(f);var Zb=E[ta];for(var Le in Zb){xa=Zb[Le];ia=n.Ma(xa.ya,Ia);ca=(ia[0]-z[0])/q*A/r;ia=(z[3]-ia[3])/q*A/r;var Uf=S[0]*Xa/r;var Id=S[1]*Xa/r;this.Sf(xa,
+a,b,ca,ia,Uf,Id,Z,p===ta);this.f.push(xa)}}this.wf=m;this.i=q*c/A*r;this.c=z}b=this.i/g;b=ef(this.o,c*d[0]/2,c*d[1]/2,b,b,0,(this.c[0]-e[0])/this.i*c,(e[1]-this.c[3])/this.i*c);ef(this.l,c*d[0]/2-b[4],c*d[1]/2-b[5],c/g,-c/g,0,-e[0],-e[1]);Vi(a.usedTiles,l,p,v);Wi(a,l,n,c,f,u,p,h.c());Ti(a,l);Ui(a,l);return 0<this.f.length};
+mj.prototype.Sf=function(a,b,c,d,e,f,g,h,l){if(c=a.Y(this.a)){var m=x(this),n=l?qj(a,m,b.time):1;1!==n||this.a.ha().eg(b.viewState.projection)||this.context.clearRect(d,e,f,g);var p=n!==this.context.globalAlpha;p&&(this.context.save(),this.context.globalAlpha=n);this.context.drawImage(c,h,h,c.width-2*h,c.height-2*h,d,e,f,g);p&&this.context.restore();1!==n?b.animate=!0:l&&a.j&&(a.s[m]=-1)}};mj.prototype.Y=function(){var a=this.context;return a?a.canvas:null};mj.prototype.v=function(){return this.o};var rj={Jc:function(){}};
+(function(a){function b(a,b,d,f,g){c(a,b,d||0,f||a.length-1,g||e)}function c(a,b,e,f,g){for(;f>e;){if(600<f-e){var h=f-e+1,l=b-e+1,m=Math.log(h),n=.5*Math.exp(2*m/3);m=.5*Math.sqrt(m*n*(h-n)/h)*(0>l-h/2?-1:1);c(a,b,Math.max(e,Math.floor(b-l*n/h+m)),Math.min(f,Math.floor(b+(h-l)*n/h+m)),g)}h=a[b];l=e;n=f;d(a,e,b);for(0<g(a[f],h)&&d(a,e,f);l<n;){d(a,l,n);l++;for(n--;0>g(a[l],h);)l++;for(;0<g(a[n],h);)n--}0===g(a[e],h)?d(a,e,n):(n++,d(a,n,f));n<=b&&(e=n+1);b<=n&&(f=n-1)}}function d(a,b,c){var d=a[b];
+a[b]=a[c];a[c]=d}function e(a,b){return a<b?-1:a>b?1:0}function f(a,b){if(!(this instanceof f))return new f(a,b);this.Lf=Math.max(4,a||9);this.wh=Math.max(2,Math.ceil(.4*this.Lf));b&&this.Ak(b);this.clear()}function g(a,b){h(a,0,a.children.length,b,a)}function h(a,b,c,d,e){e||(e=v(null));e.fa=Infinity;e.ea=Infinity;e.la=-Infinity;e.ka=-Infinity;for(var f;b<c;b++)f=a.children[b],l(e,a.fb?d(f):f);return e}function l(a,b){a.fa=Math.min(a.fa,b.fa);a.ea=Math.min(a.ea,b.ea);a.la=Math.max(a.la,b.la);a.ka=
+Math.max(a.ka,b.ka);return a}function m(a,b){return a.fa-b.fa}function n(a,b){return a.ea-b.ea}function p(a){return(a.la-a.fa)*(a.ka-a.ea)}function q(a){return a.la-a.fa+(a.ka-a.ea)}function r(a,b){return a.fa<=b.fa&&a.ea<=b.ea&&b.la<=a.la&&b.ka<=a.ka}function u(a,b){return b.fa<=a.la&&b.ea<=a.ka&&b.la>=a.fa&&b.ka>=a.ea}function v(a){return{children:a,height:1,fb:!0,fa:Infinity,ea:Infinity,la:-Infinity,ka:-Infinity}}function z(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,A(a,g,b,c,e),f.push(b,g,g,c))}var A=b;A.default=b;f.prototype={all:function(){return this.rh(this.data,[])},search:function(a){var b=this.data,c=[],d=this.xb;if(!u(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.fb?d(h):h,u(a,l)&&(b.fb?c.push(h):r(a,l)?this.rh(h,c):e.push(h));b=e.pop()}return c},Ok:function(a){var b=this.data,c=this.xb;if(!u(a,b))return!1;for(var d=[],e,f,g,h;b;){e=0;for(f=b.children.length;e<f;e++)if(g=b.children[e],
+h=b.fb?c(g):g,u(a,h)){if(b.fb||r(a,h))return!0;d.push(g)}b=d.pop()}return!1},load:function(a){if(!a||!a.length)return this;if(a.length<this.wh){for(var b=0,c=a.length;b<c;b++)this.Ca(a[b]);return this}a=this.th(a.slice(),0,a.length-1,0);this.data.children.length?this.data.height===a.height?this.yh(this.data,a):(this.data.height<a.height&&(b=this.data,this.data=a,a=b),this.vh(a,this.data.height-a.height-1,!0)):this.data=a;return this},Ca:function(a){a&&this.vh(a,this.data.height-1);return this},clear:function(){this.data=
+v([]);return this},remove:function(a,b){if(!a)return this;for(var c=this.data,d=this.xb(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.fb){a:{l=a;var n=c.children,p=b;if(p){for(var q=0;q<n.length;q++)if(p(l,n[q])){l=q;break a}l=-1}else l=n.indexOf(l)}if(-1!==l){c.children.splice(l,1);e.push(c);this.yk(e);break}}m||c.fb||!r(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},xb:function(a){return a},Pf:m,Qf:n,toJSON:function(){return this.data},
+rh:function(a,b){for(var c=[];a;)a.fb?b.push.apply(b,a.children):c.push.apply(c,a.children),a=c.pop();return b},th:function(a,b,c,d){var e=c-b+1,f=this.Lf;if(e<=f){var h=v(a.slice(b,c+1));g(h,this.xb);return h}d||(d=Math.ceil(Math.log(e)/Math.log(f)),f=Math.ceil(e/Math.pow(f,d-1)));h=v([]);h.fb=!1;h.height=d;e=Math.ceil(e/f);f=e*Math.ceil(Math.sqrt(f));var l;for(z(a,b,c,f,this.Pf);b<=c;b+=f){var m=Math.min(b+f-1,c);z(a,b,m,e,this.Qf);for(l=b;l<=m;l+=e){var n=Math.min(l+e-1,m);h.children.push(this.th(a,
+l,n,d-1))}}g(h,this.xb);return h},xk:function(a,b,c,d){for(var e,f,g,h,l,m,n,q;;){d.push(b);if(b.fb||d.length-1===c)break;n=q=Infinity;e=0;for(f=b.children.length;e<f;e++)g=b.children[e],l=p(g),m=(Math.max(g.la,a.la)-Math.min(g.fa,a.fa))*(Math.max(g.ka,a.ka)-Math.min(g.ea,a.ea))-l,m<q?(q=m,n=l<n?l:n,h=g):m===q&&l<n&&(n=l,h=g);b=h||b.children[0]}return b},vh:function(a,b,c){var d=this.xb;c=c?a:d(a);d=[];var e=this.xk(c,this.data,b,d);e.children.push(a);for(l(e,c);0<=b;)if(d[b].children.length>this.Lf)this.Dk(d,
+b),b--;else break;this.uk(c,d,b)},Dk:function(a,b){var c=a[b],d=c.children.length,e=this.wh;this.vk(c,e,d);d=this.wk(c,e,d);d=v(c.children.splice(d,c.children.length-d));d.height=c.height;d.fb=c.fb;g(c,this.xb);g(d,this.xb);b?a[b-1].children.push(d):this.yh(c,d)},yh:function(a,b){this.data=v([a,b]);this.data.height=a.height+1;this.data.fb=!1;g(this.data,this.xb)},wk:function(a,b,c){var d,e;var f=e=Infinity;for(d=b;d<=c-b;d++){var g=h(a,0,d,this.xb);var l=h(a,d,c,this.xb);var m=Math.max(0,Math.min(g.la,
+l.la)-Math.max(g.fa,l.fa))*Math.max(0,Math.min(g.ka,l.ka)-Math.max(g.ea,l.ea));g=p(g)+p(l);if(m<f){f=m;var n=d;e=g<e?g:e}else m===f&&g<e&&(e=g,n=d)}return n},vk:function(a,b,c){var d=a.fb?this.Pf:m,e=a.fb?this.Qf:n,f=this.sh(a,b,c,d);b=this.sh(a,b,c,e);f<b&&a.children.sort(d)},sh:function(a,b,c,d){a.children.sort(d);d=this.xb;var e=h(a,0,b,d),f=h(a,c-b,c,d),g=q(e)+q(f),m;for(m=b;m<c-b;m++){var n=a.children[m];l(e,a.fb?d(n):n);g+=q(e)}for(m=c-b-1;m>=b;m--)n=a.children[m],l(f,a.fb?d(n):n),g+=q(f);return g},
+uk:function(a,b,c){for(;0<=c;c--)l(b[c],a)},yk: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():g(a[b],this.xb)},Ak:function(a){var b=["return a"," - b",";"];this.Pf=new Function("a","b",b.join(a[0]));this.Qf=new Function("a","b",b.join(a[1]));this.xb=new Function("a","return {minX: a"+a[0]+", minY: a"+a[1]+", maxX: a"+a[2]+", maxY: a"+a[3]+"};")}};a["default"]=f})(rj.Jc=rj.Jc||{});rj.Jc=rj.Jc.default;function sj(){};function tj(a,b,c,d){var e=a[b],f=a[b+1],g=0;for(b+=d;b<c;b+=d){var h=a[b],l=a[b+1];g+=Math.sqrt((h-e)*(h-e)+(l-f)*(l-f));e=h;f=l}return g};var uj="Polygon Circle LineString Image Text Default".split(" "),vj={left:0,end:0,center:.5,right:1,start:1,top:0,middle:.5,hanging:.2,alphabetic:.8,ideographic:.8,bottom:1};function wj(a,b,c,d,e,f){this.ra=f;this.La=Da();this.ob=a;this.Ea=b;this.overlaps=e;this.pixelRatio=d;this.Wa=0;this.resolution=c;this.i=this.T=this.qa=null;this.a=[];this.coordinates=[];this.Ub={};this.ca=We();this.b=[];this.oa=null;this.state={};this.$=0;this.bb=We()}w(wj,Ai);function xj(a,b,c,d,e,f,g,h){b.beginPath();b.moveTo.apply(b,c);b.lineTo.apply(b,d);b.lineTo.apply(b,e);b.lineTo.apply(b,f);b.lineTo.apply(b,c);g&&(a.O=g[2],a.Xa(b));h&&(yj(b,h),b.stroke())}
+function zj(a,b,c,d,e,f,g,h,l,m,n,p,q,r,u,v,z,A,E){var S=A||E,Ia=a.bb;f*=r;g*=r;c-=f;d-=g;u&&(c=Math.round(c),d=Math.round(d));u=v+n>e.width?e.width-n:v;l=l+p>e.height?e.height-p:l;v=a.La;var ta=z[3]+u*r+z[1],la=z[0]+l*r+z[2],ca=c-z[3],ia=d-z[0];if(S||0!==q){var xa=[ca,ia];var Va=[ca+ta,ia];var ic=[ca+ta,ia+la];var Xa=[ca,ia+la]}z=null;0!==q?(f=c+f,g=d+g,z=ef(Ia,f,g,1,1,q,-f,-g),Oa(v),Ea(v,af(Ia,xa)),Ea(v,af(Ia,Va)),Ea(v,af(Ia,ic)),Ea(v,af(Ia,Xa))):Na(ca,ia,ca+ta,ia+la,v);q=b.canvas;q=v[0]<=q.width&&
+0<=v[2]&&v[1]<=q.height&&0<=v[3];if(h){if(q||1!=h[4])Ta(h,v),(a=q?[b,z?z.slice(0):null,m,e,n,p,u,l,c,d,r]:null)&&S&&a.push(A,E,xa,Va,ic,Xa),h.push(a)}else q&&(S&&xj(a,b,xa,Va,ic,Xa,A,E),si(b,z,m,e,n,p,u,l,c,d,r))}function Aj(a,b){var c=a.pixelRatio;return 1==c?b:b.map(function(a){return a*c})}
+function Bj(a,b,c,d,e,f,g){var h=a.coordinates.length,l=Cj(a);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=Ma(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 Dj(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;++g){var l=d[g];c=Bj(a,b,c,l,e,!1,!1);f.push(c);c=l}return c}k=wj.prototype;
+k.Hh=function(a,b,c){Ej(this,b);var d=a.S(),e=a.pa(),f=this.coordinates.length,g;if("MultiPolygon"==d){d=Ii(a);var h=[];for(var l=a.td(),m=g=0,n=l.length;m<n;++m){var p=[];g=Dj(this,d,g,l[m],e,p);h.push(p)}this.a.push([4,f,h,a,c,Af])}else"Polygon"==d||"MultiLineString"==d?(h=[],d="Polygon"==d?a.Xb():a.da(),Dj(this,d,0,a.pb(),e,h),this.a.push([4,f,h,a,c,zf])):"LineString"==d||"MultiPoint"==d?(d=a.da(),e=Bj(this,d,0,d.length,e,!1,!1),this.a.push([4,f,e,a,c,yf])):"Point"==d&&(d=a.da(),this.coordinates.push(d[0],
+d[1]),e=this.coordinates.length,this.a.push([4,f,e,a,c]));Fj(this,b)};function Ej(a,b){a.qa=[0,b,0];a.a.push(a.qa);a.T=[0,b,0];a.b.push(a.T)}k.Xa=function(a){if(this.O){var b=af(this.ca,this.O.slice());a.translate(b[0],b[1]);a.rotate(this.$)}a.fill();this.O&&a.setTransform.apply(a,ri)};function yj(a,b){a.strokeStyle=b[1];a.lineWidth=b[2];a.lineCap=b[3];a.lineJoin=b[4];a.miterLimit=b[5];od&&(a.lineDashOffset=b[7],a.setLineDash(b[6]))}
+function Gj(a,b,c){if(b&&5<b.length){var d=b[4];if(1==d||d==b.length-5){c={fa:b[0],ea:b[1],la:b[2],ka:b[3],value:c};if(!a.ra.Ok(c))for(a.ra.Ca(c),c=5,d=b.length;c<d;++c){var e=b[c];e&&(11<e.length&&xj(a,e[0],e[13],e[14],e[15],e[16],e[11],e[12]),si.apply(void 0,e))}b.length=5;Oa(b)}}}
+function Hj(a,b,c,d,e,f,g){if(a.oa&&jc(c,a.ca))var h=a.oa;else a.oa||(a.oa=[]),h=Te(a.coordinates,0,a.coordinates.length,2,c,a.oa),$e(a.ca,c);for(var l=!nb(d),m=0,n=e.length,p=0,q,r,u,v,z,A,E,S,Ia,ta=0,la=0,ca=null,ia=null,xa=a.Ub,Va=a.$,ic={context:b,pixelRatio:a.pixelRatio,resolution:a.resolution,rotation:Va},Xa=a.a!=e||a.overlaps?0:200;m<n;){var Z=e[m];switch(Z[0]){case 0:var Zb=Z[1];l&&d[x(Zb).toString()]||!Zb.U()?m=Z[2]:void 0===g||hb(g,Zb.U().G())?++m:m=Z[2]+1;break;case 1:ta>Xa&&(a.Xa(b),ta=
+0);la>Xa&&(b.stroke(),la=0);ta||la||(b.beginPath(),v=z=NaN);++m;break;case 2:p=Z[1];var Le=h[p],Uf=h[p+1],Id=h[p+2]-Le,te=h[p+3]-Uf,Jh=Math.sqrt(Id*Id+te*te);b.moveTo(Le+Jh,Uf);b.arc(Le,Uf,Jh,0,2*Math.PI,!0);++m;break;case 3:b.closePath();++m;break;case 4:p=Z[1];q=Z[2];var Mg=Z[4],Ng=6==Z.length?Z[5]:void 0;ic.geometry=Z[3];ic.feature=Zb;m in xa||(xa[m]=[]);var Wf=xa[m];Ng?Ng(h,p,q,2,Wf):(Wf[0]=h[p],Wf[1]=h[p+1],Wf.length=2);Mg(Wf,ic);++m;break;case 6:p=Z[1];q=Z[2];Ia=Z[3];r=Z[4];u=Z[5];S=f?null:
+Z[6];var rf=Z[7],yu=Z[8],zu=Z[9],Au=Z[10],Bu=Z[11],jp=Z[12],Cu=Z[13],Du=Z[14],Eu=Z[15];if(16<Z.length){var kp=Z[16];var lp=Z[17];var mp=Z[18]}else kp=hi,lp=mp=!1;for(Bu&&(jp+=Va);p<q;p+=2)zj(a,b,h[p],h[p+1],Ia,r,u,S,rf,yu,zu,Au,jp,Cu,Du,Eu,kp,lp?ca:null,mp?ia:null);Gj(a,S,Zb);++m;break;case 5:var np=Z[1],op=Z[2],Lk=Z[3];S=f?null:Z[4];var Fu=Z[5],pp=Z[6],Gu=Z[7],qp=Z[8],rp=Z[9],sp=Z[10],tp=Z[11],up=Z[12],Mk=Z[13],vp=Z[14],wp=tj(h,np,op,2),xp=qp(up);if(Fu||xp<=wp){a:{var Ni=void 0,yp=void 0,Xf=void 0,
+sf=h,ve=np,zp=op,Ap=up,Hu=qp,Bp=(wp-xp)*vj[a.s[Mk].textAlign],Iu=Gu,Nk=[],Kh=sf[ve]>sf[zp-2],Cp=Ap.length,Lh=sf[ve],Mh=sf[ve+1];ve+=2;for(var Og=sf[ve],Pg=sf[ve+1],Ok=0,Oi=Math.sqrt(Math.pow(Og-Lh,2)+Math.pow(Pg-Mh,2)),Yf="",Pk=0,Pi=0;Pi<Cp;++Pi){yp=Kh?Cp-Pi-1:Pi;var Qk=Ap.charAt(yp);Yf=Kh?Qk+Yf:Yf+Qk;var Qg=Hu(Yf)-Pk;Pk+=Qg;for(var Dp=Bp+Qg/2;ve<zp-2&&Ok+Oi<Dp;)Lh=Og,Mh=Pg,ve+=2,Og=sf[ve],Pg=sf[ve+1],Ok+=Oi,Oi=Math.sqrt(Math.pow(Og-Lh,2)+Math.pow(Pg-Mh,2));var Ju=Dp-Ok,Rg=Math.atan2(Pg-Mh,Og-Lh);
+Kh&&(Rg+=0<Rg?-Math.PI:Math.PI);if(void 0!==Ni){var Qi=Rg-Ni;Qi+=Qi>Math.PI?-2*Math.PI:Qi<-Math.PI?2*Math.PI:0;if(Math.abs(Qi)>Iu){var Sg=null;break a}}var Ep=Ju/Oi,Fp=ya(Lh,Og,Ep),Gp=ya(Mh,Pg,Ep);Ni==Rg?(Kh&&(Xf[0]=Fp,Xf[1]=Gp,Xf[2]=Qg/2),Xf[4]=Yf):(Yf=Qk,Pk=Qg,Xf=[Fp,Gp,Qg/2,Rg,Yf],Kh?Nk.unshift(Xf):Nk.push(Xf),Ni=Rg);Bp+=Qg}Sg=Nk}if(Sg){var Ri;if(sp){var Zf=0;for(Ri=Sg.length;Zf<Ri;++Zf){var ee=Sg[Zf];var Rk=ee[4];var Ne=a.Y(Rk,Mk,"",sp);r=ee[2]+tp;u=Lk*Ne.height+2*(.5-Lk)*tp-rp;zj(a,b,ee[0],ee[1],
+Ne,r,u,S,Ne.height,1,0,0,ee[3],vp,!1,Ne.width,hi,null,null)}}if(pp)for(Zf=0,Ri=Sg.length;Zf<Ri;++Zf)ee=Sg[Zf],Rk=ee[4],Ne=a.Y(Rk,Mk,pp,""),r=ee[2],u=Lk*Ne.height-rp,zj(a,b,ee[0],ee[1],Ne,r,u,S,Ne.height,1,0,0,ee[3],vp,!1,Ne.width,hi,null,null)}}Gj(a,S,Zb);++m;break;case 7:if(void 0!==f){Zb=Z[1];var Hp=f(Zb);if(Hp)return Hp}++m;break;case 8:Xa?ta++:a.Xa(b);++m;break;case 9:p=Z[1];q=Z[2];var Nh=h[p];var Oh=h[p+1];A=Nh+.5|0;E=Oh+.5|0;if(A!==v||E!==z)b.moveTo(Nh,Oh),v=A,z=E;for(p+=2;p<q;p+=2)if(Nh=h[p],
+Oh=h[p+1],A=Nh+.5|0,E=Oh+.5|0,p==q-2||A!==v||E!==z)b.lineTo(Nh,Oh),v=A,z=E;++m;break;case 10:ca=Z;a.O=Z[2];ta&&(a.Xa(b),ta=0,la&&(b.stroke(),la=0));b.fillStyle=Z[1];++m;break;case 11:ia=Z;la&&(b.stroke(),la=0);yj(b,Z);++m;break;case 12:Xa?la++:b.stroke();++m;break;default:++m}}ta&&a.Xa(b);la&&b.stroke()}k.Na=function(a,b,c,d){this.$=c;Hj(this,a,b,d,this.a,void 0,void 0)};function Ij(a,b,c,d,e,f,g){a.$=d;return Hj(a,b,c,e,a.b,f,g)}
+function Jj(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(7==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}}}
+k.Oa=function(a,b){var c=this.state;a?(a=a.b,c.fillStyle=zi(a?a:ei)):c.fillStyle=void 0;b?(a=b.a,c.strokeStyle=zi(a?a:gi),a=b.f,c.lineCap=void 0!==a?a:"round",a=b.g,c.lineDash=a?a.slice():fi,a=b.i,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.l,c.miterLimit=void 0!==b?b:10,c.lineWidth>this.Wa&&(this.Wa=c.lineWidth,this.i=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)};k.Ah=function(a,b){var c=a.fillStyle;a=[10,c];"string"!==typeof c&&(b=b.G(),a.push([b[0],b[3]]));this.a.push(a)};k.pd=function(a){this.a.push([11,a.strokeStyle,a.lineWidth*this.pixelRatio,a.lineCap,a.lineJoin,a.miterLimit,Aj(this,a.lineDash),a.lineDashOffset*this.pixelRatio])};function Kj(a,b,c,d){var e=b.fillStyle;if("string"!==typeof e||b.Pk!=e)c.call(a,b,d),b.Pk=e}
+function Lj(a,b,c){var d=b.strokeStyle,e=b.lineCap,f=b.lineDash,g=b.lineDashOffset,h=b.lineJoin,l=b.lineWidth,m=b.miterLimit;if(b.Vk!=d||b.Qk!=e||f!=b.Fh&&!jc(b.Fh,f)||b.Rk!=g||b.Sk!=h||b.Tk!=l||b.Uk!=m)c.call(a,b),b.Vk=d,b.Qk=e,b.Fh=f,b.Rk=g,b.Sk=h,b.Tk=l,b.Uk=m}function Fj(a,b){a.qa[2]=a.a.length;a.qa=null;a.T[2]=a.b.length;a.T=null;b=[7,b];a.a.push(b);a.b.push(b)}k.bf=ea;function Cj(a){a.i||(a.i=Ga(a.Ea),0<a.Wa&&Fa(a.i,a.resolution*(a.Wa+1)/2,a.i));return a.i};function Mj(a,b,c,d,e,f){wj.call(this,a,b,c,d,e,f);this.M=this.V=this.B=null;this.N=this.o=this.v=this.s=this.l=this.C=this.D=this.j=this.f=this.c=this.g=void 0}w(Mj,wj);
+Mj.prototype.yc=function(a,b){if(this.M){Ej(this,b);var c=a.da(),d=this.coordinates.length;a=Bj(this,c,0,c.length,a.pa(),!1,!1);this.a.push([6,d,a,this.M,this.g,this.c,this.B,this.f,this.j,this.D,this.C,this.l,this.s,this.v*this.pixelRatio,this.o,this.N]);this.b.push([6,d,a,this.V,this.g,this.c,this.B,this.f,this.j,this.D,this.C,this.l,this.s,this.v,this.o,this.N]);Fj(this,b)}};
+Mj.prototype.wc=function(a,b){if(this.M){Ej(this,b);var c=a.da(),d=this.coordinates.length;a=Bj(this,c,0,c.length,a.pa(),!1,!1);this.a.push([6,d,a,this.M,this.g,this.c,this.B,this.f,this.j,this.D,this.C,this.l,this.s,this.v*this.pixelRatio,this.o,this.N]);this.b.push([6,d,a,this.V,this.g,this.c,this.B,this.f,this.j,this.D,this.C,this.l,this.s,this.v,this.o,this.N]);Fj(this,b)}};
+Mj.prototype.bf=function(){Jj(this);this.c=this.g=void 0;this.M=this.V=null;this.N=this.o=this.s=this.l=this.C=this.D=this.j=this.v=this.f=void 0};Mj.prototype.Zb=function(a,b){var c=a.Vc(),d=a.oc(),e=a.Eg(),f=a.Y(1),g=a.bd();this.g=c[0];this.c=c[1];this.B=b;this.V=e;this.M=f;this.f=d[1];this.j=a.i;this.D=g[0];this.C=g[1];this.l=a.s;this.s=a.f;this.v=a.a;this.o=a.v;this.N=d[0]};function Nj(a,b,c,d,e,f){wj.call(this,a,b,c,d,e,f)}w(Nj,wj);function Oj(a,b,c,d,e){var f=a.coordinates.length;b=Bj(a,b,c,d,e,!1,!1);f=[9,f,b];a.a.push(f);a.b.push(f);return d}Nj.prototype.uc=function(a,b){var c=this.state,d=c.lineWidth;void 0!==c.strokeStyle&&void 0!==d&&(Lj(this,c,this.pd),Ej(this,b),this.b.push([11,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset],[1]),c=a.da(),Oj(this,c,0,c.length,a.pa()),this.b.push([12]),Fj(this,b))};
+Nj.prototype.vc=function(a,b){var c=this.state,d=c.lineWidth;if(void 0!==c.strokeStyle&&void 0!==d){Lj(this,c,this.pd);Ej(this,b);this.b.push([11,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset],[1]);c=a.pb();d=a.da();a=a.pa();var e=0,f;var g=0;for(f=c.length;g<f;++g)e=Oj(this,d,e,c[g],a);this.b.push([12]);Fj(this,b)}};Nj.prototype.bf=function(){var a=this.state;void 0!=a.$d&&a.$d!=this.coordinates.length&&this.a.push([12]);Jj(this);this.state=null};
+Nj.prototype.pd=function(a){void 0!=a.$d&&a.$d!=this.coordinates.length&&(this.a.push([12]),a.$d=this.coordinates.length);a.$d=0;wj.prototype.pd.call(this,a);this.a.push([1])};function Pj(a,b,c,d,e,f){wj.call(this,a,b,c,d,e,f)}w(Pj,wj);function Qj(a,b,c,d,e){var f=a.state,g=void 0!==f.fillStyle;f=void 0!=f.strokeStyle;var 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=Bj(a,b,c,m,e,!0,!f);c=[9,n,c];a.a.push(c);a.b.push(c);f&&(c=[3],a.a.push(c),a.b.push(c));c=m}b=[8];a.b.push(b);g&&a.a.push(b);f&&(g=[12],a.a.push(g),a.b.push(g));return c}
+Pj.prototype.cc=function(a,b){var c=this.state,d=c.strokeStyle;if(void 0!==c.fillStyle||void 0!==d){Rj(this,a);Ej(this,b);this.b.push([10,xi(ei)]);void 0!==c.strokeStyle&&this.b.push([11,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset]);var e=a.da();d=this.coordinates.length;Bj(this,e,0,e.length,a.pa(),!1,!1);a=[1];d=[2,d];this.a.push(a,d);this.b.push(a,d);a=[8];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));Fj(this,b)}};Pj.prototype.zc=function(a,b){var c=this.state;Rj(this,a);Ej(this,b);this.b.push([10,xi(ei)]);void 0!==c.strokeStyle&&this.b.push([11,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset]);c=a.pb();var d=a.Xb();Qj(this,d,0,c,a.pa());Fj(this,b)};
+Pj.prototype.xc=function(a,b){var c=this.state,d=c.strokeStyle;if(void 0!==c.fillStyle||void 0!==d){Rj(this,a);Ej(this,b);this.b.push([10,xi(ei)]);void 0!==c.strokeStyle&&this.b.push([11,c.strokeStyle,c.lineWidth,c.lineCap,c.lineJoin,c.miterLimit,c.lineDash,c.lineDashOffset]);c=a.td();d=Ii(a);a=a.pa();var e=0,f;var g=0;for(f=c.length;g<f;++g)e=Qj(this,d,e,c[g],a);Fj(this,b)}};
+Pj.prototype.bf=function(){Jj(this);this.state=null;var a=this.ob;if(0!==a){var b=this.coordinates,c;var d=0;for(c=b.length;d<c;++d)b[d]=a*Math.round(b[d]/a)}};function Rj(a,b){var c=a.state;void 0!==c.fillStyle&&Kj(a,c,a.Ah,b);void 0!==c.strokeStyle&&Lj(a,c,a.pd)};function Sj(a,b,c,d,e,f){wj.call(this,a,b,c,d,e,f);this.ta="";this.l=this.D=0;this.C=void 0;this.B=0;this.c=null;this.o={};this.g=null;this.ab={};this.f={};this.s={};this.V=this.v=this.j="";for(this.ua={};di(ii);)ii.pop()}w(Sj,wj);
+Sj.prototype.Wb=function(a,b){var c=this.c,d=this.g,e=this.f;if(""!==this.ta&&e&&(c||d)){c=this.coordinates.length;var f=a.S();d=null;var g=2,h=2;if("line"===e.placement){if(!hb(Cj(this),a.G()))return;d=a.da();h=a.pa();if("LineString"==f)var l=[d.length];else if("MultiLineString"==f)l=a.pb();else if("Polygon"==f)l=a.pb().slice(0,1);else if("MultiPolygon"==f)for(a=a.td(),l=[],g=0,f=a.length;g<f;++g)l.push(a[g][0]);Ej(this,b);a=e.textAlign;var m=0,n;f=0;for(var p=l.length;f<p;++f){if(void 0==a){for(var q,
+r,u=void 0,v=void 0,z=g=r=q=void 0,A=n=m,E=0,S=0,Ia=m;m<l[f];m+=h){var ta=d[m],la=d[m+1];void 0!==r&&(r=ta-r,q=la-q,g=Math.sqrt(r*r+q*q),void 0!==v&&(S+=z,u=Math.acos((v*r+u*q)/(z*g)),u>e.maxAngle&&(S>E&&(E=S,n=Ia,A=m),S=0,Ia=m-h)),z=g,v=r,u=q);r=ta;q=la}g=S+g>E?[Ia,m]:[n,A];m=g[0];n=g[1]}else n=l[f];for(g=m;g<n;g+=h)this.coordinates.push(d[g],d[g+1]);g=this.coordinates.length;m=l[f];Tj(this,c,g,this.N);c=g}}else{l=this.Y(this.ta,this.j,this.v,this.V);p=l.width/this.pixelRatio;switch(f){case "Point":case "MultiPoint":d=
+a.da();g=d.length;break;case "LineString":d=a.Fe();break;case "Circle":d=a.xa();break;case "MultiLineString":d=a.Ge();g=d.length;break;case "Polygon":d=a.Td();if(!e.overflow&&d[2]/this.resolution<p)return;h=3;break;case "MultiPolygon":n=Ji(a);d=[];g=0;for(f=n.length;g<f;g+=3)(e.overflow||n[g+2]/this.resolution>=p)&&d.push(n[g],n[g+1]);g=d.length;if(0==g)return}g=Bj(this,d,0,g,h,!1,!1);Ej(this,b);if(e.backgroundFill||e.backgroundStroke)this.Oa(e.backgroundFill,e.backgroundStroke),Kj(this,this.state,
+this.Ah,a),Lj(this,this.state,this.pd);Uj(this,l,c,g)}Fj(this,b)}};
+Sj.prototype.Y=function(a,b,c,d){var e=d+b+a+c+this.pixelRatio;if(!ii.a.hasOwnProperty(e)){var f=d?this.ab[d]||this.g:null,g=c?this.o[c]||this.c:null,h=this.s[b]||this.f,l=h.scale*this.pixelRatio,m=vj[h.textAlign||"center"];b=d&&f.lineWidth?f.lineWidth:0;a=a.split("\n");var n=a.length,p=[],q=h.font;var r=a.length;var u=0;var v;for(v=0;v<r;++v){var z=pi(q,a[v]);u=Math.max(u,z);p.push(z)}r=u;q=oi(h.font);r=hg(Math.ceil((r+b)*l),Math.ceil((q*n+b)*l));u=r.canvas;ii.set(e,u);1!=l&&r.scale(l,l);r.font=
+h.font;d&&(r.strokeStyle=f.strokeStyle,r.lineWidth=b*(kd?l:1),r.lineCap=f.lineCap,r.lineJoin=f.lineJoin,r.miterLimit=f.miterLimit,od&&f.lineDash.length&&(r.setLineDash(f.lineDash),r.lineDashOffset=f.lineDashOffset));c&&(r.fillStyle=g.fillStyle);r.textBaseline="middle";r.textAlign="center";f=.5-m;g=m*u.width/l+f*b;if(d)for(d=0;d<n;++d)r.strokeText(a[d],g+f*p[d],.5*(b+q)+d*q);if(c)for(d=0;d<n;++d)r.fillText(a[d],g+f*p[d],.5*(b+q)+d*q)}return ii.get(e)};
+function Uj(a,b,c,d){var e=a.f,f=a.g,g=a.pixelRatio,h=vj[e.textAlign||"center"],l=vj[e.textBaseline];f=f&&f.lineWidth?f.lineWidth:0;h=h*b.width/g+2*(.5-h)*f;l=l*b.height/g+2*(.5-l)*f;a.a.push([6,c,d,b,(h-a.D)*g,(l-a.l)*g,a.N,b.height,1,0,0,a.C,a.B,1,!0,b.width,e.padding==hi?hi:e.padding.map(function(a){return a*g}),!!e.backgroundFill,!!e.backgroundStroke]);a.b.push([6,c,d,b,(h-a.D)*g,(l-a.l)*g,a.N,b.height,1,0,0,a.C,a.B,1/g,!0,b.width,e.padding,!!e.backgroundFill,!!e.backgroundStroke])}
+function Tj(a,b,c,d){var e=a.g,f=a.f,g=a.c,h=a.V;e&&(h in a.ab||(a.ab[h]={strokeStyle:e.strokeStyle,lineCap:e.lineCap,lineDashOffset:e.lineDashOffset,lineWidth:e.lineWidth,lineJoin:e.lineJoin,miterLimit:e.miterLimit,lineDash:e.lineDash}));var l=a.j;a.j in a.s||(a.s[a.j]={font:f.font,textAlign:f.textAlign||"center",scale:f.scale});var m=a.v;g&&(m in a.o||(a.o[m]={fillStyle:g.fillStyle}));var n=a.pixelRatio;g=vj[f.textBaseline];var p=a.l*n,q=a.ta,r=f.font,u=f.scale;e=e?e.lineWidth*u/2:0;var v=a.ua[r];
+v||(a.ua[r]=v={});a.a.push([5,b,c,g,d,f.overflow,m,f.maxAngle,function(a){var b=v[a];b||(b=v[a]=pi(r,a));return b*u*n},p,h,e*n,q,l,1]);a.b.push([5,b,c,g,d,f.overflow,m,f.maxAngle,function(a){var b=v[a];b||(b=v[a]=pi(r,a));return b*u},p,h,e,q,l,1/n])}
+Sj.prototype.nb=function(a,b){var c,d;if(a){this.N=b;(d=a.Fa())?(b=this.c,b||(b=this.c={}),b.fillStyle=zi(d.b||ei)):b=this.c=null;if(c=a.Ga()){d=this.g;d||(d=this.g={});var e=c.g,f=c.i,g=c.c,h=c.l;d.lineCap=c.f||"round";d.lineDash=e?e.slice():fi;d.lineDashOffset=void 0===f?0:f;d.lineJoin=c.j||"round";d.lineWidth=void 0===g?1:g;d.miterLimit=void 0===h?10:h;d.strokeStyle=zi(c.a||gi)}else d=this.g=null;c=this.f;e=a.a||"10px sans-serif";ni(e);f=a.b;c.overflow=a.v;c.font=e;c.maxAngle=a.s;c.placement=a.o;
+c.textAlign=a.f;c.textBaseline=a.j||"middle";c.backgroundFill=a.N;c.backgroundStroke=a.D;c.padding=a.C||hi;c.scale=void 0===f?1:f;e=a.g;f=a.c;g=a.l;h=a.i;this.ta=a.Ka()||"";this.D=void 0===e?0:e;this.l=void 0===f?0:f;this.C=void 0===g?!1:g;this.B=void 0===h?0:h;this.V=d?("string"==typeof d.strokeStyle?d.strokeStyle:x(d.strokeStyle))+d.lineCap+d.lineDashOffset+"|"+d.lineWidth+d.lineJoin+d.miterLimit+"["+d.lineDash.join()+"]":"";this.j=c.font+c.scale+(c.textAlign||"?");this.v=b?"string"==typeof b.fillStyle?
+b.fillStyle:"|"+x(b.fillStyle):""}else this.ta=""};function Vj(a,b,c,d,e,f,g){this.a=f;this.g=null;this.o=a;this.c=b;this.l=e;this.s=d;this.v=c;this.i=g;this.b={};this.f=hg(1,1);this.j=We()}w(Vj,sj);var Wj={0:[[!0]]};function Xj(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 Yj(a){if(void 0!==Wj[a])return Wj[a];for(var b=2*a+1,c=Array(b),d=0;d<b;d++)c[d]=Array(b);b=a;for(var e=d=0;b>=d;)Xj(c,a+b,a+d),Xj(c,a+d,a+b),Xj(c,a-d,a+b),Xj(c,a-b,a+d),Xj(c,a-b,a-d),Xj(c,a-d,a-b),Xj(c,a+d,a-b),Xj(c,a+b,a-d),d++,e+=1+2*d,0<2*(e-b)+1&&(--b,e+=1-2*b);return Wj[a]=c}k=Vj.prototype;k.Vb=function(a){var b=null;this.a&&(a?(b=this.g,b[4]++):(b=this.g=Da(),b.push(1)));return b};function Zj(a){for(var b in a.b){var c=a.b[b],d;for(d in c)c[d].bf()}}
+k.wa=function(a,b,c,d,e,f,g){function h(a){for(var b=n.getImageData(0,0,l,l).data,c=0;c<l;c++)for(var d=0;d<l;d++)if(q[c][d]&&0<b[4*(d*l+c)+3]){if(!r||"Image"!=z&&"Text"!=z||-1!==r.indexOf(a))var e=f(a);if(e)return e;n.clearRect(0,0,l,l);return}}d=Math.round(d);var l=2*d+1,m=ef(this.j,d+.5,d+.5,1/b,-1/b,-c,-a[0],-a[1]),n=this.f;n.canvas.width!==l||n.canvas.height!==l?(n.canvas.width=l,n.canvas.height=l):n.clearRect(0,0,l,l);if(void 0!==this.i){var p=Da();Ea(p,a);Fa(p,b*(this.i+d),p)}var q=Yj(d),r;
+this.a&&(r=this.a.all().map(function(a){return a.value}));a=Object.keys(this.b).map(Number);a.sort(dc);for(b=a.length-1;0<=b;--b){var u=a[b].toString();var v=this.b[u];for(d=uj.length-1;0<=d;--d){var z=uj[d];var A=v[z];if(void 0!==A)if(!g||"Image"!=z&&"Text"!=z){if(A=Ij(A,n,m,c,e,h,p))return A}else{var E=g[u];E?E.push(A,m.slice(0)):g[u]=[A,m.slice(0)]}}}};function ak(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];Te(a,0,8,2,b,a);return a}
+k.Ja=function(a,b){var c=void 0!==a?a.toString():"0";a=this.b[c];void 0===a&&(a={},this.b[c]=a);c=a[b];void 0===c&&(c=new bk[b](this.o,this.c,this.v,this.s,this.l,this.a),a[b]=c);return c};k.yg=function(){return nb(this.b)};
+k.Na=function(a,b,c,d,e,f){var g=Object.keys(this.b).map(Number);g.sort(dc);a.save();var h=ak(this,b);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();e=e?e:uj;var l,m;h=0;for(l=g.length;h<l;++h){var n=g[h].toString();var p=this.b[n];var q=0;for(m=e.length;q<m;++q){var r=e[q];var u=p[r];void 0!==u&&(!f||"Image"!=r&&"Text"!=r?u.Na(a,b,c,d):(r=f[n])?r.push(u,b.slice(0)):f[n]=[u,b.slice(0)])}}a.restore()};
+var bk={Circle:Pj,Default:wj,Image:Mj,LineString:Nj,Polygon:Pj,Text:Sj};function ck(a,b){return x(a)-x(b)}function dk(a,b){a=.5*a/b;return a*a}function ek(a,b,c,d,e,f){var g=!1,h;if(h=c.Y()){var l=h.gf();2==l||3==l?h.Yj(e,f):(0==l&&h.load(),h.gi(e,f),g=!0)}if(e=(0,c.cb)(b))if(d=e.Wd(d),c.Ie())fk(a,d,c,b);else(0,gk[d.S()])(a,d,c,b);return g}function fk(a,b,c,d){if("GeometryCollection"==b.S()){b=b.vd();for(var e=0,f=b.length;e<f;++e)fk(a,b[e],c,d)}else a.Ja(c.Ba(),"Default").Hh(b,d,c.Ie())}
+var gk={Point:function(a,b,c,d){var e=c.Y();if(e){if(2!=e.gf())return;var f=a.Ja(c.Ba(),"Image");f.Zb(e,a.Vb(!1));f.yc(b,d)}if(f=c.Ka())c=a.Ja(c.Ba(),"Text"),c.nb(f,a.Vb(!!e)),c.Wb(b,d)},LineString:function(a,b,c,d){var e=c.Ga();if(e){var f=a.Ja(c.Ba(),"LineString");f.Oa(null,e);f.uc(b,d)}if(e=c.Ka())c=a.Ja(c.Ba(),"Text"),c.nb(e,a.Vb(!1)),c.Wb(b,d)},Polygon:function(a,b,c,d){var e=c.Fa(),f=c.Ga();if(e||f){var g=a.Ja(c.Ba(),"Polygon");g.Oa(e,f);g.zc(b,d)}if(e=c.Ka())c=a.Ja(c.Ba(),"Text"),c.nb(e,a.Vb(!1)),
+c.Wb(b,d)},MultiPoint:function(a,b,c,d){var e=c.Y();if(e){if(2!=e.gf())return;var f=a.Ja(c.Ba(),"Image");f.Zb(e,a.Vb(!1));f.wc(b,d)}if(f=c.Ka())c=a.Ja(c.Ba(),"Text"),c.nb(f,a.Vb(!!e)),c.Wb(b,d)},MultiLineString:function(a,b,c,d){var e=c.Ga();if(e){var f=a.Ja(c.Ba(),"LineString");f.Oa(null,e);f.vc(b,d)}if(e=c.Ka())c=a.Ja(c.Ba(),"Text"),c.nb(e,a.Vb(!1)),c.Wb(b,d)},MultiPolygon:function(a,b,c,d){var e=c.Fa(),f=c.Ga();if(f||e){var g=a.Ja(c.Ba(),"Polygon");g.Oa(e,f);g.xc(b,d)}if(e=c.Ka())c=a.Ja(c.Ba(),
+"Text"),c.nb(e,a.Vb(!1)),c.Wb(b,d)},GeometryCollection:function(a,b,c,d){b=b.a;var e;var f=0;for(e=b.length;f<e;++f)(0,gk[b[f].S()])(a,b[f],c,d)},Circle:function(a,b,c,d){var e=c.Fa(),f=c.Ga();if(e||f){var g=a.Ja(c.Ba(),"Circle");g.Oa(e,f);g.cc(b,d)}if(e=c.Ka())c=a.Ja(c.Ba(),"Text"),c.nb(e,a.Vb(!1)),c.Wb(b,d)}};function hk(a){Xi.call(this,a);this.f=a.D?rj.Jc(9):null;this.i=!1;this.N=-1;this.o=NaN;this.l=Da();this.c=this.v=null;this.j=!0;this.context=hg();y(ii,"clear",this.Vi,this)}w(hk,Xi);hk.handles=function(a,b){return"canvas"===a&&"VECTOR"===b.S()};hk.create=function(a,b){return new hk(b)};k=hk.prototype;k.ia=function(){Mc(ii,"clear",this.Vi,this);Xi.prototype.ia.call(this)};
+k.df=function(a,b,c){var d=a.extent,e=a.pixelRatio,f=b.Te?a.skippedFeatureUids:{},g=a.viewState,h=g.projection,l=g.rotation,m=h.G(),n=this.a.ha(),p=$i(this,a,0);Zi(this,"precompose",c,a,p);var q=b.extent;(g=void 0!==q)&&Yi(c,a,q);var r=this.c;if(r&&!r.yg()){this.f&&this.f.clear();var u=q=0,v=1!==b.opacity,z=Tc(this.a,"render");if(v||z){var A=c.canvas.width;var E=c.canvas.height;if(l){var S=Math.round(Math.sqrt(A*A+E*E));q=(S-A)/2;u=(S-E)/2;A=E=S}this.context.canvas.width=A;this.context.canvas.height=
+E;A=this.context}else A=c;E=A.globalAlpha;v||(A.globalAlpha=b.opacity);A!=c&&A.translate(q,u);S=a.size[0]*e;e*=a.size[1];qi(A,-l,S/2,e/2);r.Na(A,p,l,f);if(n.D&&h.g&&!La(m,d)){h=d[0];n=cb(m);for(var Ia=0;h<m[0];)--Ia,p=n*Ia,p=$i(this,a,p),r.Na(A,p,l,f),h+=n;Ia=0;for(h=d[2];h>m[2];)++Ia,p=n*Ia,p=$i(this,a,p),r.Na(A,p,l,f),h-=n;p=$i(this,a,0)}qi(A,l,S/2,e/2);A!=c&&(z&&Zi(this,"render",A,a,p),v?(d=c.globalAlpha,c.globalAlpha=b.opacity,c.drawImage(A.canvas,-q,-u),c.globalAlpha=d):c.drawImage(A.canvas,
+-q,-u),A.translate(-q,-u));v||(A.globalAlpha=E)}g&&c.restore();this.pf(c,a,b,p)};k.wa=function(a,b,c,d,e){if(this.c){var f=this.a,g={};return this.c.wa(a,b.viewState.resolution,b.viewState.rotation,c,{},function(a){var b=x(a).toString();if(!(b in g))return g[b]=!0,d.call(e,a,f)},null)}};k.Vi=function(){var a=this.a;a.Jb()&&this.c&&a.u()};k.Wi=function(){Mi(this)};
+k.$c=function(a){var b=this.a,c=b.ha();Ui(a,c);var d=a.viewHints[0],e=a.viewHints[1],f=b.ca,g=b.ra;if(!this.i&&!f&&d||!g&&e)return!0;f=a.extent;var h=a.viewState;g=h.projection;var l=h.resolution,m=a.pixelRatio;d=b.g;var n=b.f;e=b.get(ik);void 0===e&&(e=ck);f=Fa(f,n*l);n=h.projection.G();c.D&&h.projection.g&&!La(n,a.extent)&&(a=Math.max(cb(f)/2,cb(n)),f[0]=n[0]-a,f[2]=n[2]+a);if(!this.i&&this.o==l&&this.N==d&&this.v==e&&La(this.l,f))return this.j=!1,!0;this.c=null;this.i=!1;var p=new Vj(.5*l/m,f,
+l,m,c.$,this.f,b.f);c.ae(f,l,g);a=function(a){var c=a.ib();if(c)var d=c.call(a,l);else(c=b.ib())&&(d=c(a,l));if(d){if(d){c=!1;if(Array.isArray(d))for(var e=0,f=d.length;e<f;++e)c=ek(p,a,d[e],dk(l,m),this.Wi,this)||c;else c=ek(p,a,d,dk(l,m),this.Wi,this);a=c}else a=!1;this.i=this.i||a}}.bind(this);if(e){var q=[];c.ec(f,function(a){q.push(a)},this);q.sort(e);c=0;for(g=q.length;c<g;++c)a(q[c])}else c.ec(f,a,this);Zj(p);this.o=l;this.N=d;this.v=e;this.l=f;this.c=p;return this.j=!0};function jk(a){this.context=null;mj.call(this,a);this.N=a.D?rj.Jc(9):null;this.D=!1;this.ca=We();this.O="vector"==a.l?1:0;y(ii,"clear",this.Xi,this)}w(jk,mj);jk.handles=function(a,b){return"canvas"===a&&"VECTOR_TILE"===b.S()};jk.create=function(a,b){return new jk(b)};var kk={image:["Polygon","Circle","LineString","Image","Text"],hybrid:["Polygon","LineString"]},lk={image:["Default"],hybrid:["Image","Text","Default"],vector:uj};k=jk.prototype;k.ia=function(){Mc(ii,"clear",this.Xi,this);mj.prototype.ia.call(this)};
+k.$c=function(a,b){var c=this.a,d=c.g;this.B!=d&&(this.f.length=0,c=c.l,this.context||"vector"==c||(this.context=hg()),this.context&&"vector"==c&&(this.context=null));this.B=d;return mj.prototype.$c.apply(this,arguments)};
+k.Sf=function(a,b,c,d,e,f,g,h,l){var m=a,n=this.a,p=b.pixelRatio,q=b.viewState.projection,r=n.g,u=n.get(ik)||null,v=mk(m,n);if(v.Be||v.wf!=r||v.eh!=u){var z=n.ha(),A=z.tileGrid,E=z.eb(q),S=E.Ta(m.ya[0]);E=E.Ma(m.l);for(var Ia=0,ta=m.a.length;Ia<ta;++Ia){var la=m.c[m.a[Ia]];if(3!=la.getState()){var ca=A.Ma(la.ya),ia=gb(E,ca),xa=Sa(ca,ia)?null:Fa(ia,n.f*S),Va=la.o,ic=!1;Xb(q,Va)||(ic=!0,la.vg(q));v.Be=!1;ia=new Vj(0,ia,S,p,z.s,this.N,n.f);var Xa=dk(S,p),Z=la.a;u&&u!==v.eh&&Z.sort(u);for(var Zb,Le=0,
+Uf=Z.length;Le<Uf;++Le)if(Zb=Z[Le],ic&&("tile-pixels"==Va.a&&(Va.Sj(ca),Va.Si(la.G())),Zb.U().mb(Va,q)),!xa||hb(xa,Zb.U().G())){var Id=void 0,te=Zb.ib();te?Id=te.call(Zb,S):(te=n.ib())&&(Id=te(Zb,S));if(Id){te=Xa;var Jh=ia;if(Id){var Mg=!1;if(Array.isArray(Id))for(var Ng=0,Wf=Id.length;Ng<Wf;++Ng)Mg=ek(Jh,Zb,Id[Ng],te,this.Yi,this)||Mg;else Mg=ek(Jh,Zb,Id,te,this.Yi,this);Zb=Mg}else Zb=!1;this.D=this.D||Zb;v.Be=v.Be||Zb}}Zj(ia);for(var rf in ia.b);ca=m.ya.toString();xa=ia;la.f[x(n)+","+ca]=xa}}v.wf=
+r;v.eh=u}if(this.context){v=b;n=this.a;q=mk(m,n);r=n.g;if((p=kk[n.l])&&q.fh!==r)for(q.fh=r,z=m.l,S=z[0],q=v.pixelRatio,rf=n.ha(),A=rf.eb(v.viewState.projection),r=A.Ta(S),u=nk(m,n),v=rf.Zd(S,q,v.viewState.projection),u.canvas.width=v[0],u.canvas.height=v[1],v=A.Ma(z),z=0,A=m.a.length;z<A;++z)S=m.c[m.a[z]],3!=S.getState()&&(rf=q/r,E=Xe(this.ca),cf(E,rf,-rf),df(E,-v[0],-v[3]),ok(S,n,m.ya.toString()).Na(u,E,0,{},p));mj.prototype.Sf.apply(this,arguments)}};
+k.wa=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.f;b=h.ha().eb(b.viewState.projection);var n;var p=0;for(n=m.length;p<n;++p){var q=m[p];var r=q.l;r=b.Ma(r,this.T);var u=Fa(r,c*f,u);if(Ja(u,a)){r=0;for(var v=q.a.length;r<v;++r){var z=q.c[q.a[r]];if(3!=z.getState()){z=ok(z,h,q.ya.toString());var A=A||z.wa(a,f,g,c,{},function(a){var b=x(a).toString();if(!(b in l))return l[b]=!0,d.call(e,a,h)},null)}}}}return A};
+k.Xi=function(){var a=this.a;a.Jb()&&void 0!==this.B&&a.u()};k.Yi=function(){Mi(this)};
+k.pf=function(a,b,c){var d=this.a,e=d.D?{}:null,f=d.ha(),g=d.l,h=lk[g],l=b.pixelRatio,m=b.viewState.rotation,n=b.size;if(m){var p=Math.round(l*n[0]/2);var q=Math.round(l*n[1]/2);qi(a,-m,p,q)}e&&this.N.clear();l=this.f;f=f.eb(b.viewState.projection);n=[];for(var r=[],u=l.length-1;0<=u;--u){var v=l[u];if(5!=v.getState())for(var z=v.ya,A=f.Ma(z)[0]-f.Ma(v.l)[0],E=void 0,S=0,Ia=v.a.length;S<Ia;++S){var ta=v.c[v.a[S]];if(3!=ta.getState()){var la=ok(ta,d,z.toString()),ca;if(!(ca="vector"==g))a:{ca=void 0;
+for(ca in la.b)for(var ia=la.b[ca],xa=0,Va=h.length;xa<Va;++xa)if(h[xa]in ia){ca=!0;break a}ca=!1}if(ca){E||(E=$i(this,b,A));ta=ta.ya[0];ca=ak(la,E);a.save();a.globalAlpha=c.opacity;ia=0;for(xa=n.length;ia<xa;++ia)Va=n[ia],ta<r[ia]&&(a.beginPath(),a.moveTo(ca[0],ca[1]),a.lineTo(ca[2],ca[3]),a.lineTo(ca[4],ca[5]),a.lineTo(ca[6],ca[7]),a.moveTo(Va[6],Va[7]),a.lineTo(Va[4],Va[5]),a.lineTo(Va[2],Va[3]),a.lineTo(Va[0],Va[1]),a.clip());la.Na(a,E,m,{},h,e);a.restore();n.push(ca);r.push(ta)}}}}if(e)for(d=
+a,g=Object.keys(e).map(Number).sort(dc),h={},l=0,f=g.length;l<f;++l)for(n=e[g[l].toString()],r=0,u=n.length;r<u;)v=n[r++],z=n[r++],v.Na(d,z,m,h);m&&qi(a,m,p,q);mj.prototype.pf.apply(this,arguments)};qg("MAP_RENDERER",kj);rg([bj,mj,hk,jk]);function H(a){a=kb({},a);delete a.renderer;a.controls||(a.controls=Fg());a.interactions||(a.interactions=Zh());G.call(this,a)}w(H,G);function pk(a){Vc.call(this);a=a?a:{};this.a=null;y(this,Xc(qk),this.$m,this);this.rg(void 0!==a.tracking?a.tracking:!1)}w(pk,Vc);k=pk.prototype;k.ia=function(){this.rg(!1);Vc.prototype.ia.call(this)};
+k.Dp=function(a){if(null!==a.alpha){var b=va(a.alpha);this.set(rk,b);"boolean"===typeof a.absolute&&a.absolute?this.set(sk,b):"number"===typeof a.webkitCompassHeading&&-1!=a.webkitCompassAccuracy&&this.set(sk,va(a.webkitCompassHeading))}null!==a.beta&&this.set(tk,va(a.beta));null!==a.gamma&&this.set(uk,va(a.gamma));this.u()};k.Ym=function(){return this.get(rk)};k.ll=function(){return this.get(tk)};k.ql=function(){return this.get(uk)};k.Zm=function(){return this.get(sk)};k.li=function(){return this.get(qk)};
+k.$m=function(){if(qd){var a=this.li();a&&!this.a?this.a=y(window,"deviceorientation",this.Dp,this):a||null===this.a||(Gc(this.a),this.a=null)}};k.rg=function(a){this.set(qk,a)};var rk="alpha",tk="beta",uk="gamma",sk="heading",qk="tracking";function vk(a){this.i=a.opacity;this.s=a.rotateWithView;this.f=a.rotation;this.a=a.scale;this.v=a.snapToPixel}k=vk.prototype;k.hf=function(){return this.i};k.jf=function(){return this.s};k.kf=function(){return this.f};k.lf=function(){return this.a};k.Ke=function(){return this.v};k.Ed=function(a){this.i=a};k.mf=function(a){this.f=a};k.Fd=function(a){this.a=a};function wk(a){this.D=this.o=this.c=null;this.Xa=void 0!==a.fill?a.fill:null;this.oa=[0,0];this.l=a.points;this.b=void 0!==a.radius?a.radius:a.radius1;this.g=a.radius2;this.j=void 0!==a.angle?a.angle:0;this.Ya=void 0!==a.stroke?a.stroke:null;this.B=this.qa=this.C=null;this.N=a.atlasManager;xk(this,this.N);vk.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})}
+w(wk,vk);k=wk.prototype;k.clone=function(){var a=new wk({fill:this.Fa()?this.Fa().clone():void 0,points:this.l,radius:this.b,radius2:this.g,angle:this.j,snapToPixel:this.v,stroke:this.Ga()?this.Ga().clone():void 0,rotation:this.f,rotateWithView:this.s,atlasManager:this.N});a.Ed(this.i);a.Fd(this.a);return a};k.Vc=function(){return this.C};k.ij=function(){return this.j};k.Fa=function(){return this.Xa};k.Eg=function(){return this.D};k.Y=function(){return this.o};k.He=function(){return this.B};
+k.gf=function(){return 2};k.bd=function(){return this.oa};k.jj=function(){return this.l};k.kj=function(){return this.b};k.Zh=function(){return this.g};k.oc=function(){return this.qa};k.Ga=function(){return this.Ya};k.gi=function(){};k.load=function(){};k.Yj=function(){};
+function xk(a,b){var c="",d="",e=0,f=null,g=0,h=0;if(a.Ya){var l=a.Ya.a;null===l&&(l=gi);l=zi(l);h=a.Ya.c;void 0===h&&(h=1);f=a.Ya.g;g=a.Ya.i;od||(f=null,g=0);d=a.Ya.j;void 0===d&&(d="round");c=a.Ya.f;void 0===c&&(c="round");e=a.Ya.l;void 0===e&&(e=10)}var m=2*(a.b+h)+1;c={strokeStyle:l,Wj:h,size:m,lineCap:c,lineDash:f,lineDashOffset:g,lineJoin:d,miterLimit:e};if(void 0===b){var n=hg(m,m);a.o=n.canvas;b=m=a.o.width;a.Jh(c,n,0,0);a.Xa?a.D=a.o:(n=hg(c.size,c.size),a.D=n.canvas,a.Ih(c,n,0,0))}else m=
+Math.round(m),(d=!a.Xa)&&(n=a.Ih.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+x(e.a).toString():e.b+"-",e.b+=","+(void 0!==e.f?e.f.toString():"-")+","+(e.g?e.g.toString():"-")+","+(void 0!==e.i?e.i:"-")+","+(void 0!==e.j?e.j:"-")+","+(void 0!==e.l?e.l.toString():"-")+","+(void 0!==e.c?e.c.toString():"-")),e=e.b):e="-",a.Xa?(f=a.Xa,void 0===f.a&&(f.a=f.b instanceof CanvasPattern||f.b instanceof CanvasGradient?x(f.b).toString():"f"+(f.b?xi(f.b):"-")),f=f.a):
+f="-",a.c&&e==a.c[1]&&f==a.c[2]&&a.b==a.c[3]&&a.g==a.c[4]&&a.j==a.c[5]&&a.l==a.c[6]||(a.c=["r"+e+f+(void 0!==a.b?a.b.toString():"-")+(void 0!==a.g?a.g.toString():"-")+(void 0!==a.j?a.j.toString():"-")+(void 0!==a.l?a.l.toString():"-"),e,f,a.b,a.g,a.j,a.l]),n=b.add(a.c[0],m,m,a.Jh.bind(a,c),n),a.o=n.image,a.oa=[n.offsetX,n.offsetY],b=n.image.width,a.D=d?n.Bm:a.o;a.C=[m/2,m/2];a.qa=[m,m];a.B=[b,b]}
+k.Jh=function(a,b,c,d){b.setTransform(1,0,0,1,0,0);b.translate(c,d);b.beginPath();var e=this.l;if(Infinity===e)b.arc(a.size/2,a.size/2,this.b,0,2*Math.PI,!0);else{var f=void 0!==this.g?this.g: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=0===c%2?this.b:f;b.lineTo(a.size/2+g*Math.cos(d),a.size/2+g*Math.sin(d))}}this.Xa&&(c=this.Xa.b,null===c&&(c=ei),b.fillStyle=zi(c),b.fill());this.Ya&&(b.strokeStyle=a.strokeStyle,b.lineWidth=a.Wj,a.lineDash&&(b.setLineDash(a.lineDash),
+b.lineDashOffset=a.lineDashOffset),b.lineCap=a.lineCap,b.lineJoin=a.lineJoin,b.miterLimit=a.miterLimit,b.stroke());b.closePath()};
+k.Ih=function(a,b,c,d){b.setTransform(1,0,0,1,0,0);b.translate(c,d);b.beginPath();c=this.l;if(Infinity===c)b.arc(a.size/2,a.size/2,this.b,0,2*Math.PI,!0);else{d=void 0!==this.g?this.g: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=0===e%2?this.b:d;b.lineTo(a.size/2+g*Math.cos(f),a.size/2+g*Math.sin(f))}}b.fillStyle=ei;b.fill();this.Ya&&(b.strokeStyle=a.strokeStyle,b.lineWidth=a.Wj,a.lineDash&&(b.setLineDash(a.lineDash),b.lineDashOffset=a.lineDashOffset),
+b.stroke());b.closePath()};function yk(a){a=a||{};wk.call(this,{points:Infinity,fill:a.fill,radius:a.radius,snapToPixel:a.snapToPixel,stroke:a.stroke,atlasManager:a.atlasManager})}w(yk,wk);yk.prototype.clone=function(){var a=new yk({fill:this.Fa()?this.Fa().clone():void 0,stroke:this.Ga()?this.Ga().clone():void 0,radius:this.b,snapToPixel:this.v,atlasManager:this.N});a.Ed(this.i);a.Fd(this.a);return a};yk.prototype.fd=function(a){this.b=a;xk(this,this.N)};function zk(a){a=a||{};this.b=void 0!==a.color?a.color:null;this.a=void 0}zk.prototype.clone=function(){var a=this.b;return new zk({color:a&&a.slice?a.slice():a||void 0})};zk.prototype.g=function(){return this.b};zk.prototype.c=function(a){this.b=a;this.a=void 0};function Ak(a){a=a||{};this.a=void 0!==a.color?a.color:null;this.f=a.lineCap;this.g=void 0!==a.lineDash?a.lineDash:null;this.i=a.lineDashOffset;this.j=a.lineJoin;this.l=a.miterLimit;this.c=a.width;this.b=void 0}k=Ak.prototype;k.clone=function(){var a=this.a;return new Ak({color:a&&a.slice?a.slice():a||void 0,lineCap:this.f,lineDash:this.g?this.g.slice():void 0,lineDashOffset:this.i,lineJoin:this.j,miterLimit:this.l,width:this.c})};k.pp=function(){return this.a};k.vl=function(){return this.f};
+k.qp=function(){return this.g};k.wl=function(){return this.i};k.xl=function(){return this.j};k.Dl=function(){return this.l};k.rp=function(){return this.c};k.sp=function(a){this.a=a;this.b=void 0};k.yq=function(a){this.f=a;this.b=void 0};k.setLineDash=function(a){this.g=a;this.b=void 0};k.zq=function(a){this.i=a;this.b=void 0};k.Aq=function(a){this.j=a;this.b=void 0};k.Eq=function(a){this.l=a;this.b=void 0};k.Kq=function(a){this.c=a;this.b=void 0};function Bk(a){a=a||{};this.Uc=null;this.cb=Ck;void 0!==a.geometry&&this.Va(a.geometry);this.Xa=void 0!==a.fill?a.fill:null;this.M=void 0!==a.image?a.image:null;this.pc=void 0!==a.renderer?a.renderer:null;this.Ya=void 0!==a.stroke?a.stroke:null;this.ta=void 0!==a.text?a.text:null;this.bk=a.zIndex}k=Bk.prototype;
+k.clone=function(){var a=this.U();a&&a.clone&&(a=a.clone());return new Bk({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.Ka()?this.Ka().clone():void 0,zIndex:this.Ba()})};k.Ie=function(){return this.pc};k.Iq=function(a){this.pc=a};k.U=function(){return this.Uc};k.rl=function(){return this.cb};k.Fa=function(){return this.Xa};k.yf=function(a){this.Xa=a};k.Y=function(){return this.M};
+k.ih=function(a){this.M=a};k.Ga=function(){return this.Ya};k.Af=function(a){this.Ya=a};k.Ka=function(){return this.ta};k.Hd=function(a){this.ta=a};k.Ba=function(){return this.bk};k.Va=function(a){"function"===typeof a?this.cb=a:"string"===typeof a?this.cb=function(b){return b.get(a)}:a?void 0!==a&&(this.cb=function(){return a}):this.cb=Ck;this.Uc=a};k.$b=function(a){this.bk=a};
+function Dk(a){if("function"!==typeof a){if(Array.isArray(a))var b=a;else oa(a instanceof Bk,41),b=[a];a=function(){return b}}return a}var Ek=null;function Fk(){if(!Ek){var a=new zk({color:"rgba(255,255,255,0.4)"}),b=new Ak({color:"#3399CC",width:1.25});Ek=[new Bk({image:new yk({fill:a,stroke:b,radius:5}),fill:a,stroke:b})]}return Ek}
+function Gk(){var a={},b=[255,255,255,1],c=[0,153,255,1];a.Polygon=[new Bk({fill:new zk({color:[255,255,255,.5]})})];a.MultiPolygon=a.Polygon;a.LineString=[new Bk({stroke:new Ak({color:b,width:5})}),new Bk({stroke:new Ak({color:c,width:3})})];a.MultiLineString=a.LineString;a.Circle=a.Polygon.concat(a.LineString);a.Point=[new Bk({image:new yk({radius:6,fill:new zk({color:c}),stroke:new Ak({color:b,width:1.5})}),zIndex:Infinity})];a.MultiPoint=a.Point;a.GeometryCollection=a.Polygon.concat(a.LineString,
+a.Point);return a}function Ck(a){return a.U()};function Hk(a){Vc.call(this);this.c=void 0;this.a="geometry";this.f=null;this.j=void 0;this.i=null;y(this,Xc(this.a),this.Oe,this);void 0!==a&&(a instanceof gf||!a?this.Va(a):this.H(a))}w(Hk,Vc);k=Hk.prototype;k.clone=function(){var a=new Hk(this.L());a.Lc(this.a);var b=this.U();b&&a.Va(b.clone());(b=this.f)&&a.sg(b);return a};k.U=function(){return this.get(this.a)};k.an=function(){return this.c};k.sl=function(){return this.a};k.bn=function(){return this.f};k.ib=function(){return this.j};k.bm=function(){this.u()};
+k.Oe=function(){this.i&&(Gc(this.i),this.i=null);var a=this.U();a&&(this.i=y(a,"change",this.bm,this));this.u()};k.Va=function(a){this.set(this.a,a)};k.sg=function(a){this.j=(this.f=a)?Ik(a):void 0;this.u()};k.qc=function(a){this.c=a;this.u()};k.Lc=function(a){Mc(this,Xc(this.a),this.Oe,this);this.a=a;y(this,Xc(this.a),this.Oe,this);this.Oe()};
+function Ik(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 oa(a instanceof Bk,41),c=[a];b=function(){return c}}return b};function Jk(a){Vc.call(this);a=a||{};this.a=null;this.i=$b;this.f=new ob(6378137);this.c=void 0;y(this,Xc("projection"),this.en,this);y(this,Xc("tracking"),this.fn,this);void 0!==a.projection&&this.oi(a.projection);void 0!==a.trackingOptions&&this.Rj(a.trackingOptions);this.Ue(void 0!==a.tracking?a.tracking:!1)}w(Jk,Vc);k=Jk.prototype;k.ia=function(){this.Ue(!1);Vc.prototype.ia.call(this)};k.en=function(){var a=this.mi();a&&(this.i=Pb(Ob("EPSG:4326"),a),this.a&&this.set("position",this.i(this.a)))};
+k.fn=function(){if(rd){var a=this.ni();a&&void 0===this.c?this.c=navigator.geolocation.watchPosition(this.Pp.bind(this),this.Qp.bind(this),this.ai()):a||void 0===this.c||(navigator.geolocation.clearWatch(this.c),this.c=void 0)}};
+k.Pp=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:va(a.heading));this.a?(this.a[0]=a.longitude,this.a[1]=a.latitude):this.a=[a.longitude,a.latitude];var b=this.i(this.a);this.set("position",b);this.set("speed",null===a.speed?void 0:a.speed);a=Qf(this.f,this.a,a.accuracy);a.Rc(this.i);this.set("accuracyGeometry",
+a);this.u()};k.Qp=function(a){a.type="error";this.Ue(!1);this.b(a)};k.el=function(){return this.get("accuracy")};k.fl=function(){return this.get("accuracyGeometry")||null};k.gl=function(){return this.get("altitude")};k.hl=function(){return this.get("altitudeAccuracy")};k.cn=function(){return this.get("heading")};k.dn=function(){return this.get("position")};k.mi=function(){return this.get("projection")};k.Ol=function(){return this.get("speed")};k.ni=function(){return this.get("tracking")};k.ai=function(){return this.get("trackingOptions")};
+k.oi=function(a){this.set("projection",Ob(a))};k.Ue=function(a){this.set("tracking",a)};k.Rj=function(a){this.set("trackingOptions",a)};function Kk(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(0!==l){h=a[b];l=a[b+1];var m=0;g=[0];var n;for(n=b+d;n<c;n+=d){var p=a[n],q=a[n+1];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=+dc(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=ya(a[b],a[b+d],c),h=ya(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 Sk(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(ya(a[(b-1)*d+g],a[b*d+g],f));c.push(e);return c}
+function Tk(a,b,c,d,e,f){var g=0;if(f)return Sk(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 Sk(a,g,h,c,d,!1);g=h}}return null};function I(a,b){hf.call(this);this.c=null;this.o=this.D=this.j=-1;this.na(a,b)}w(I,hf);k=I.prototype;k.Fk=function(a){this.A?gc(this.A,a):this.A=a.slice();this.u()};k.clone=function(){var a=new I(null);a.ba(this.ja,this.A.slice());return a};k.Nb=function(a,b,c,d){if(d<Ha(this.G(),a,b))return d;this.o!=this.g&&(this.D=Math.sqrt(pf(this.A,0,this.A.length,this.a,0)),this.o=this.g);return tf(this.A,0,this.A.length,this.a,this.D,!1,a,b,c,d)};
+k.dl=function(a,b){return Jf(this.A,0,this.A.length,this.a,a,b)};k.Tn=function(a,b){return"XYM"!=this.ja&&"XYZM"!=this.ja?null:Sk(this.A,0,this.A.length,this.a,a,void 0!==b?b:!1)};k.W=function(){return yf(this.A,0,this.A.length,this.a)};k.Ph=function(a,b){return Kk(this.A,0,this.A.length,this.a,a,b)};k.Un=function(){return tj(this.A,0,this.A.length,this.a)};k.Fe=function(){this.j!=this.g&&(this.c=this.Ph(.5,this.c),this.j=this.g);return this.c};
+k.xd=function(a){var b=[];b.length=Bf(this.A,0,this.A.length,this.a,a,b,0);a=new I(null);a.ba("XY",b);return a};k.S=function(){return"LineString"};k.$a=function(a){return Kf(this.A,0,this.A.length,this.a,a)};k.na=function(a,b){a?(lf(this,b,a,1),this.A||(this.A=[]),this.A.length=wf(this.A,0,a,this.a),this.u()):this.ba("XY",null)};k.ba=function(a,b){kf(this,a,b);this.u()};function Uk(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,v,z,A;0<--q&&0<n.length;)v=n.pop(),e=l.pop(),g=m.pop(),f=v.toString(),f in p||(d.push(g[0],g[1]),p[f]=!0),z=n.pop(),f=l.pop(),h=m.pop(),A=(v+z)/2,r=a(A),u=b(r),sa(u[0],u[1],g[0],g[1],h[0],h[1])<c?(d.push(h[0],h[1]),f=z.toString(),p[f]=!0):(n.push(z,A,A,v),m.push(h,u,u,g),l.push(f,r,r,e));return d}function Vk(a,b,c,d,e){var f=Ob("EPSG:4326");return Uk(function(d){return[a,b+(c-b)*d]},Yb(f,d),e)}
+function Wk(a,b,c,d,e){var f=Ob("EPSG:4326");return Uk(function(d){return[b+(c-b)*d,a]},Yb(f,d),e)};function J(a){a=a||{};this.a=a.font;this.i=a.rotation;this.l=a.rotateWithView;this.b=a.scale;this.ta=a.text;this.f=a.textAlign;this.j=a.textBaseline;this.Xa=void 0!==a.fill?a.fill:new zk({color:"#333"});this.s=void 0!==a.maxAngle?a.maxAngle:Math.PI/4;this.o=void 0!==a.placement?a.placement:"point";var b=void 0===a.overflow?a.exceedLength:a.overflow;this.v=void 0!==b?b:!1;this.Ya=void 0!==a.stroke?a.stroke:null;this.g=void 0!==a.offsetX?a.offsetX:0;this.c=void 0!==a.offsetY?a.offsetY:0;this.N=a.backgroundFill?
+a.backgroundFill:null;this.D=a.backgroundStroke?a.backgroundStroke:null;this.C=void 0===a.padding?null:a.padding}k=J.prototype;k.clone=function(){return new J({font:this.a,placement:this.o,maxAngle:this.s,overflow:this.v,rotation:this.i,rotateWithView:this.l,scale:this.b,text:this.Ka(),textAlign:this.f,textBaseline:this.j,fill:this.Fa()?this.Fa().clone():void 0,stroke:this.Ga()?this.Ga().clone():void 0,offsetX:this.g,offsetY:this.c})};k.Gl=function(){return this.v};k.pl=function(){return this.a};
+k.Bl=function(){return this.s};k.Kl=function(){return this.o};k.El=function(){return this.g};k.Fl=function(){return this.c};k.Fa=function(){return this.Xa};k.tp=function(){return this.l};k.up=function(){return this.i};k.vp=function(){return this.b};k.Ga=function(){return this.Ya};k.Ka=function(){return this.ta};k.Ql=function(){return this.f};k.Rl=function(){return this.j};k.jl=function(){return this.N};k.kl=function(){return this.D};k.Il=function(){return this.C};k.Fq=function(a){this.v=a};
+k.Jj=function(a){this.a=a};k.Bq=function(a){this.s=a};k.Nj=function(a){this.g=a};k.Oj=function(a){this.c=a};k.Hq=function(a){this.o=a};k.yf=function(a){this.Xa=a};k.wp=function(a){this.i=a};k.lj=function(a){this.b=a};k.Af=function(a){this.Ya=a};k.Hd=function(a){this.ta=a};k.Qj=function(a){this.f=a};k.Jq=function(a){this.j=a};k.sq=function(a){this.N=a};k.tq=function(a){this.D=a};k.Gq=function(a){this.C=a};function Xk(a){a=a||{};this.i=this.v=null;this.j=this.f=Infinity;this.s=this.l=-Infinity;this.qa=this.oa=Infinity;this.O=this.T=-Infinity;this.ua=void 0!==a.targetSize?a.targetSize:100;this.ab=void 0!==a.maxLines?a.maxLines:100;this.g=[];this.c=[];this.ra=void 0!==a.strokeStyle?a.strokeStyle:Yk;this.D=this.o=void 0;this.a=this.b=this.N=null;1==a.showLabels&&(this.$=void 0==a.lonLabelFormatter?Ce.bind(this,"EW"):a.lonLabelFormatter,this.Wa=void 0==a.latLabelFormatter?Ce.bind(this,"NS"):a.latLabelFormatter,
+this.ca=void 0==a.lonLabelPosition?0:a.lonLabelPosition,this.V=void 0==a.latLabelPosition?1:a.latLabelPosition,this.B=void 0!==a.lonLabelStyle?a.lonLabelStyle:new J({font:"12px Calibri,sans-serif",textBaseline:"bottom",fill:new zk({color:"rgba(0,0,0,1)"}),stroke:new Ak({color:"rgba(255,255,255,1)",width:3})}),this.C=void 0!==a.latLabelStyle?a.latLabelStyle:new J({font:"12px Calibri,sans-serif",textAlign:"end",fill:new zk({color:"rgba(0,0,0,1)"}),stroke:new Ak({color:"rgba(255,255,255,1)",width:3})}),
+this.b=[],this.a=[]);this.setMap(void 0!==a.map?a.map:null)}var Yk=new Ak({color:"rgba(0,0,0,0.2)"}),Zk=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001];function $k(a,b,c,d,e,f,g){var h=g;c=Vk(b,c,d,a.i,e);h=void 0!==a.g[h]?a.g[h]:new I(null);h.ba("XY",c);hb(h.G(),f)&&(a.b&&(c=g,d=h.da(),f=[d[0],pa(f[1]+Math.abs(f[1]-f[3])*a.ca,Math.max(f[1],d[1]),Math.min(f[3],d[d.length-1]))],c=void 0!==a.b[c]?a.b[c].Qd:new C(null),c.na(f),a.b[g]={Qd:c,text:a.$(b)}),a.g[g++]=h);return g}
+function al(a,b,c,d,e,f,g){var h=g;c=Wk(b,c,d,a.i,e);h=void 0!==a.c[h]?a.c[h]:new I(null);h.ba("XY",c);hb(h.G(),f)&&(a.a&&(c=g,d=h.da(),f=[pa(f[0]+Math.abs(f[0]-f[2])*a.V,Math.max(f[0],d[0]),Math.min(f[2],d[d.length-2])),d[1]],c=void 0!==a.a[c]?a.a[c].Qd:new C(null),c.na(f),a.a[g]={Qd:c,text:a.Wa(b)}),a.c[g++]=h);return g}k=Xk.prototype;k.gn=function(){return this.v};k.Cl=function(){return this.g};k.Jl=function(){return this.c};
+k.di=function(a){var b=a.vectorContext,c=a.frameState;a=c.extent;var d=c.viewState,e=d.center,f=d.projection;d=d.resolution;c=c.pixelRatio;c=d*d/(4*c*c);if(!this.i||!Xb(this.i,f)){var g=Ob("EPSG:4326"),h=f.G(),l=f.oe,m=bc(l,g,f),n=l[2],p=l[1],q=l[0],r=m[3],u=m[2],v=m[1];m=m[0];this.f=l[3];this.j=n;this.l=p;this.s=q;this.oa=r;this.qa=u;this.T=v;this.O=m;this.o=Yb(g,f);this.D=Yb(f,g);this.N=this.D(eb(h));this.i=f}f=this.N[0];g=this.N[1];h=-1;n=Math.pow(this.ua*d,2);p=[];q=[];d=0;for(l=Zk.length;d<l;++d){r=
+Zk[d]/2;p[0]=f-r;p[1]=g-r;q[0]=f+r;q[1]=g+r;this.o(p,p);this.o(q,q);r=Math.pow(q[0]-p[0],2)+Math.pow(q[1]-p[1],2);if(r<=n)break;h=Zk[d]}d=h;if(-1==d)this.g.length=this.c.length=0,this.b&&(this.b.length=0),this.a&&(this.a.length=0);else{f=this.D(e);e=f[0];f=f[1];g=this.ab;l=[Math.max(a[0],this.O),Math.max(a[1],this.T),Math.min(a[2],this.qa),Math.min(a[3],this.oa)];l=bc(l,this.i,"EPSG:4326");p=l[3];h=l[2];q=l[1];r=l[0];e=Math.floor(e/d)*d;u=pa(e,this.s,this.j);n=$k(this,u,q,p,c,a,0);for(l=0;u!=this.s&&
+l++<g;)u=Math.max(u-d,this.s),n=$k(this,u,q,p,c,a,n);u=pa(e,this.s,this.j);for(l=0;u!=this.j&&l++<g;)u=Math.min(u+d,this.j),n=$k(this,u,q,p,c,a,n);this.g.length=n;this.b&&(this.b.length=n);f=Math.floor(f/d)*d;e=pa(f,this.l,this.f);n=al(this,e,r,h,c,a,0);for(l=0;e!=this.l&&l++<g;)e=Math.max(e-d,this.l),n=al(this,e,r,h,c,a,n);e=pa(f,this.l,this.f);for(l=0;e!=this.f&&l++<g;)e=Math.min(e+d,this.f),n=al(this,e,r,h,c,a,n);this.c.length=n;this.a&&(this.a.length=n)}b.Oa(null,this.ra);a=0;for(c=this.g.length;a<
+c;++a)e=this.g[a],b.Hb(e);a=0;for(c=this.c.length;a<c;++a)e=this.c[a],b.Hb(e);if(this.b)for(a=0,c=this.b.length;a<c;++a)e=this.b[a],this.B.Hd(e.text),b.nb(this.B),b.Hb(e.Qd);if(this.a)for(a=0,c=this.a.length;a<c;++a)e=this.a[a],this.C.Hd(e.text),b.nb(this.C),b.Hb(e.Qd)};k.setMap=function(a){this.v&&(this.v.J("postcompose",this.di,this),this.v.render());a&&(a.I("postcompose",this.di,this),a.render());this.v=a};function bl(a,b,c,d,e,f){$h.call(this,a,b,c,0);this.i=d;this.M=new Image;null!==e&&(this.M.crossOrigin=e);this.g=null;this.state=0;this.c=f}w(bl,$h);k=bl.prototype;k.Y=function(){return this.M};k.kn=function(){this.state=3;this.g.forEach(Gc);this.g=null;this.u()};k.ln=function(){void 0===this.resolution&&(this.resolution=db(this.extent)/this.M.height);this.state=2;this.g.forEach(Gc);this.g=null;this.u()};
+k.load=function(){if(0==this.state||3==this.state)this.state=1,this.u(),this.g=[Lc(this.M,"error",this.kn,this),Lc(this.M,"load",this.ln,this)],this.c(this,this.i)};k.ih=function(a){this.M=a};function cl(a,b,c){Sc.call(this);c=c?c:{};this.ya=a;this.state=b;this.g=null;this.key="";this.j=void 0===c.transition?250:c.transition;this.s={}}w(cl,Sc);cl.prototype.u=function(){this.b("change")};cl.prototype.lb=function(){return this.key+"/"+this.ya};function pj(a){if(!a.g)return a;var b=a.g;do{if(2==b.getState())return b;b=b.g}while(b);return a}function dl(a){if(a.g){var b=a.g;do{if(2==b.getState()){b.g=null;break}else 1==b.getState()?a=b:0==b.getState()?a.g=b.g:a=b;b=a.g}while(b)}}
+cl.prototype.i=function(){return this.ya};cl.prototype.getState=function(){return this.state};function oj(a,b){a.state=b;a.u()}function qj(a,b,c){if(!a.j)return 1;var d=a.s[b];if(!d)d=c,a.s[b]=d;else if(-1===d)return 1;b=c-d+1E3/60;return b>=a.j?1:Me(b/a.j)};function el(a,b,c,d,e,f){cl.call(this,a,b,f);this.f=d;this.l=c;this.M=new Image;null!==d&&(this.M.crossOrigin=d);this.c=null;this.v=e}w(el,cl);k=el.prototype;k.ia=function(){1==this.state&&(fl(this),this.M=gl());this.g&&Pc(this.g);this.state=5;this.u();cl.prototype.ia.call(this)};k.Y=function(){return this.M};k.lb=function(){return this.l};k.hn=function(){this.state=3;fl(this);this.M=gl();this.u()};k.jn=function(){this.state=this.M.naturalWidth&&this.M.naturalHeight?2:4;fl(this);this.u()};
+k.load=function(){3==this.state&&(this.state=0,this.M=new Image,null!==this.f&&(this.M.crossOrigin=this.f));0==this.state&&(this.state=1,this.u(),this.c=[Lc(this.M,"error",this.hn,this),Lc(this.M,"load",this.jn,this)],this.v(this,this.l))};function fl(a){a.c.forEach(Gc);a.c=null}function gl(){var a=hg(1,1);a.fillStyle="rgba(0,0,0,0)";a.fillRect(0,0,1,1);return a.canvas};function hl(a){this.b=a};function il(a){this.b=a}w(il,hl);il.prototype.S=function(){return 35632};function jl(a){this.b=a}w(jl,hl);jl.prototype.S=function(){return 35633};var kl=new il("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;}}"),
+ll=new jl("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;//Until we get gl_VertexID in WebGL,we store an instruction.if(f==0.0){//Offsetting the edges of the triangle by lineWidth/2 is necessary,however//we should also leave some space for the antialiasing,thus we offset by lineWidth.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);}}");function ml(a,b){this.g=a.getUniformLocation(b,"h");this.i=a.getUniformLocation(b,"i");this.c=a.getUniformLocation(b,"j");this.oa=a.getUniformLocation(b,"k");this.qa=a.getUniformLocation(b,"l");this.a=a.getUniformLocation(b,"m");this.C=a.getUniformLocation(b,"n");this.O=a.getUniformLocation(b,"o");this.T=a.getUniformLocation(b,"p");this.b=a.getAttribLocation(b,"e");this.j=a.getAttribLocation(b,"f");this.N=a.getAttribLocation(b,"g")};function nl(){return[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}function pl(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 ql(a,b){this.origin=eb(b);this.bb=We();this.Ea=We();this.La=We();this.V=nl();this.b=[];this.j=null;this.g=[];this.i=[];this.a=[];this.s=null;this.f=void 0}w(ql,Ai);
+ql.prototype.Na=function(a,b,c,d,e,f,g,h,l,m,n){var p=a.b;if(this.f){var q=p.isEnabled(p.STENCIL_TEST);var r=p.getParameter(p.STENCIL_FUNC);var u=p.getParameter(p.STENCIL_VALUE_MASK);var v=p.getParameter(p.STENCIL_REF);var z=p.getParameter(p.STENCIL_WRITEMASK);var A=p.getParameter(p.STENCIL_FAIL);var E=p.getParameter(p.STENCIL_PASS_DEPTH_PASS);var S=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.f.Na(a,b,c,d,e,f,g,h,l,m,n);p.stencilMask(0);p.stencilFunc(p.NOTEQUAL,1,255)}rl(a,34962,this.s);rl(a,34963,this.j);f=this.Bf(p,a,e,f);var Ia=Xe(this.bb);cf(Ia,2/(c*e[0]),2/(c*e[1]));bf(Ia,-d);df(Ia,-(b[0]-this.origin[0]),-(b[1]-this.origin[1]));b=Xe(this.La);cf(b,2/e[0],2/e[1]);e=Xe(this.Ea);0!==d&&bf(e,-d);p.uniformMatrix4fv(f.g,!1,pl(this.V,Ia));p.uniformMatrix4fv(f.i,!1,pl(this.V,b));p.uniformMatrix4fv(f.c,!1,pl(this.V,e));p.uniform1f(f.a,g);if(void 0===l)this.Od(p,a,h,!1);
+else{m?a=this.Ee(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 ta=a}this.Cf(p,f);this.f&&(q||p.disable(p.STENCIL_TEST),p.clear(p.STENCIL_BUFFER_BIT),p.stencilFunc(r,v,u),p.stencilMask(z),p.stencilOp(A,S,E));return ta};function sl(a,b,c,d){a.drawElements(4,d-c,b.f?5125:5123,c*(b.f?4:2))};var tl=[0,0,0,1],ul=[],vl=[0,0,0,1];function wl(a,b,c,d,e,f){a=(c-a)*(f-b)-(e-a)*(d-b);return a<=xl&&a>=-xl?void 0:0<a}var xl=Number.EPSILON||2.220446049250313E-16;function yl(a){this.b=void 0!==a?a:[];this.a=zl}var zl=35044;function Al(a,b){ql.call(this,a,b);this.v=null;this.l=[];this.o=[];this.N=0;this.c={fillColor:null,strokeColor:null,lineDash:null,lineDashOffset:void 0,lineWidth:void 0,u:!1}}w(Al,ql);k=Al.prototype;
+k.cc=function(a,b){var c=a.Bd(),d=a.pa();if(c){this.g.push(this.b.length);this.i.push(b);this.c.u&&(this.o.push(this.b.length),this.c.u=!1);this.N=c;a=a.da();a=Ue(a,0,2,d,-this.origin[0],-this.origin[1]);b=this.a.length;c=this.b.length;var 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.N,this.a[b++]=a[f],this.a[b++]=a[f+1],this.a[b++]=1,this.a[b++]=this.N,this.a[b++]=a[f],this.a[b++]=a[f+1],this.a[b++]=2,this.a[b++]=this.N,this.a[b++]=a[f],this.a[b++]=
+a[f+1],this.a[b++]=3,this.a[b++]=this.N,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.u&&(this.l.pop(),this.l.length&&(d=this.l[this.l.length-1],this.c.fillColor=d[0],this.c.strokeColor=d[1],this.c.lineWidth=d[2],this.c.u=!1))};k.gb=function(){this.s=new yl(this.a);this.j=new yl(this.b);this.g.push(this.b.length);0===this.o.length&&0<this.l.length&&(this.l=[]);this.b=this.a=null};
+k.Db=function(a){var b=this.s,c=this.j;return function(){Bl(a,b);Bl(a,c)}};k.Bf=function(a,b,c,d){var e=Cl(b,kl,ll);if(this.v)var f=this.v;else this.v=f=new ml(a,e);b.cd(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.N);a.vertexAttribPointer(f.N,1,5126,!1,16,12);a.uniform2fv(f.T,c);a.uniform1f(f.qa,d);return f};
+k.Cf=function(a,b){a.disableVertexAttribArray(b.b);a.disableVertexAttribArray(b.j);a.disableVertexAttribArray(b.N)};
+k.Od=function(a,b,c){if(nb(c)){var d=this.g[this.g.length-1];for(c=this.o.length-1;0<=c;--c){var e=this.o[c];var f=this.l[c];a.uniform4fv(this.v.C,f[0]);Dl(this,a,f[1],f[2]);sl(a,b,e,d);d=e}}else{var g=this.g.length-2;f=d=this.g[g+1];for(e=this.o.length-1;0<=e;--e){var h=this.l[e];a.uniform4fv(this.v.C,h[0]);Dl(this,a,h[1],h[2]);for(h=this.o[e];0<=g&&this.g[g]>=h;){var l=this.g[g];var m=this.i[g];m=x(m).toString();c[m]&&(d!==f&&sl(a,b,d,f),f=l);g--;d=l}d!==f&&sl(a,b,d,f);d=f=h}}};
+k.Ee=function(a,b,c,d,e){var f,g;var h=this.g.length-2;var l=this.g[h+1];for(f=this.o.length-1;0<=f;--f){var m=this.l[f];a.uniform4fv(this.v.C,m[0]);Dl(this,a,m[1],m[2]);for(g=this.o[f];0<=h&&this.g[h]>=g;){m=this.g[h];var n=this.i[h];var p=x(n).toString();if(void 0===c[p]&&n.U()&&(void 0===e||hb(e,n.U().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),sl(a,b,m,l),l=d(n)))return l;h--;l=m}}};function Dl(a,b,c,d){b.uniform4fv(a.v.O,c);b.uniform1f(a.v.oa,d)}
+k.Oa=function(a,b){if(b){var c=b.g;this.c.lineDash=c?c:ul;c=b.i;this.c.lineDashOffset=c?c:0;c=b.a;c instanceof CanvasGradient||c instanceof CanvasPattern?c=vl:c=vi(c).map(function(a,b){return 3!=b?a/255:a})||vl;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=tl:a=vi(a).map(function(a,b){return 3!=b?a/255:a})||tl;this.c.strokeColor&&jc(this.c.strokeColor,c)&&this.c.fillColor&&jc(this.c.fillColor,a)&&this.c.lineWidth===b||(this.c.u=
+!0,this.c.fillColor=a,this.c.strokeColor=c,this.c.lineWidth=b,this.l.push([a,c,b]))};var El=new il("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;}"),Fl=new jl("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;}");function Gl(a,b){this.g=a.getUniformLocation(b,"h");this.i=a.getUniformLocation(b,"i");this.c=a.getUniformLocation(b,"j");this.a=a.getUniformLocation(b,"k");this.b=a.getAttribLocation(b,"c");this.B=a.getAttribLocation(b,"d");this.v=a.getAttribLocation(b,"e");this.o=a.getAttribLocation(b,"f");this.D=a.getAttribLocation(b,"g")};function Hl(a,b){this.j=a;this.b=b;this.a={};this.c={};this.g={};this.s=this.v=this.i=this.l=null;(this.f=ec(da,"OES_element_index_uint"))&&b.getExtension("OES_element_index_uint");y(this.j,"webglcontextlost",this.zp,this);y(this.j,"webglcontextrestored",this.Ap,this)}w(Hl,Oc);
+function rl(a,b,c){var d=a.b,e=c.b,f=String(x(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.f?new Uint32Array(e):new Uint16Array(e));d.bufferData(b,h,c.a);a.a[f]={tc:c,buffer:g}}}function Bl(a,b){var c=a.b;b=String(x(b));var d=a.a[b];c.isContextLost()||c.deleteBuffer(d.buffer);delete a.a[b]}k=Hl.prototype;
+k.ia=function(){Nc(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.g)a.deleteProgram(this.g[b]);for(b in this.c)a.deleteShader(this.c[b]);a.deleteFramebuffer(this.i);a.deleteRenderbuffer(this.s);a.deleteTexture(this.v)}};k.yp=function(){return this.b};
+function Il(a){if(!a.i){var b=a.b,c=b.createFramebuffer();b.bindFramebuffer(b.FRAMEBUFFER,c);var d=Jl(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.i=c;
+a.v=d;a.s=e}return a.i}function Kl(a,b){var c=String(x(b));if(c in a.c)return a.c[c];var d=a.b,e=d.createShader(b.S());d.shaderSource(e,b.b);d.compileShader(e);return a.c[c]=e}function Cl(a,b,c){var d=x(b)+"/"+x(c);if(d in a.g)return a.g[d];var e=a.b,f=e.createProgram();e.attachShader(f,Kl(a,b));e.attachShader(f,Kl(a,c));e.linkProgram(f);return a.g[d]=f}k.zp=function(){lb(this.a);lb(this.c);lb(this.g);this.s=this.v=this.i=this.l=null};k.Ap=function(){};
+k.cd=function(a){if(a==this.l)return!1;this.b.useProgram(a);this.l=a;return!0};function Ll(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 Jl(a,b,c){var d=Ll(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 Ml(a,b){var c=Ll(a,33071,33071);a.texImage2D(a.TEXTURE_2D,0,a.RGBA,a.RGBA,a.UNSIGNED_BYTE,b);return c};function Nl(a,b){ql.call(this,a,b);this.C=this.D=void 0;this.v=[];this.o=[];this.qa=this.oa=this.height=void 0;this.Wa=null;this.width=this.scale=this.rotation=this.rotateWithView=this.O=this.T=this.opacity=void 0}w(Nl,ql);k=Nl.prototype;k.Db=function(a){var b=this.s,c=this.j,d=this.ig(!0),e=a.b;return function(){if(!e.isContextLost()){var f;var g=0;for(f=d.length;g<f;++g)e.deleteTexture(d[g])}Bl(a,b);Bl(a,c)}};
+function Ol(a,b,c,d){var e=a.D,f=a.C,g=a.height,h=a.oa,l=a.qa,m=a.opacity,n=a.T,p=a.O,q=a.rotateWithView?1:0,r=-a.rotation,u=a.scale,v=a.width,z=Math.cos(r);r=Math.sin(r);var A=a.b.length,E=a.a.length,S;for(S=0;S<c;S+=d){var Ia=b[S]-a.origin[0];var ta=b[S+1]-a.origin[1];var la=E/8;var ca=-u*e;var ia=-u*(g-f);a.a[E++]=Ia;a.a[E++]=ta;a.a[E++]=ca*z-ia*r;a.a[E++]=ca*r+ia*z;a.a[E++]=n/l;a.a[E++]=(p+g)/h;a.a[E++]=m;a.a[E++]=q;ca=u*(v-e);ia=-u*(g-f);a.a[E++]=Ia;a.a[E++]=ta;a.a[E++]=ca*z-ia*r;a.a[E++]=ca*
+r+ia*z;a.a[E++]=(n+v)/l;a.a[E++]=(p+g)/h;a.a[E++]=m;a.a[E++]=q;ca=u*(v-e);ia=u*f;a.a[E++]=Ia;a.a[E++]=ta;a.a[E++]=ca*z-ia*r;a.a[E++]=ca*r+ia*z;a.a[E++]=(n+v)/l;a.a[E++]=p/h;a.a[E++]=m;a.a[E++]=q;ca=-u*e;ia=u*f;a.a[E++]=Ia;a.a[E++]=ta;a.a[E++]=ca*z-ia*r;a.a[E++]=ca*r+ia*z;a.a[E++]=n/l;a.a[E++]=p/h;a.a[E++]=m;a.a[E++]=q;a.b[A++]=la;a.b[A++]=la+1;a.b[A++]=la+2;a.b[A++]=la;a.b[A++]=la+2;a.b[A++]=la+3}}
+function Pl(a,b,c,d){var e,f=b.length;for(e=0;e<f;++e){var g=b[e];var h=x(g).toString();h in c?g=c[h]:(g=Ml(d,g),c[h]=g);a[e]=g}}
+k.Bf=function(a,b){var c=Cl(b,El,Fl);if(this.Wa)var d=this.Wa;else this.Wa=d=new Gl(a,c);b.cd(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.B);a.vertexAttribPointer(d.B,2,5126,!1,32,16);a.enableVertexAttribArray(d.o);a.vertexAttribPointer(d.o,1,5126,!1,32,24);a.enableVertexAttribArray(d.D);a.vertexAttribPointer(d.D,1,5126,!1,32,28);return d};
+k.Cf=function(a,b){a.disableVertexAttribArray(b.b);a.disableVertexAttribArray(b.v);a.disableVertexAttribArray(b.B);a.disableVertexAttribArray(b.o);a.disableVertexAttribArray(b.D)};
+k.Od=function(a,b,c,d){var e=d?this.ag():this.ig();d=d?this.o:this.v;if(nb(c)){var f;c=0;var g=e.length;for(f=0;c<g;++c){a.bindTexture(3553,e[c]);var h=d[c];sl(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.g.length&&this.g[g]<=m;){var p=x(this.i[g]).toString();void 0!==c[p]?(n!==l&&sl(a,b,n,l),l=n=g===this.g.length-1?m:this.g[g+1]):l=g===this.g.length-1?m:this.g[g+1];g++}n!==l&&sl(a,b,n,l)}};
+k.Ee=function(a,b,c,d,e){var f,g,h=this.g.length-1,l=this.ag();for(f=l.length-1;0<=f;--f){a.bindTexture(3553,l[f]);var m=0<f?this.o[f-1]:0;for(g=this.o[f];0<=h&&this.g[h]>=m;){var n=this.g[h];var p=this.i[h];var q=x(p).toString();if(void 0===c[q]&&p.U()&&(void 0===e||hb(e,p.U().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),sl(a,b,n,g),g=d(p)))return g;g=n;h--}}};
+k.gb=function(){this.qa=this.oa=this.height=this.C=this.D=void 0;this.b=null;this.scale=this.rotation=this.rotateWithView=this.O=this.T=this.opacity=void 0;this.a=null;this.width=void 0};function Ql(a,b){Nl.call(this,a,b);this.l=[];this.c=[];this.B=[];this.N=[]}w(Ql,Nl);k=Ql.prototype;k.wc=function(a,b){this.g.push(this.b.length);this.i.push(b);b=a.da();Ol(this,b,b.length,a.pa())};k.yc=function(a,b){this.g.push(this.b.length);this.i.push(b);b=a.da();Ol(this,b,b.length,a.pa())};
+k.gb=function(a){var b=a.b;this.v.push(this.b.length);this.o.push(this.b.length);this.s=new yl(this.a);this.j=new yl(this.b);var c={};Pl(this.B,this.l,c,b);Pl(this.N,this.c,c,b);this.c=this.l=null;Nl.prototype.gb.call(this,a)};
+k.Zb=function(a){var b=a.Vc(),c=a.Y(1),d=a.He(),e=a.Eg(),f=a.i,g=a.bd(),h=a.s,l=a.f,m=a.oc();a=a.a;if(0===this.l.length)this.l.push(c);else{var n=this.l[this.l.length-1];x(n)!=x(c)&&(this.v.push(this.b.length),this.l.push(c))}0===this.c.length?this.c.push(e):(n=this.c[this.c.length-1],x(n)!=x(e)&&(this.o.push(this.b.length),this.c.push(e)));this.D=b[0];this.C=b[1];this.height=m[1];this.oa=d[1];this.qa=d[0];this.opacity=f;this.T=g[0];this.O=g[1];this.rotation=l;this.rotateWithView=h;this.scale=a;this.width=
+m[0]};k.ig=function(a){return a?this.B.concat(this.N):this.B};k.ag=function(){return this.N};function Rl(a,b,c){var d=b-c;return a[0]===a[d]&&a[1]===a[d+1]&&3<(b-0)/c?!!mf(a,0,b,c):!1};var Sl=new il("precision mediump float;varying float a;varying vec2 aVertex;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((aVertex.x+1.0)/2.0*o.x*p,(aVertex.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;}"),Tl=new jl("varying float a;varying vec2 aVertex;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;aVertex=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);}}");function Ul(a,b){this.g=a.getUniformLocation(b,"h");this.i=a.getUniformLocation(b,"i");this.c=a.getUniformLocation(b,"j");this.oa=a.getUniformLocation(b,"k");this.O=a.getUniformLocation(b,"l");this.a=a.getUniformLocation(b,"m");this.C=a.getUniformLocation(b,"n");this.T=a.getUniformLocation(b,"o");this.qa=a.getUniformLocation(b,"p");this.l=a.getAttribLocation(b,"d");this.b=a.getAttribLocation(b,"e");this.s=a.getAttribLocation(b,"f");this.f=a.getAttribLocation(b,"g")};function Vl(a,b){ql.call(this,a,b);this.v=null;this.o=[];this.l=[];this.c={strokeColor:null,lineCap:void 0,lineDash:null,lineDashOffset:void 0,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0,u:!1}}w(Vl,ql);
+function Wl(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=Rl(b,c,d),n=g,p=1;for(e=0;e<c;e+=d){var q=f/7;var r=u;var u=v||[b[e],b[e+1]];if(0===e){var v=[b[e+d],b[e+d+1]];if(c-0===2*d&&jc(u,v))break;if(m){r=[b[c-2*d],b[c-2*d+1]];var z=v}else{l&&(f=Xl(a,[0,0],u,v,p*Yl*l,f),f=Xl(a,[0,0],u,v,-p*Yl*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=Xl(a,[0,0],u,v,p*Zl*
+(l||1),f);f=Xl(a,[0,0],u,v,-p*Zl*(l||1),f);n=f/7-1;continue}}else if(e===c-d){m?v=z:(r=r||[0,0],f=Xl(a,r,u,[0,0],p*$l*(l||1),f),f=Xl(a,r,u,[0,0],-p*$l*(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=Xl(a,r,u,[0,0],p*am*l,f),f=Xl(a,r,u,[0,0],-p*am*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 v=[b[e+d],b[e+d+1]];var A=wl(r[0],r[1],u[0],u[1],v[0],v[1])?-1:1;f=Xl(a,r,u,v,A*bm*(h||1),f);f=Xl(a,r,u,v,A*cm*(h||1),f);f=
+Xl(a,r,u,v,-A*dm*(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*A?n:n-1);a.b[g++]=q;a.b[g++]=q+2;a.b[g++]=q+1;n=q+2;p=A;h&&(f=Xl(a,r,u,v,A*em*h,f),a.b[g++]=q+1,a.b[g++]=q+3,a.b[g++]=q)}m&&(q=q||f/7,A=Mf([r[0],r[1],u[0],u[1],v[0],v[1]],0,6,2)?1:-1,f=Xl(a,r,u,v,A*bm*(h||1),f),Xl(a,r,u,v,-A*dm*(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*A?n:n-1)}
+function Xl(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 fm(a,b,c,d){c-=b;return c<2*d?!1:c===2*d?!jc([a[b],a[b+1]],[a[b+d],a[b+d+1]]):!0}k=Vl.prototype;k.uc=function(a,b){var c=a.da();a=a.pa();fm(c,0,c.length,a)&&(c=Ue(c,0,c.length,a,-this.origin[0],-this.origin[1]),this.c.u&&(this.l.push(this.b.length),this.c.u=!1),this.g.push(this.b.length),this.i.push(b),Wl(this,c,c.length,a))};
+k.vc=function(a,b){var c=this.b.length,d=a.pb();d.unshift(0);var e=a.da();a=a.pa();var f;if(1<d.length){var g=1;for(f=d.length;g<f;++g)if(fm(e,d[g-1],d[g],a)){var h=Ue(e,d[g-1],d[g],a,-this.origin[0],-this.origin[1]);Wl(this,h,h.length,a)}}this.b.length>c&&(this.g.push(c),this.i.push(b),this.c.u&&(this.l.push(c),this.c.u=!1))};
+function gm(a,b,c,d){Rl(b,b.length,d)||(b.push(b[0]),b.push(b[1]));Wl(a,b,b.length,d);if(c.length){var e;b=0;for(e=c.length;b<e;++b)Rl(c[b],c[b].length,d)||(c[b].push(c[b][0]),c[b].push(c[b][1])),Wl(a,c[b],c[b].length,d)}}function hm(a,b,c){c=void 0===c?a.b.length:c;a.g.push(c);a.i.push(b);a.c.u&&(a.l.push(c),a.c.u=!1)}k.gb=function(){this.s=new yl(this.a);this.j=new yl(this.b);this.g.push(this.b.length);0===this.l.length&&0<this.o.length&&(this.o=[]);this.b=this.a=null};
+k.Db=function(a){var b=this.s,c=this.j;return function(){Bl(a,b);Bl(a,c)}};
+k.Bf=function(a,b,c,d){var e=Cl(b,Sl,Tl);if(this.v)var f=this.v;else this.v=f=new Ul(a,e);b.cd(e);a.enableVertexAttribArray(f.l);a.vertexAttribPointer(f.l,2,5126,!1,28,0);a.enableVertexAttribArray(f.b);a.vertexAttribPointer(f.b,2,5126,!1,28,8);a.enableVertexAttribArray(f.s);a.vertexAttribPointer(f.s,2,5126,!1,28,16);a.enableVertexAttribArray(f.f);a.vertexAttribPointer(f.f,1,5126,!1,28,24);a.uniform2fv(f.T,c);a.uniform1f(f.qa,d);return f};
+k.Cf=function(a,b){a.disableVertexAttribArray(b.l);a.disableVertexAttribArray(b.b);a.disableVertexAttribArray(b.s);a.disableVertexAttribArray(b.f)};
+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(nb(c)){var g=this.g[this.g.length-1];for(c=this.l.length-1;0<=c;--c){var h=this.l[c];var l=this.o[c];im(this,a,l[0],l[1],l[2]);sl(a,b,h,g);a.clear(a.DEPTH_BUFFER_BIT);g=h}}else{var m=this.g.length-2;l=g=this.g[m+1];for(h=this.l.length-1;0<=h;--h){var n=this.o[h];im(this,a,n[0],n[1],n[2]);for(n=this.l[h];0<=m&&this.g[m]>=n;){var p=this.g[m];
+var q=this.i[m];q=x(q).toString();c[q]&&(g!==l&&(sl(a,b,g,l),a.clear(a.DEPTH_BUFFER_BIT)),l=p);m--;g=p}g!==l&&(sl(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.Ee=function(a,b,c,d,e){var f,g;var h=this.g.length-2;var l=this.g[h+1];for(f=this.l.length-1;0<=f;--f){var m=this.o[f];im(this,a,m[0],m[1],m[2]);for(g=this.l[f];0<=h&&this.g[h]>=g;){m=this.g[h];var n=this.i[h];var p=x(n).toString();if(void 0===c[p]&&n.U()&&(void 0===e||hb(e,n.U().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),sl(a,b,m,l),l=d(n)))return l;h--;l=m}}};function im(a,b,c,d,e){b.uniform4fv(a.v.C,c);b.uniform1f(a.v.oa,d);b.uniform1f(a.v.O,e)}
+k.Oa=function(a,b){a=b.f;this.c.lineCap=void 0!==a?a:"round";a=b.g;this.c.lineDash=a?a:ul;a=b.i;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=vl:a=vi(a).map(function(a,b){return 3!=b?a/255:a})||vl;var c=b.c;c=void 0!==c?c:1;b=b.l;b=void 0!==b?b:10;this.c.strokeColor&&jc(this.c.strokeColor,a)&&this.c.lineWidth===c&&this.c.miterLimit===b||(this.c.u=!0,this.c.strokeColor=a,this.c.lineWidth=c,this.c.miterLimit=b,
+this.o.push([a,c,b]))};var Zl=3,$l=5,Yl=7,am=11,bm=13,cm=17,dm=19,em=23;var jm=new il("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;}"),km=new jl("attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}");function lm(a,b){this.g=a.getUniformLocation(b,"b");this.i=a.getUniformLocation(b,"c");this.c=a.getUniformLocation(b,"d");this.C=a.getUniformLocation(b,"e");this.a=a.getUniformLocation(b,"f");this.b=a.getAttribLocation(b,"a")};function mm(){this.b=this.a=this.g=void 0;this.c=0}function nm(a){var b=a.b;if(b){var c=b.next,d=b.Eb;c&&(c.Eb=d);d&&(d.next=c);a.b=c||d;a.g===a.a?(a.b=void 0,a.g=void 0,a.a=void 0):a.g===b?a.g=a.b:a.a===b&&(a.a=d?a.b.Eb:a.b);a.c--}}function om(a){a.b=a.g;if(a.b)return a.b.data}function pm(a){if(a.b&&a.b.next)return a.b=a.b.next,a.b.data}function qm(a){if(a.b&&a.b.next)return a.b.next.data}function rm(a){if(a.b&&a.b.Eb)return a.b=a.b.Eb,a.b.data}function sm(a){if(a.b&&a.b.Eb)return a.b.Eb.data}
+function tm(a){if(a.b)return a.b.data}mm.prototype.concat=function(a){if(a.b){if(this.b){var b=this.b.next;this.b.next=a.g;a.g.Eb=this.b;b.Eb=a.a;a.a.next=b;this.c+=a.c}else this.b=a.b,this.g=a.g,this.a=a.a,this.c=a.c;a.b=void 0;a.g=void 0;a.a=void 0;a.c=0}};function um(){this.a=rj.Jc(void 0);this.b={}}k=um.prototype;k.Ca=function(a,b){a={fa:a[0],ea:a[1],la:a[2],ka:a[3],value:b};this.a.Ca(a);this.b[x(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={fa:f[0],ea:f[1],la:f[2],ka:f[3],value:g};c[d]=f;this.b[x(g)]=f}this.a.load(c)};k.remove=function(a){a=x(a);var b=this.b[a];delete this.b[a];return null!==this.a.remove(b)};
+function vm(a,b,c){var d=a.b[x(c)];Sa([d.fa,d.ea,d.la,d.ka],b)||(a.remove(c),a.Ca(b,c))}function wm(a){return a.a.all().map(function(a){return a.value})}function xm(a,b){return a.a.search({fa:b[0],ea:b[1],la:b[2],ka:b[3]}).map(function(a){return a.value})}k.forEach=function(a,b){return ym(wm(this),a,b)};function zm(a,b,c,d){return ym(xm(a,b),c,d)}function ym(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 Na(b.fa,b.ea,b.la,b.ka,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 Am(a,b){ql.call(this,a,b);this.f=new Vl(a,b);this.v=null;this.o=[];this.c=[];this.l={fillColor:null,u:!1}}w(Am,ql);
+function Bm(a,b,c,d){var e=new mm,f=new um;Cm(a,b,d,e,f,!0);b=Dm(e);if(c.length){var g,h=[];var l=0;for(g=c.length;l<g;++l){var m={list:new mm,Ec:void 0,gh:new um};h.push(m);Cm(a,c[l],d,m.list,m.gh,!1);Em(m.list,m.gh,!0);m.Ec=Dm(m.list)}h.sort(function(a,b){return b.Ec[0]===a.Ec[0]?a.Ec[1]-b.Ec[1]:b.Ec[0]-a.Ec[0]});for(l=0;l<h.length;++l){c=h[l].list;g=d=om(c);do{if(Fm(g,f).length){var n=!0;break}g=pm(c)}while(d!==g);!n&&Gm(c,h[l].Ec[0],e,b[0],f)&&(f.concat(h[l].gh),Em(e,f,!1))}}else Em(e,f,!1);Hm(a,
+e,f)}
+function Cm(a,b,c,d,e,f){var g,h=a.a.length/2,l=[],m=[];if(f===Mf(b,0,b.length,c)){var n=f=Im(a,b[0],b[1],h++);var p=c;for(g=b.length;p<g;p+=c){var q=Im(a,b[p],b[p+1],h++);m.push(Jm(n,q,d));l.push([Math.min(n.x,q.x),Math.min(n.y,q.y),Math.max(n.x,q.x),Math.max(n.y,q.y)]);n=q}}else for(p=b.length-c,n=f=Im(a,b[p],b[p+1],h++),p-=c,g=0;p>=g;p-=c)q=Im(a,b[p],b[p+1],h++),m.push(Jm(n,q,d)),l.push([Math.min(n.x,q.x),Math.min(n.y,q.y),Math.max(n.x,q.x),Math.max(n.y,q.y)]),n=q;m.push(Jm(q,f,d));l.push([Math.min(n.x,q.x),
+Math.min(n.y,q.y),Math.max(n.x,q.x),Math.max(n.y,q.y)]);e.load(l,m)}function Dm(a){var b=om(a),c=b,d=[c.Z.x,c.Z.y];do c=pm(a),c.Z.x>d[0]&&(d=[c.Z.x,c.Z.y]);while(c!==b);return d}function Em(a,b,c){var d=om(a),e=d,f=pm(a),g=!1;do{var h=c?wl(f.X.x,f.X.y,e.X.x,e.X.y,e.Z.x,e.Z.y):wl(e.Z.x,e.Z.y,e.X.x,e.X.y,f.X.x,f.X.y);void 0===h?(Km(e,f,a,b),g=!0,f===d&&(d=qm(a)),f=e,rm(a)):e.X.Kb!==h&&(e.X.Kb=h,g=!0);e=f;f=pm(a)}while(e!==d);return g}
+function Gm(a,b,c,d,e){for(var f=om(a);f.X.x!==b;)f=pm(a);b=f.X;d={x:d,y:b.y,qb:-1};var g=Infinity,h;var l=Fm({Z:b,X:d},e,!0);var m=0;for(h=l.length;m<h;++m){var n=l[m],p=Lm(b,d,n.Z,n.X,!0),q=Math.abs(b.x-p[0]);if(q<g&&void 0!==wl(b.x,b.y,n.Z.x,n.Z.y,n.X.x,n.X.y)){g=q;var r={x:p[0],y:p[1],qb:-1};f=n}}if(Infinity===g)return!1;l=f.X;if(0<g&&(f=Mm(b,r,f.X,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=om(c);f.X.x!==l.x||f.X.y!==
+l.y;)f=pm(c);d={x:b.x,y:b.y,qb:b.qb,Kb:void 0};m={x:f.X.x,y:f.X.y,qb:f.X.qb,Kb:void 0};qm(a).Z=d;Jm(b,f.X,a,e);Jm(m,d,a,e);f.X=m;a.b&&(a.g=a.b,a.a=a.b.Eb);c.concat(a);return!0}
+function Hm(a,b,c){for(var d=!1,e=Nm(b,c);3<b.c;)if(e){if(!Om(a,b,c,e,d)&&!Em(b,c,d)&&!Pm(a,b,c,!0))break}else if(!Om(a,b,c,e,d)&&!Em(b,c,d)&&!Pm(a,b,c))if(e=Nm(b,c)){d=b;var f=2*d.c,g=Array(f),h=om(d),l=h,m=0;do g[m++]=l.Z.x,g[m++]=l.Z.y,l=pm(d);while(l!==h);d=!Mf(g,0,f,2);Em(b,c,d)}else{e=a;d=b;f=g=om(d);do{h=Fm(f,c);if(h.length){g=h[0];h=Lm(f.Z,f.X,g.Z,g.X);h=Im(e,h[0],h[1],e.a.length/2);l=new mm;m=new um;Jm(h,f.X,l,m);f.X=h;vm(c,[Math.min(f.Z.x,h.x),Math.min(f.Z.y,h.y),Math.max(f.Z.x,h.x),Math.max(f.Z.y,
+h.y)],f);for(f=pm(d);f!==g;)Jm(f.Z,f.X,l,m),c.remove(f),nm(d),f=tm(d);Jm(g.Z,h,l,m);g.Z=h;vm(c,[Math.min(g.X.x,h.x),Math.min(g.X.y,h.y),Math.max(g.X.x,h.x),Math.max(g.X.y,h.y)],g);Em(d,c,!1);Hm(e,d,c);Em(l,m,!1);Hm(e,l,m);break}f=pm(d)}while(f!==g);break}3===b.c&&(e=a.b.length,a.b[e++]=sm(b).Z.qb,a.b[e++]=tm(b).Z.qb,a.b[e++]=qm(b).Z.qb)}
+function Om(a,b,c,d,e){var f=a.b.length,g=om(b),h=sm(b),l=g,m=pm(b),n=qm(b),p=!1;do{var q=l.Z;var r=l.X;var u=m.X;if(!1===r.Kb){var v=d?0===Mm(q,r,u,c,!0).length:e?Qm(n.X,u,r,q,h.Z):Qm(h.Z,q,r,u,n.X);!d&&0!==Fm({Z:q,X:u},c).length||!v||!d&&!1!==q.Kb&&!1!==u.Kb&&Mf([h.Z.x,h.Z.y,q.x,q.y,r.x,r.y,u.x,u.y,n.X.x,n.X.y],0,10,2)!==!e||(a.b[f++]=q.qb,a.b[f++]=r.qb,a.b[f++]=u.qb,Km(l,m,b,c),m===g&&(g=n),p=!0)}h=sm(b);l=tm(b);m=pm(b);n=qm(b)}while(l!==g&&3<b.c);return p}
+function Pm(a,b,c,d){var e=om(b);pm(b);var f=e,g=pm(b),h=!1;do{var l=Lm(f.Z,f.X,g.Z,g.X,d);if(l){h=a.b.length;var m=a.a.length/2,n=rm(b);nm(b);c.remove(n);var p=n===e;d?(l[0]===f.Z.x&&l[1]===f.Z.y?(rm(b),l=f.Z,g.Z=l,c.remove(f),p=p||f===e):(l=g.X,f.X=l,c.remove(g),p=p||g===e),nm(b)):(l=Im(a,l[0],l[1],m),f.X=l,g.Z=l,vm(c,[Math.min(f.Z.x,f.X.x),Math.min(f.Z.y,f.X.y),Math.max(f.Z.x,f.X.x),Math.max(f.Z.y,f.X.y)],f),vm(c,[Math.min(g.Z.x,g.X.x),Math.min(g.Z.y,g.X.y),Math.max(g.Z.x,g.X.x),Math.max(g.Z.y,
+g.X.y)],g));a.b[h++]=n.Z.qb;a.b[h++]=n.X.qb;a.b[h++]=l.qb;h=!0;if(p)break}f=sm(b);g=pm(b)}while(f!==e);return h}function Nm(a,b){var c=om(a),d=c;do{if(Fm(d,b).length)return!1;d=pm(a)}while(d!==c);return!0}function Im(a,b,c,d){var e=a.a.length;a.a[e++]=b;a.a[e++]=c;return{x:b,y:c,qb:d,Kb:void 0}}
+function Jm(a,b,c,d){var e={Z:a,X:b},f={Eb:void 0,next:void 0,data:e},g=c.b;if(g){var h=g.next;f.Eb=g;f.next=h;g.next=f;h&&(h.Eb=f);g===c.a&&(c.a=f)}else c.g=f,c.a=f,f.next=f,f.Eb=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 Km(a,b,c,d){tm(c)===b&&(nm(c),a.X=b.X,d.remove(b),vm(d,[Math.min(a.Z.x,a.X.x),Math.min(a.Z.y,a.X.y),Math.max(a.Z.x,a.X.x),Math.max(a.Z.y,a.X.y)],a))}
+function Mm(a,b,c,d,e){var f,g,h=[],l=xm(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.Kb||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)||!Gf([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 Fm(a,b,c){var d=a.Z,e=a.X;b=xm(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.Z!==e||l.X!==d)&&Lm(d,e,l.Z,l.X,c)&&f.push(l)}return f}
+function Lm(a,b,c,d,e){var f=(d.y-c.y)*(b.x-a.x)-(d.x-c.x)*(b.y-a.y);if(0!==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>xl&&d<1-xl&&c>xl&&c<1-xl||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 Qm(a,b,c,d,e){if(void 0===b.Kb||void 0===d.Kb)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.Kb?c||a:c&&a;return(d.Kb?e||f:e&&f)&&b}k=Am.prototype;
+k.xc=function(a,b){var c=a.td(),d=a.pa(),e=this.b.length,f=this.f.b.length;a=a.da();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=Ue(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=Ue(a,n[h-1],n[h],d,-this.origin[0],-this.origin[1]);q.push(r)}gm(this.f,p,q,d);Bm(this,p,q,d)}}h=n[n.length-1]}this.b.length>e&&(this.g.push(e),this.i.push(b),this.l.u&&(this.c.push(e),this.l.u=!1));this.f.b.length>f&&hm(this.f,
+b,f)};k.zc=function(a,b){var c=a.pb(),d=a.pa();if(0<c.length){a=a.da().map(Number);var e=Ue(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=Ue(a,c[h-1],c[h],d,-this.origin[0],-this.origin[1]);f.push(l)}this.g.push(this.b.length);this.i.push(b);this.l.u&&(this.c.push(this.b.length),this.l.u=!1);hm(this.f,b);gm(this.f,e,f,d);Bm(this,e,f,d)}}};
+k.gb=function(a){this.s=new yl(this.a);this.j=new yl(this.b);this.g.push(this.b.length);this.f.gb(a);0===this.c.length&&0<this.o.length&&(this.o=[]);this.b=this.a=null};k.Db=function(a){var b=this.s,c=this.j,d=this.f.Db(a);return function(){Bl(a,b);Bl(a,c);d()}};k.Bf=function(a,b){var c=Cl(b,jm,km);if(this.v)var d=this.v;else this.v=d=new lm(a,c);b.cd(c);a.enableVertexAttribArray(d.b);a.vertexAttribPointer(d.b,2,5126,!1,8,0);return d};k.Cf=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(nb(c)){var g=this.g[this.g.length-1];for(c=this.c.length-1;0<=c;--c){var h=this.c[c];var l=this.o[c];a.uniform4fv(this.v.C,l);sl(a,b,h,g);g=h}}else{var m=this.g.length-2;l=g=this.g[m+1];for(h=this.c.length-1;0<=h;--h){var n=this.o[h];a.uniform4fv(this.v.C,n);for(n=this.c[h];0<=m&&this.g[m]>=n;){var p=this.g[m];var q=this.i[m];q=x(q).toString();
+c[q]&&(g!==l&&(sl(a,b,g,l),a.clear(a.DEPTH_BUFFER_BIT)),l=p);m--;g=p}g!==l&&(sl(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.Ee=function(a,b,c,d,e){var f,g;var h=this.g.length-2;var l=this.g[h+1];for(f=this.c.length-1;0<=f;--f){var m=this.o[f];a.uniform4fv(this.v.C,m);for(g=this.c[f];0<=h&&this.g[h]>=g;){m=this.g[h];var n=this.i[h];var p=x(n).toString();if(void 0===c[p]&&n.U()&&(void 0===e||hb(e,n.U().G()))&&(a.clear(a.COLOR_BUFFER_BIT|a.DEPTH_BUFFER_BIT),sl(a,b,m,l),l=d(n)))return l;h--;l=m}}};
+k.Oa=function(a,b){a=a?a.b:[0,0,0,0];a instanceof CanvasGradient||a instanceof CanvasPattern?a=tl:a=vi(a).map(function(a,b){return 3!=b?a/255:a})||tl;this.l.fillColor&&jc(a,this.l.fillColor)||(this.l.fillColor=a,this.l.u=!0,this.o.push(a));b?this.f.Oa(null,b):this.f.Oa(null,new Ak({color:[0,0,0,0],lineWidth:0}))};function Rm(a,b){this.b=b;this.a=[{x:0,y:0,width:a,height:a}];this.c={};this.g=hg(a,a);this.i=this.g.canvas}Rm.prototype.get=function(a){return this.c[a]||null};
+Rm.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.i},this.c[a]=f,d.call(e,this.g,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},Sm(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},
+Sm(this,a,c,b)),f}return null};function Sm(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 Tm(a){a=a||{};this.a=void 0!==a.initialSize?a.initialSize:256;this.g=void 0!==a.maxSize?a.maxSize:void 0!==ba?ba:2048;this.b=void 0!==a.space?a.space:1;this.f=[new Rm(this.a,this.b)];this.c=this.a;this.i=[new Rm(this.c,this.b)]}function Um(a,b){var c;var d=0;for(c=a.length;d<c;++d){var e=a[d];if(e=e.get(b))return e}return null}function Vm(a,b){return{offsetX:a.offsetX,offsetY:a.offsetY,image:a.image,Bm:b.image}}
+Tm.prototype.add=function(a,b,c,d,e,f){if(b+this.b>this.g||c+this.b>this.g)return null;d=Wm(this,!1,a,b,c,d,f);if(!d)return null;a=Wm(this,!0,a,b,c,void 0!==e?e:ea,f);return Vm(d,a)};function Wm(a,b,c,d,e,f,g){var h=b?a.i: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.g),a.c=n):(n=Math.min(2*a.a,a.g),a.a=n),n=new Rm(n,a.b),h.push(n),++l)}return null};function Xm(a,b){Nl.call(this,a,b);this.c=[];this.ua=[];this.Ub=hg(0,0).canvas;this.N={strokeColor:null,lineCap:void 0,lineDash:null,lineDashOffset:void 0,lineJoin:void 0,lineWidth:0,miterLimit:void 0,fillColor:null,font:void 0,scale:void 0};this.ta="";this.ca=this.$=this.ra=this.ab=void 0;this.B={};this.l=void 0;this.opacity=this.scale=1}w(Xm,Nl);k=Xm.prototype;
+k.Wb=function(a,b){if(this.ta){var c=null,d=2,e=2;switch(a.S()){case "Point":case "MultiPoint":c=a.da();d=c.length;e=a.pa();break;case "Circle":c=a.xa();break;case "LineString":c=a.Fe();break;case "MultiLineString":c=a.Ge();d=c.length;break;case "Polygon":c=a.Td();break;case "MultiPolygon":c=Ji(a),d=c.length}this.g.push(this.b.length);this.i.push(b);a=this.l;b=this.ta.split("\n");var f=Ym(this,b),g,h,l=Math.round(f[0]*this.ab-this.$),m=Math.round(f[1]*this.ra-this.ca),n=this.N.lineWidth/2*this.N.scale;
+f=0;for(g=b.length;f<g;++f){var p=0;var q=a.height*f;var r=b[f].split("");var u=0;for(h=r.length;u<h;++u){var v=a.Bh;var z=r[u],A=Um(v.f,z);A?(v=Um(v.i,z),v=Vm(A,v)):v=null;if(v){A=v.image;this.D=l-p;this.C=m-q;this.T=0===u?v.offsetX-n:v.offsetX;this.O=v.offsetY;this.height=a.height;this.width=0===u||u===r.length-1?a.width[r[u]]+n:a.width[r[u]];this.oa=A.height;this.qa=A.width;0===this.c.length?this.c.push(A):(v=this.c[this.c.length-1],x(v)!=x(A)&&(this.v.push(this.b.length),this.c.push(A)));v=c;
+z=d;var E=e;for(A=0;A<z;A+=E)Ol(this,v,z,E)}p+=this.width}}}};function Ym(a,b){var c=a.l,d=b.length*c.height;return[b.map(function(b){var d=0,e;var h=0;for(e=b.length;h<e;++h){var l=b[h];c.width[l]||Zm(a,l);d+=c.width[l]?c.width[l]:0}return d}).reduce(function(a,b){return Math.max(a,b)}),d]}
+function Zm(a,b){if(1===b.length){var c=a.l,d=a.N;a=a.Ub.getContext("2d");a.font=d.font;a=Math.ceil(a.measureText(b).width*d.scale);c.Bh.add(b,a,c.height,function(a,c,g){a.font=d.font;a.fillStyle=d.fillColor;a.strokeStyle=d.strokeColor;a.lineWidth=d.lineWidth;a.lineCap=d.lineCap;a.lineJoin=d.lineJoin;a.miterLimit=d.miterLimit;a.textAlign="left";a.textBaseline="top";od&&d.lineDash&&(a.setLineDash(d.lineDash),a.lineDashOffset=d.lineDashOffset);1!==d.scale&&a.setTransform(d.scale,0,0,d.scale,0,0);d.strokeColor&&
+a.strokeText(b,c,g);d.fillColor&&a.fillText(b,c,g)})&&(c.width[b]=a)}}k.gb=function(a){var b=a.b;this.v.push(this.b.length);this.o=this.v;this.s=new yl(this.a);this.j=new yl(this.b);Pl(this.ua,this.c,{},b);this.N={strokeColor:null,lineCap:void 0,lineDash:null,lineDashOffset:void 0,lineJoin:void 0,lineWidth:0,miterLimit:void 0,fillColor:null,font:void 0,scale:void 0};this.ta="";this.ca=this.$=this.ra=this.ab=void 0;this.c=null;this.B={};this.l=void 0;Nl.prototype.gb.call(this,a)};
+k.nb=function(a){var b=this.N,c=a.Fa(),d=a.Ga();if(a&&a.Ka()&&(c||d)){c?(c=c.b,b.fillColor=zi(c?c:tl)):b.fillColor=null;d?(c=d.a,b.strokeColor=zi(c?c:vl),b.lineWidth=d.c||1,b.lineCap=d.f||"round",b.lineDashOffset=d.i||0,b.lineJoin=d.j||"round",b.miterLimit=d.l||10,d=d.g,b.lineDash=d?d.slice():ul):(b.strokeColor=null,b.lineWidth=0);b.font=a.a||"10px sans-serif";b.scale=a.b||1;this.ta=a.Ka();d=vj[a.f];c=vj[a.j];this.ab=void 0===d?.5:d;this.ra=void 0===c?.5:c;this.$=a.g||0;this.ca=a.c||0;this.rotateWithView=
+!!a.l;this.rotation=a.i||0;a=[];for(var e in b)if(b[e]||0===b[e])Array.isArray(b[e])?a=a.concat(b[e]):a.push(b[e]);c="";e=0;for(d=a.length;e<d;++e)c+=a[e];e=c;this.B[e]||(a=this.Ub.getContext("2d"),a.font=b.font,a=Math.ceil((1.5*a.measureText("M").width+b.lineWidth/2)*b.scale),this.B[e]={Bh:new Tm({space:b.lineWidth+1}),width:{},height:a});this.l=this.B[e]}else this.ta=""};k.ig=function(){return this.ua};k.ag=function(){return this.ua};function $m(a,b,c){this.c=b;this.i=a;this.g=c;this.a={}}w($m,sj);k=$m.prototype;k.Vb=function(){};function an(a,b){var c=[],d;for(d in a.a){var e=a.a[d],f;for(f in e)c.push(e[f].Db(b))}return function(){for(var a=c.length,b,d=0;d<a;d++)b=c[d].apply(this,arguments);return b}}function bn(a,b){for(var c in a.a){var d=a.a[c],e;for(e in d)d[e].gb(b)}}k.Ja=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 cn[b](this.i,this.c),a[b]=c);return c};
+k.yg=function(){return nb(this.a)};k.Na=function(a,b,c,d,e,f,g,h){var l=Object.keys(this.a).map(Number);l.sort(dc);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=uj.length;r<n;++r){var u=q[uj[r]];void 0!==u&&u.Na(a,b,c,d,e,f,g,h,void 0,!1)}}};
+function dn(a,b,c,d,e,f,g,h,l,m,n){var p=en,q=Object.keys(a.a).map(Number);q.sort(function(a,b){return b-a});var r,u;var v=0;for(r=q.length;v<r;++v){var z=a.a[q[v].toString()];for(u=uj.length-1;0<=u;--u){var A=z[uj[u]];if(void 0!==A&&(A=A.Na(b,c,d,e,p,f,g,h,l,m,n)))return A}}}
+k.wa=function(a,b,c,d,e,f,g,h,l,m){var n=b.b;n.bindFramebuffer(n.FRAMEBUFFER,Il(b));var p;void 0!==this.g&&(p=Fa(Pa(a),d*this.g));return dn(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 fn(a,b,c,d,e,f,g,h){var l=c.b;l.bindFramebuffer(l.FRAMEBUFFER,Il(c));return void 0!==dn(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 en=[1,1],cn={Circle:Al,Image:Ql,LineString:Vl,Polygon:Am,Text:Xm};function gn(a,b,c,d,e,f,g){this.b=a;this.g=b;this.c=f;this.i=g;this.l=e;this.j=d;this.f=c;this.a=this.s=this.v=this.o=null}w(gn,Ai);function hn(a,b,c){var d=a.b;b=b.Ja(0,"Text");b.nb(a.a);b.Wb(c,null);b.gb(d);b.Na(a.b,a.g,a.f,a.j,a.l,a.i,1,{},void 0,!1);b.Db(d)()}k=gn.prototype;k.Dd=function(a){this.Oa(a.Fa(),a.Ga());this.Zb(a.Y());this.nb(a.Ka())};
+k.Hb=function(a){switch(a.S()){case "Point":this.yc(a,null);break;case "LineString":this.uc(a,null);break;case "Polygon":this.zc(a,null);break;case "MultiPoint":this.wc(a,null);break;case "MultiLineString":this.vc(a,null);break;case "MultiPolygon":this.xc(a,null);break;case "GeometryCollection":this.De(a);break;case "Circle":this.cc(a,null)}};k.Ce=function(a,b){(a=(0,b.cb)(a))&&hb(this.c,a.G())&&(this.Dd(b),this.Hb(a))};k.De=function(a){a=a.a;var b;var c=0;for(b=a.length;c<b;++c)this.Hb(a[c])};
+k.yc=function(a,b){var c=this.b,d=new $m(1,this.c),e=d.Ja(0,"Image");e.Zb(this.o);e.yc(a,b);e.gb(c);e.Na(this.b,this.g,this.f,this.j,this.l,this.i,1,{},void 0,!1);e.Db(c)();this.a&&hn(this,d,a)};k.wc=function(a,b){var c=this.b,d=new $m(1,this.c),e=d.Ja(0,"Image");e.Zb(this.o);e.wc(a,b);e.gb(c);e.Na(this.b,this.g,this.f,this.j,this.l,this.i,1,{},void 0,!1);e.Db(c)();this.a&&hn(this,d,a)};
+k.uc=function(a,b){var c=this.b,d=new $m(1,this.c),e=d.Ja(0,"LineString");e.Oa(null,this.s);e.uc(a,b);e.gb(c);e.Na(this.b,this.g,this.f,this.j,this.l,this.i,1,{},void 0,!1);e.Db(c)();this.a&&hn(this,d,a)};k.vc=function(a,b){var c=this.b,d=new $m(1,this.c),e=d.Ja(0,"LineString");e.Oa(null,this.s);e.vc(a,b);e.gb(c);e.Na(this.b,this.g,this.f,this.j,this.l,this.i,1,{},void 0,!1);e.Db(c)();this.a&&hn(this,d,a)};
+k.zc=function(a,b){var c=this.b,d=new $m(1,this.c),e=d.Ja(0,"Polygon");e.Oa(this.v,this.s);e.zc(a,b);e.gb(c);e.Na(this.b,this.g,this.f,this.j,this.l,this.i,1,{},void 0,!1);e.Db(c)();this.a&&hn(this,d,a)};k.xc=function(a,b){var c=this.b,d=new $m(1,this.c),e=d.Ja(0,"Polygon");e.Oa(this.v,this.s);e.xc(a,b);e.gb(c);e.Na(this.b,this.g,this.f,this.j,this.l,this.i,1,{},void 0,!1);e.Db(c)();this.a&&hn(this,d,a)};
+k.cc=function(a,b){var c=this.b,d=new $m(1,this.c),e=d.Ja(0,"Circle");e.Oa(this.v,this.s);e.cc(a,b);e.gb(c);e.Na(this.b,this.g,this.f,this.j,this.l,this.i,1,{},void 0,!1);e.Db(c)();this.a&&hn(this,d,a)};k.Zb=function(a){this.o=a};k.Oa=function(a,b){this.v=a;this.s=b};k.nb=function(a){this.a=a};var jn=new il("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;}"),kn=new jl("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;}");function ln(a,b){this.f=a.getUniformLocation(b,"d");this.c=a.getUniformLocation(b,"e");this.g=a.getUniformLocation(b,"f");this.i=a.getUniformLocation(b,"g");this.b=a.getAttribLocation(b,"b");this.a=a.getAttribLocation(b,"c")};function mn(a,b){Ki.call(this,b);this.c=a;this.V=new yl([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.f=this.Mb=null;this.j=void 0;this.v=We();this.N=We();this.C=nl();this.o=null}w(mn,Ki);
+function nn(a,b,c){var d=a.c.g;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.f,a.Mb));b=Jl(d,c,c);var e=d.createFramebuffer();d.bindFramebuffer(36160,e);d.framebufferTexture2D(36160,36064,3553,b,0);a.Mb=b;a.f=e;a.j=c}else d.bindFramebuffer(36160,a.f)}
+mn.prototype.Zi=function(a,b,c){on(this,"precompose",c,a);rl(c,34962,this.V);var d=c.b,e=Cl(c,jn,kn);if(this.o)var f=this.o;else this.o=f=new ln(d,e);c.cd(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.i,0));d.uniformMatrix4fv(f.f,!1,pl(this.C,this.v));d.uniformMatrix4fv(f.c,!1,pl(this.C,this.N));d.uniform1f(f.g,b.opacity);d.bindTexture(3553,this.Mb);d.drawArrays(5,0,4);on(this,"postcompose",
+c,a)};function on(a,b,c,d){a=a.a;if(Tc(a,b)){var e=d.viewState;a.b(new bi(b,new gn(c,e.center,e.resolution,e.rotation,d.size,d.extent,d.pixelRatio),d,null,c))}}mn.prototype.Ag=function(){this.f=this.Mb=null;this.j=void 0};function pn(a,b){mn.call(this,a,b);this.l=this.i=this.M=null}w(pn,mn);pn.handles=function(a,b){return"webgl"===a&&"IMAGE"===b.S()};pn.create=function(a,b){return new pn(a,b)};function qn(a,b){b=b.Y();return Ml(a.c.g,b)}pn.prototype.wa=function(a,b,c,d,e){var f=this.a;return f.ha().wa(a,b.viewState.resolution,b.viewState.rotation,c,b.skippedFeatureUids,function(a){return d.call(e,a,f)})};
+pn.prototype.Bg=function(a,b){var c=this.c.g,d=a.pixelRatio,e=a.viewState,f=e.center,g=e.resolution,h=e.rotation,l=this.M,m=this.Mb,n=this.a.ha(),p=a.viewHints,q=a.extent;void 0!==b.extent&&(q=gb(q,b.extent));p[0]||p[1]||bb(q)||(b=n.Y(q,g,d,e.projection))&&Si(this,b)&&(l=b,m=qn(this,b),this.Mb&&a.postRenderFunctions.push(function(a,b){a.isContextLost()||a.deleteTexture(b)}.bind(null,c,this.Mb)));l&&(c=this.c.i.j,rn(this,c.width,c.height,d,f,g,h,l.G()),this.l=null,d=this.v,Xe(d),cf(d,1,-1),df(d,0,
+-1),this.M=l,this.Mb=m,Ui(a,n));return!!l};function rn(a,b,c,d,e,f,g,h){b*=f;c*=f;a=a.N;Xe(a);cf(a,2*d/b,2*d/c);bf(a,-g);df(a,h[0]-e[0],h[1]-e[1]);cf(a,(h[2]-h[0])/2,(h[3]-h[1])/2);df(a,1,1)}pn.prototype.cf=function(a,b){return void 0!==this.wa(a,b,0,Re,this)};
+pn.prototype.zg=function(a,b,c,d){if(this.M&&this.M.Y())if(this.a.ha().wa!==ea){var e=af(b.pixelToCoordinateTransform,a.slice());if(this.wa(e,b,0,Re,this))return c.call(d,this.a,null)}else{e=[this.M.Y().width,this.M.Y().height];if(!this.l){var f=b.size;b=We();df(b,-1,-1);cf(b,2/f[0],2/f[1]);df(b,0,f[1]);cf(b,1,-1);f=ff(this.N.slice());var g=We();df(g,0,e[1]);cf(g,1,-1);cf(g,e[0]/2,e[1]/2);df(g,1,1);Ze(g,f);Ze(g,b);this.l=g}a=af(this.l,a.slice());if(!(0>a[0]||a[0]>e[0]||0>a[1]||a[1]>e[1])&&(this.i||
+(this.i=hg(1,1)),this.i.clearRect(0,0,1,1),this.i.drawImage(this.M.Y(),a[0],a[1],1,1,0,0,1,1),e=this.i.getImageData(0,0,1,1).data,0<e[3]))return c.call(d,this.a,e)}};function sn(a,b){fj.call(this,a,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.N=this.D=0;this.C=hg();this.s=!0;this.g=gd(this.b,{antialias:!0,depth:!0,failIfMajorPerformanceCaveat:!0,preserveDrawingBuffer:!1,stencil:!0});this.i=new Hl(this.b,this.g);y(this.b,"webglcontextlost",this.Co,this);y(this.b,"webglcontextrestored",this.Do,this);
+this.a=new ci;this.o=null;this.j=new ge(function(a){var b=a[1];a=a[2];var c=b[0]-this.o[0];b=b[1]-this.o[1];return 65536*Math.log(a)+Math.sqrt(c*c+b*b)/a}.bind(this),function(a){return a[0].lb()});this.B=function(){if(0!==this.j.b.length){ke(this.j);var a=he(this.j);tn(this,a[0],a[3],a[4])}return!1}.bind(this);this.f=0;un(this)}w(sn,fj);sn.handles=function(a){return hd&&"webgl"===a};sn.create=function(a,b){return new sn(a,b)};
+function tn(a,b,c,d){var e=a.g,f=b.lb();if(a.a.a.hasOwnProperty(f))a=a.a.get(f),e.bindTexture(3553,a.Mb),9729!=a.hi&&(e.texParameteri(3553,10240,9729),a.hi=9729),9729!=a.ji&&(e.texParameteri(3553,10241,9729),a.ji=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.N!==c[1]?(h.width=c[0],h.height=c[1],a.D=c[0],a.N=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,{Mb:g,hi:9729,ji:9729})}}function vn(a,b,c){var d=a.l;if(Tc(d,b)){a=a.i;var e=c.viewState;d.b(new bi(b,new gn(a,e.center,e.resolution,e.rotation,c.size,c.extent,c.pixelRatio),c,null,a))}}k=sn.prototype;k.ia=function(){var a=this.g;a.isContextLost()||this.a.forEach(function(b){b&&a.deleteTexture(b.Mb)});Pc(this.i);fj.prototype.ia.call(this)};
+k.Yk=function(a,b){a=this.g;for(var c;1024<this.a.i-this.f;){if(c=this.a.g.Pc)a.deleteTexture(c.Mb);else if(+this.a.g.jc==b.index)break;else--this.f;this.a.pop()}};k.S=function(){return"webgl"};k.Co=function(a){a.preventDefault();this.a.clear();this.f=0;a=this.c;for(var b in a)a[b].Ag()};k.Do=function(){un(this);this.l.render()};function un(a){a=a.g;a.activeTexture(33984);a.blendFuncSeparate(770,771,1,771);a.disable(2884);a.disable(2929);a.disable(3089);a.disable(2960)}
+k.bh=function(a){var b=this.i,c=this.g;if(c.isContextLost())return!1;if(!a)return this.s&&(this.b.style.display="none",this.s=!1),!1;this.o=a.focus;this.a.set((-a.index).toString(),null);++this.f;vn(this,"precompose",a);var d=[],e=a.layerStatesArray;kc(e);var f=a.viewState.resolution,g;var h=0;for(g=e.length;h<g;++h){var l=e[h];if(yg(l,f)&&"ready"==l.Vj){var m=ij(this,l.layer);m.Bg(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=ij(this,l.layer),m.Zi(a,l,b);this.s||(this.b.style.display="",this.s=!0);gj(a);1024<this.a.i-this.f&&a.postRenderFunctions.push(this.Yk.bind(this));0!==this.j.b.length&&(a.postRenderFunctions.push(this.B),a.animate=!0);vn(this,"postcompose",a);jj(this,a);a.postRenderFunctions.push(hj)};
+k.wa=function(a,b,c,d,e,f,g){if(this.g.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(yg(n,h.resolution)&&f.call(g,p)&&(n=ij(this,p).wa(a,b,c,d,e)))return n}};k.Ui=function(a,b,c,d,e){c=!1;if(this.g.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(yg(l,f.resolution)&&d.call(e,m)&&(c=ij(this,m).cf(a,b)))return!0}return c};
+k.Ti=function(a,b,c,d,e){if(this.g.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(yg(l,f.resolution)&&e.call(d,m)&&(l=ij(this,m).zg(a,b,c,d)))return l}};var wn=new il("precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"),xn=new jl("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;}");function yn(a,b){this.c=a.getUniformLocation(b,"d");this.g=a.getUniformLocation(b,"e");this.b=a.getAttribLocation(b,"b");this.a=a.getAttribLocation(b,"c")};function zn(a,b){mn.call(this,a,b);this.T=wn;this.ca=xn;this.i=null;this.B=new yl([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.D=this.l=null;this.s=-1;this.O=[0,0]}w(zn,mn);zn.handles=function(a,b){return"webgl"===a&&"TILE"===b.S()};zn.create=function(a,b){return new zn(a,b)};k=zn.prototype;k.ia=function(){Bl(this.c.i,this.B);mn.prototype.ia.call(this)};
+k.Rf=function(a,b,c){var d=this.c;return function(e,f){return Li(a,b,e,f,function(a){var b=d.a.a.hasOwnProperty(a.lb());b&&(c[e]||(c[e]={}),c[e][a.ya.toString()]=a);return b})}};k.Ag=function(){mn.prototype.Ag.call(this);this.i=null};
+k.Bg=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.eb(g),n=m.Dc(f.resolution),p=m.Ta(n),q=l.Zd(n,a.pixelRatio,g),r=q[0]/Ba(m.Za(n),this.O)[0],u=p/r,v=l.Xc(r)*l.Zf(g),z=f.center,A=a.extent,E=tc(m,A,n);if(this.l&&na(this.l,E)&&this.s==l.g)u=this.D;else{var S=[E.la-E.fa+1,E.ka-E.ea+1],Ia=ra(Math.max(S[0]*q[0],S[1]*q[1]));S=u*Ia;var ta=m.Ic(n),la=ta[0]+E.fa*q[0]*u;u=ta[1]+E.ea*q[1]*u;u=[la,u,la+S,u+S];nn(this,a,Ia);e.viewport(0,0,Ia,Ia);e.clearColor(0,0,0,0);e.clear(16384);
+e.disable(3042);Ia=Cl(c,this.T,this.ca);c.cd(Ia);this.i||(this.i=new yn(e,Ia));rl(c,34962,this.B);e.enableVertexAttribArray(this.i.b);e.vertexAttribPointer(this.i.b,2,5126,!1,16,0);e.enableVertexAttribArray(this.i.a);e.vertexAttribPointer(this.i.a,2,5126,!1,16,8);e.uniform1i(this.i.g,0);c={};c[n]={};var ca=this.Rf(l,g,c),ia=h.i();Ia=!0;la=Da();var xa=new ja(0,0,0,0),Va,ic;for(Va=E.fa;Va<=E.la;++Va)for(ic=E.ea;ic<=E.ka;++ic){ta=l.ad(n,Va,ic,r,g);if(void 0!==b.extent){var Xa=m.Ma(ta.ya,la);if(!hb(Xa,
+b.extent))continue}Xa=ta.getState();(Xa=2==Xa||4==Xa||3==Xa&&!ia)||(ta=pj(ta));Xa=ta.getState();if(2==Xa){if(d.a.a.hasOwnProperty(ta.lb())){c[n][ta.ya.toString()]=ta;continue}}else if(4==Xa||3==Xa&&!ia)continue;Ia=!1;Xa=uc(m,ta.ya,ca,xa,la);Xa||(ta=vc(m,ta.ya,xa,la))&&ca(n+1,ta)}b=Object.keys(c).map(Number);b.sort(dc);ca=new Float32Array(4);var Z;ia=0;for(xa=b.length;ia<xa;++ia)for(Z in Va=c[b[ia]],Va)ta=Va[Z],Xa=m.Ma(ta.ya,la),ca[0]=2*(Xa[2]-Xa[0])/S,ca[1]=2*(Xa[3]-Xa[1])/S,ca[2]=2*(Xa[0]-u[0])/
+S-1,ca[3]=2*(Xa[1]-u[1])/S-1,e.uniform4fv(this.i.c,ca),tn(d,ta,q,v*r),e.drawArrays(5,0,4);Ia?(this.l=E,this.D=u,this.s=l.g):(this.D=this.l=null,this.s=-1,a.animate=!0)}Vi(a.usedTiles,l,n,E);var Zb=d.j;Wi(a,l,m,r,g,A,n,h.c(),function(a){2!=a.getState()||d.a.a.hasOwnProperty(a.lb())||a.lb()in Zb.a||Zb.i([a,yc(m,a.ya),m.Ta(a.ya[0]),q,v*r])},this);Ti(a,l);Ui(a,l);e=this.v;Xe(e);df(e,(Math.round(z[0]/p)*p-u[0])/(u[2]-u[0]),(Math.round(z[1]/p)*p-u[1])/(u[3]-u[1]));0!==f.rotation&&bf(e,f.rotation);cf(e,
+a.size[0]*f.resolution/(u[2]-u[0]),a.size[1]*f.resolution/(u[3]-u[1]));df(e,-.5,-.5);return!0};k.zg=function(a,b,c,d){if(this.f){a=af(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.i.b;b.bindFramebuffer(b.FRAMEBUFFER,this.f);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 An(a,b){mn.call(this,a,b);this.s=!1;this.O=-1;this.T=NaN;this.D=Da();this.l=this.i=this.B=null}w(An,mn);An.handles=function(a,b){return"webgl"===a&&"VECTOR"===b.S()};An.create=function(a,b){return new An(a,b)};k=An.prototype;k.Zi=function(a,b,c){this.l=b;var d=a.viewState,e=this.i,f=a.size,g=a.pixelRatio,h=this.c.g;e&&!e.yg()&&(h.enable(h.SCISSOR_TEST),h.scissor(0,0,f[0]*g,f[1]*g),e.Na(c,d.center,d.resolution,d.rotation,f,g,b.opacity,b.Te?a.skippedFeatureUids:{}),h.disable(h.SCISSOR_TEST))};
+k.ia=function(){var a=this.i;a&&(an(a,this.c.i)(),this.i=null);mn.prototype.ia.call(this)};k.wa=function(a,b,c,d,e){if(this.i&&this.l){c=b.viewState;var f=this.a,g={};return this.i.wa(a,this.c.i,c.center,c.resolution,c.rotation,b.size,b.pixelRatio,this.l.opacity,{},function(a){var b=x(a).toString();if(!(b in g))return g[b]=!0,d.call(e,a,f)})}};k.cf=function(a,b){if(this.i&&this.l){var c=b.viewState;return fn(this.i,a,this.c.i,c.resolution,c.rotation,b.pixelRatio,this.l.opacity,b.skippedFeatureUids)}return!1};
+k.zg=function(a,b,c,d){a=af(b.pixelToCoordinateTransform,a.slice());if(this.cf(a,b))return c.call(d,this.a,null)};k.$i=function(){Mi(this)};
+k.Bg=function(a,b,c){function d(a){var b=a.ib();if(b)var c=b.call(a,m);else(b=e.ib())&&(c=b(a,m));if(c){if(c){b=!1;if(Array.isArray(c))for(var d=c.length-1;0<=d;--d)b=ek(q,a,c[d],dk(m,n),this.$i,this)||b;else b=ek(q,a,c,dk(m,n),this.$i,this)||b;a=b}else a=!1;this.s=this.s||a}}var e=this.a;b=e.ha();Ui(a,b);var f=a.viewHints[0],g=a.viewHints[1],h=e.ca,l=e.ra;if(!this.s&&!h&&f||!l&&g)return!0;g=a.extent;h=a.viewState;f=h.projection;var m=h.resolution,n=a.pixelRatio;h=e.g;var p=e.f;l=e.get(ik);void 0===
+l&&(l=ck);g=Fa(g,p*m);if(!this.s&&this.T==m&&this.O==h&&this.B==l&&La(this.D,g))return!0;this.i&&a.postRenderFunctions.push(an(this.i,c));this.s=!1;var q=new $m(.5*m/n,g,e.f);b.ae(g,m,f);if(l){var r=[];b.ec(g,function(a){r.push(a)},this);r.sort(l);r.forEach(d,this)}else b.ec(g,d,this);bn(q,c);this.T=m;this.O=h;this.B=l;this.D=g;this.i=q;return!0};qg("MAP_RENDERER",kj);rg([bj,mj,hk,jk]);qg("MAP_RENDERER",sn);rg([pn,zn,An]);function K(a){a=kb({},a);a.controls||(a.controls=Fg());a.interactions||(a.interactions=Zh());G.call(this,a)}w(K,G);function Bn(a){Vc.call(this);this.id=a.id;this.insertFirst=void 0!==a.insertFirst?a.insertFirst:!0;this.stopEvent=void 0!==a.stopEvent?a.stopEvent:!0;this.element=document.createElement("DIV");this.element.className=void 0!==a.className?a.className:"ol-overlay-container ol-selectable";this.element.style.position="absolute";this.autoPan=void 0!==a.autoPan?a.autoPan:!1;this.autoPanAnimation=a.autoPanAnimation||{};this.autoPanMargin=void 0!==a.autoPanMargin?a.autoPanMargin:20;this.a={ze:"",Se:"",xf:"",
+Ef:"",visible:!0};this.c=null;y(this,Xc(Cn),this.am,this);y(this,Xc(Dn),this.km,this);y(this,Xc(En),this.om,this);y(this,Xc(Fn),this.qm,this);y(this,Xc(Gn),this.rm,this);void 0!==a.element&&this.Hj(a.element);this.Mj(void 0!==a.offset?a.offset:[0,0]);this.Pj(void 0!==a.positioning?a.positioning:"top-left");void 0!==a.position&&this.We(a.position)}w(Bn,Vc);k=Bn.prototype;k.Rd=function(){return this.get(Cn)};k.nn=function(){return this.id};k.Ve=function(){return this.get(Dn)};k.Xh=function(){return this.get(En)};
+k.pi=function(){return this.get(Fn)};k.Yh=function(){return this.get(Gn)};k.am=function(){for(var a=this.element;a.lastChild;)a.removeChild(a.lastChild);(a=this.Rd())&&this.element.appendChild(a)};k.km=function(){this.c&&(jg(this.element),Gc(this.c),this.c=null);var a=this.Ve();a&&(this.c=y(a,"postrender",this.render,this),Hn(this),a=this.stopEvent?a.v:a.o,this.insertFirst?a.insertBefore(this.element,a.childNodes[0]||null):a.appendChild(this.element))};k.render=function(){Hn(this)};k.om=function(){Hn(this)};
+k.qm=function(){Hn(this);if(this.get(Fn)&&this.autoPan){var a=this.Ve();if(a&&a.Cc()){var b=In(a.Cc(),a.Cb()),c=this.Rd(),d=c.offsetWidth,e=getComputedStyle(c);d+=parseInt(e.marginLeft,10)+parseInt(e.marginRight,10);e=c.offsetHeight;var f=getComputedStyle(c);e+=parseInt(f.marginTop,10)+parseInt(f.marginBottom,10);var g=In(c,[d,e]);c=this.autoPanMargin;La(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.aa().xa(),c=a.Ia(c),b=[c[0]+b[0],c[1]+b[1]],a.aa().animate({center:a.Ra(b),duration:this.autoPanAnimation.duration,easing:this.autoPanAnimation.easing}))}}};k.rm=function(){Hn(this)};k.Hj=function(a){this.set(Cn,a)};k.setMap=function(a){this.set(Dn,a)};k.Mj=function(a){this.set(En,a)};k.We=function(a){this.set(Fn,a)};function In(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.Pj=function(a){this.set(Gn,a)};
+function Jn(a,b){a.a.visible!==b&&(a.element.style.display=b?"":"none",a.a.visible=b)}
+function Hn(a){var b=a.Ve(),c=a.pi();if(b&&b.c&&c){c=b.Ia(c);var d=b.Cb();b=a.element.style;var e=a.Xh(),f=a.Yh();Jn(a,!0);var g=e[0];e=e[1];if("bottom-right"==f||"center-right"==f||"top-right"==f)""!==a.a.Se&&(a.a.Se=b.left=""),g=Math.round(d[0]-c[0]-g)+"px",a.a.xf!=g&&(a.a.xf=b.right=g);else{""!==a.a.xf&&(a.a.xf=b.right="");if("bottom-center"==f||"center-center"==f||"top-center"==f)g-=a.element.offsetWidth/2;g=Math.round(c[0]+g)+"px";a.a.Se!=g&&(a.a.Se=b.left=g)}if("bottom-left"==f||"bottom-center"==
+f||"bottom-right"==f)""!==a.a.Ef&&(a.a.Ef=b.top=""),c=Math.round(d[1]-c[1]-e)+"px",a.a.ze!=c&&(a.a.ze=b.bottom=c);else{""!==a.a.ze&&(a.a.ze=b.bottom="");if("center-left"==f||"center-center"==f||"center-right"==f)e-=a.element.offsetHeight/2;c=Math.round(c[1]+e)+"px";a.a.Ef!=c&&(a.a.Ef=b.top=c)}}else Jn(a,!1)}var Cn="element",Dn="map",En="offset",Fn="position",Gn="positioning";function Kn(a,b,c,d,e,f){cl.call(this,a,b,f);this.c=0;this.l=null;this.v=d;this.a=null;this.f={};this.C=e;this.N=c}w(Kn,cl);k=Kn.prototype;k.ia=function(){this.a=null;this.f={};this.state=5;this.u();cl.prototype.ia.call(this)};k.G=function(){return this.l||Ln};k.qn=function(){return this.v};k.pn=function(){return this.a};k.lb=function(){return this.N};k.rn=function(){return this.o};function ok(a,b,c){return a.f[x(b)+","+c]}
+k.load=function(){0==this.state&&(oj(this,1),this.C(this,this.N),this.D(null,NaN,null))};k.Cp=function(a,b,c){this.vg(b);this.Ij(a);this.ri(c)};k.Bp=function(){oj(this,3)};k.ri=function(a){this.l=a};k.Ij=function(a){this.a=a;oj(this,2)};k.vg=function(a){this.o=a};k.ug=function(a){this.D=a};var Ln=[0,0,4096,4096];function Mn(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.l="string"===typeof b?document.createTextNode(b):b;b=void 0!==a.labelActive?a.labelActive:"\u00d7";this.v="string"===typeof b?document.createTextNode(b):b;var c=a.tipLabel?a.tipLabel:"Toggle full-screen";b=document.createElement("button");b.className=this.c+"-"+Nn();b.setAttribute("type","button");b.title=c;b.appendChild(this.l);y(b,"click",this.C,this);c=document.createElement("div");
+c.className=this.c+" ol-unselectable ol-control "+(On()?"":"ol-unsupported");c.appendChild(b);vg.call(this,{element:c,target:a.target});this.D=void 0!==a.keys?a.keys:!1;this.j=a.source}w(Mn,vg);
+Mn.prototype.C=function(a){a.preventDefault();On()&&(a=this.a)&&(Nn()?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.Cc(),this.D?a.mozRequestFullScreenWithKeys?a.mozRequestFullScreenWithKeys():a.webkitRequestFullscreen?a.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):
+Pn(a):Pn(a)))};Mn.prototype.o=function(){var a=this.element.firstElementChild,b=this.a;Nn()?(a.className=this.c+"-true",ig(this.v,this.l)):(a.className=this.c+"-false",ig(this.l,this.v));b&&b.Oc()};Mn.prototype.setMap=function(a){vg.prototype.setMap.call(this,a);a&&this.s.push(y(document,Qn(),this.o,this))};
+function On(){var a=document.body;return!!(a.webkitRequestFullscreen||a.mozRequestFullScreen&&document.mozFullScreenEnabled||a.msRequestFullscreen&&document.msFullscreenEnabled||a.requestFullscreen&&document.fullscreenEnabled)}function Nn(){return!!(document.webkitIsFullScreen||document.mozFullScreen||document.msFullscreenElement||document.fullscreenElement)}
+function Pn(a){a.requestFullscreen?a.requestFullscreen():a.msRequestFullscreen?a.msRequestFullscreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.webkitRequestFullscreen&&a.webkitRequestFullscreen()}var Qn=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 Rn(a){a=a?a:{};var b=document.createElement("DIV");b.className=void 0!==a.className?a.className:"ol-mouse-position";vg.call(this,{element:b,render:a.render?a.render:Sn,target:a.target});y(this,Xc(Tn),this.En,this);a.coordinateFormat&&this.Gj(a.coordinateFormat);a.projection&&this.ti(a.projection);this.o=void 0!==a.undefinedHTML?a.undefinedHTML:"";this.v=b.innerHTML;this.l=this.j=this.c=null}w(Rn,vg);
+function Sn(a){a=a.frameState;a?this.c!=a.viewState.projection&&(this.c=a.viewState.projection,this.j=null):this.c=null;Un(this,this.l)}k=Rn.prototype;k.En=function(){this.j=null};k.Qh=function(){return this.get(Vn)};k.si=function(){return this.get(Tn)};k.mm=function(a){this.l=this.a.ud(a);Un(this,this.l)};k.nm=function(){Un(this,null);this.l=null};k.setMap=function(a){vg.prototype.setMap.call(this,a);a&&(a=a.a,this.s.push(y(a,"mousemove",this.mm,this),y(a,"mouseout",this.nm,this)))};
+k.Gj=function(a){this.set(Vn,a)};k.ti=function(a){this.set(Tn,Ob(a))};function Un(a,b){var c=a.o;if(b&&a.c){if(!a.j){var d=a.si();a.j=d?Pb(a.c,d):$b}if(b=a.a.Ra(b))a.j(b,b),c=(c=a.Qh())?c(b):b.toString()}a.v&&c==a.v||(a.element.innerHTML=c,a.v=c)}var Tn="projection",Vn="coordinateFormat";function Wn(a){function b(a){a=h.Sd(a);l.a.aa().ub(a);window.removeEventListener("mousemove",c);window.removeEventListener("mouseup",b)}function c(a){a=h.Sd({clientX:a.clientX-n.offsetWidth/2,clientY:a.clientY+n.offsetHeight/2});m.We(a)}a=a?a:{};this.j=void 0!==a.collapsed?a.collapsed:!0;this.l=void 0!==a.collapsible?a.collapsible:!0;this.l||(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.o=document.createElement("span"),this.o.textContent=f):this.o=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.l&&!this.j?this.o:this.D;f=document.createElement("button");f.setAttribute("type","button");f.title=e;f.appendChild(g);y(f,"click",this.Hn,this);this.C=document.createElement("DIV");this.C.className="ol-overviewmap-map";var h=this.c=new G({controls:new B,interactions:new B,
+view:a.view});a.layers&&a.layers.forEach(function(a){h.xe(a)},this);e=document.createElement("DIV");e.className="ol-overviewmap-box";e.style.boxSizing="border-box";this.v=new Bn({position:[0,0],positioning:"bottom-left",element:e});this.c.ye(this.v);e=document.createElement("div");e.className=d+" ol-unselectable ol-control"+(this.j&&this.l?" ol-collapsed":"")+(this.l?"":" ol-uncollapsible");e.appendChild(this.C);e.appendChild(f);vg.call(this,{element:e,render:a.render?a.render:Xn,target:a.target});
+var l=this,m=this.v,n=this.v.Rd();n.addEventListener("mousedown",function(){window.addEventListener("mousemove",c);window.addEventListener("mouseup",b)})}w(Wn,vg);k=Wn.prototype;k.setMap=function(a){var b=this.a;a!==b&&(b&&((b=b.aa())&&Mc(b,Xc("rotation"),this.Qe,this),this.c.Ad(null)),vg.prototype.setMap.call(this,a),a&&(this.c.Ad(this.C),this.s.push(y(a,"propertychange",this.lm,this)),0===this.c.Xe().kc()&&this.c.zf(a.hc()),a=a.aa()))&&(y(a,Xc("rotation"),this.Qe,this),ag(a)&&(this.c.Oc(),Yn(this)))};
+k.lm=function(a){"view"===a.key&&((a=a.oldValue)&&Mc(a,Xc("rotation"),this.Qe,this),a=this.a.aa(),y(a,Xc("rotation"),this.Qe,this))};k.Qe=function(){this.c.aa().ce(this.a.aa().Sa())};function Xn(){var a=this.a,b=this.c;if(a.c&&b.c){var c=a.Cb();a=a.aa().qd(c);var d=b.Cb();c=b.aa().qd(d);var e=b.Ia($a(a)),f=b.Ia(Ya(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?Yn(this):La(c,a)||(a=this.c,c=this.a.aa(),a.aa().ub(c.xa()))}Zn(this)}
+function Yn(a){var b=a.a;a=a.c;var c=b.Cb();b=b.aa().qd(c);a=a.aa();ib(b,1/(.1*Math.pow(2,Math.log(7.5)/Math.LN2/2)));a.Uf(b)}function Zn(a){var b=a.a,c=a.c;if(b.c&&c.c){var d=b.Cb(),e=b.aa(),f=c.aa();c=e.Sa();b=a.v;var g=a.v.Rd(),h=e.qd(d);d=f.Pa();e=Wa(h);f=Za(h);if(a=a.a.aa().xa()){var l=[e[0]-a[0],e[1]-a[1]];Fe(l,c);ze(l,a)}b.We(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.Hn=function(a){a.preventDefault();$n(this)};
+function $n(a){a.element.classList.toggle("ol-collapsed");a.j?ig(a.o,a.D):ig(a.D,a.o);a.j=!a.j;var b=a.c;a.j||b.c||(b.Oc(),Yn(a),Lc(b,"postrender",function(){Zn(this)},a))}k.Gn=function(){return this.l};k.Jn=function(a){this.l!==a&&(this.l=a,this.element.classList.toggle("ol-uncollapsible"),!a&&this.j&&$n(this))};k.In=function(a){this.l&&this.j!==a&&$n(this)};k.Fn=function(){return this.j};k.Hl=function(){return this.c};function ao(a){a=a?a:{};var b=void 0!==a.className?a.className:"ol-scale-line";this.l=document.createElement("DIV");this.l.className=b+"-inner";this.c=document.createElement("DIV");this.c.className=b+" ol-unselectable";this.c.appendChild(this.l);this.o=null;this.v=void 0!==a.minWidth?a.minWidth:64;this.j=!1;this.B=void 0;this.D="";vg.call(this,{element:this.c,render:a.render?a.render:bo,target:a.target});y(this,Xc(co),this.V,this);this.O(a.units||"metric")}w(ao,vg);var eo=[1,2,5];ao.prototype.C=function(){return this.get(co)};
+function bo(a){(a=a.frameState)?this.o=a.viewState:this.o=null;fo(this)}ao.prototype.V=function(){fo(this)};ao.prototype.O=function(a){this.set(co,a)};
+function fo(a){var b=a.o;if(b){var c=b.center,d=b.projection,e=a.C();b=Nb(d,b.resolution,c,"degrees"==e?"degrees":"m");"degrees"!=e&&(b*=d.Bc());var f=a.v*b;c="";"degrees"==e?(c=ub.degrees,"degrees"==d.a?f*=c:b/=c,f<c/60?(c="\u2033",b*=3600):f<c?(c="\u2032",b*=60):c="\u00b0"):"imperial"==e?.9144>f?(c="in",b/=.0254):1609.344>f?(c="ft",b/=.3048):(c="mi",b/=1609.344):"nautical"==e?(b/=1852,c="nm"):"metric"==e?.001>f?(c="\u03bcm",b*=1E6):1>f?(c="mm",b*=1E3):1E3>f?c="m":(c="km",b/=1E3):"us"==e?.9144>f?
+(c="in",b*=39.37):1609.344>f?(c="ft",b/=.30480061):(c="mi",b/=1609.3472):oa(!1,33);for(e=3*Math.floor(Math.log(a.v*b)/Math.log(10));;){f=eo[(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.v)break;++e}b=f+" "+c;a.D!=b&&(a.l.innerHTML=b,a.D=b);a.B!=d&&(a.l.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 co="units";function go(a){a=a?a:{};this.c=void 0;this.j=ho;this.D=this.v=0;this.O=null;this.$=!1;this.V=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.l=new Xd(d);y(this.l,"pointerdown",this.$l,this);y(this.l,"pointermove",this.Yl,this);y(this.l,"pointerup",this.Zl,
+this);y(d,"click",this.Xl,this);y(c,"click",Rc);vg.call(this,{element:d,render:a.render?a.render:io})}w(go,vg);go.prototype.ia=function(){Pc(this.l);vg.prototype.ia.call(this)};var ho=0;k=go.prototype;k.setMap=function(a){vg.prototype.setMap.call(this,a);a&&a.render()};
+function io(a){if(a.frameState){if(!this.$){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.O=[b,e];c>d?(this.j=1,this.D=c-b):(this.j=ho,this.v=d-e);this.$=!0}a=a.frameState.viewState.resolution;a!==this.c&&(this.c=a,jo(this,a))}}
+k.Xl=function(a){var b=this.a.aa();a=ko(this,pa(1===this.j?(a.offsetX-this.O[0]/2)/this.D:(a.offsetY-this.O[1]/2)/this.v,0,1));b.animate({resolution:b.constrainResolution(a),duration:this.V,easing:Oe})};k.$l=function(a){this.o||a.b.target!==this.element.firstElementChild||(bg(this.a.aa(),1,1),this.C=a.clientX,this.B=a.clientY,this.o=!0)};
+k.Yl=function(a){if(this.o){var b=this.element.firstElementChild;this.c=ko(this,pa(1===this.j?(a.clientX-this.C+parseInt(b.style.left,10))/this.D:(a.clientY-this.B+parseInt(b.style.top,10))/this.v,0,1));this.a.aa().gd(this.c);jo(this,this.c);this.C=a.clientX;this.B=a.clientY}};k.Zl=function(){if(this.o){var a=this.a.aa();bg(a,1,-1);a.animate({resolution:a.constrainResolution(this.c),duration:this.V,easing:Oe});this.o=!1;this.B=this.C=void 0}};
+function jo(a,b){b=1-gg(a.a.aa())(b);var c=a.element.firstElementChild;1==a.j?c.style.left=a.D*b+"px":c.style.top=a.v*b+"px"}function ko(a,b){return fg(a.a.aa())(1-b)};function lo(a){a=a?a:{};this.extent=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.c,this);c=document.createElement("div");c.className=b+" ol-unselectable ol-control";c.appendChild(e);vg.call(this,{element:c,target:a.target})}
+w(lo,vg);lo.prototype.c=function(a){a.preventDefault();a=this.a.aa();var b=this.extent?this.extent:a.v.G();a.Uf(b)};var mo=document.implementation.createDocument("","",null);function no(a,b){return mo.createElementNS(a,b)}function oo(a,b){return po(a,b,[]).join("")}function po(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)po(a,b,c);return c}function qo(a){return a instanceof Document}function ro(a){return a instanceof Node}
+function so(a){return(new DOMParser).parseFromString(a,"application/xml")}function to(a,b){return function(c,d){c=a.call(b,c,d);void 0!==c&&gc(d[d.length-1],c)}}function uo(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 vo(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 wo(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 L(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 M(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 xo(a){var b,c;return function(d,e,f){if(void 0===b){b={};var g={};g[d.localName]=a;b[d.namespaceURI]=g;c=yo(d.localName)}zo(b,c,e,f)}}function yo(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 no(e,d)}}var Ao=yo();function Bo(a,b){for(var c=b.length,d=Array(c),e=0;e<c;++e)d[e]=a[b[e]];return d}function N(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 Co(a,b,c,d){for(b=b.firstElementChild;b;b=b.nextElementSibling){var e=a[b.namespaceURI];void 0!==e&&(e=e[b.localName],void 0!==e&&e.call(d,b,c))}}function O(a,b,c,d,e){d.push(a);Co(b,c,d,e);return d.pop()}function zo(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 Do(a,b,c,d,e,f,g){e.push(a);zo(b,c,d,e,f,g);e.pop()};function Eo(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.S()&&(h.responseType="arraybuffer");h.onload=function(){if(!h.status||200<=h.status&&300>h.status){var a=b.S();if("json"==a||"text"==a)var e=h.responseText;else"xml"==a?(e=h.responseXML)||(e=so(h.responseText)):"arraybuffer"==a&&(e=h.response);e?c.call(this,b.Qa(e,{featureProjection:g}),b.sb(e),b.cg()):d.call(this)}else d.call(this)}.bind(this);h.onerror=function(){d.call(this)}.bind(this);
+h.send()}}function Fo(a,b){return Eo(a,b,function(a){this.Qc(a)},ea)};function Go(){this.i=this.defaultDataProjection=null}function Ho(a,b,c){var d;c&&(d={dataProjection:c.dataProjection?c.dataProjection:a.sb(b),featureProjection:c.featureProjection});return Io(a,d)}function Io(a,b){return kb({dataProjection:a.defaultDataProjection,featureProjection:a.i},b)}Go.prototype.cg=function(){return null};
+function Jo(a,b,c){var d=c?Ob(c.featureProjection):null,e=c?Ob(c.dataProjection):null,f;d&&e&&!Xb(d,e)?a instanceof gf?f=(b?a.clone():a).mb(b?d:e,b?e:d):f=bc(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.Rc(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 Ko(){Go.call(this)}w(Ko,Go);function Lo(a){return"string"===typeof a?(a=JSON.parse(a))?a:null:null!==a?a:null}k=Ko.prototype;k.S=function(){return"json"};k.Yb=function(a,b){return this.dd(Lo(a),Ho(this,a,b))};k.Qa=function(a,b){return this.Mg(Lo(a),Ho(this,a,b))};k.ed=function(a,b){return this.Qg(Lo(a),Ho(this,a,b))};k.sb=function(a){return this.Tg(Lo(a))};k.Jd=function(a,b){return JSON.stringify(this.ld(a,b))};k.ac=function(a,b){return JSON.stringify(this.qe(a,b))};
+k.md=function(a,b){return JSON.stringify(this.se(a,b))};function P(a,b){hf.call(this);this.c=[];this.j=this.o=-1;this.na(a,b)}w(P,hf);k=P.prototype;k.Gk=function(a){this.A?gc(this.A,a.da().slice()):this.A=a.da().slice();this.c.push(this.A.length);this.u()};k.clone=function(){var a=new P(null);a.ba(this.ja,this.A.slice(),this.c.slice());return a};k.Nb=function(a,b,c,d){if(d<Ha(this.G(),a,b))return d;this.j!=this.g&&(this.o=Math.sqrt(qf(this.A,0,this.c,this.a,0)),this.j=this.g);return uf(this.A,0,this.c,this.a,this.o,!1,a,b,c,d)};
+k.Wn=function(a,b,c){return"XYM"!=this.ja&&"XYZM"!=this.ja||0===this.A.length?null:Tk(this.A,this.c,this.a,a,void 0!==b?b:!1,void 0!==c?c:!1)};k.W=function(){return zf(this.A,0,this.c,this.a)};k.pb=function(){return this.c};k.yl=function(a){if(0>a||this.c.length<=a)return null;var b=new I(null);b.ba(this.ja,this.A.slice(0===a?0:this.c[a-1],this.c[a]));return b};
+k.wd=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 I(null);l.ba(c,a.slice(e,h));d.push(l);e=h}return d};k.Ge=function(){var a=[],b=this.A,c=0,d=this.c,e=this.a,f;var g=0;for(f=d.length;g<f;++g){var h=d[g];c=Kk(b,c,h,e,.5);gc(a,c);c=h}return a};k.xd=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=Bf(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.S=function(){return"MultiLineString"};
+k.$a=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(Kf(b,e,c[g],d,a)){a=!0;break a}e=c[g]}a=!1}return a};k.na=function(a,b){a?(lf(this,b,a,2),this.A||(this.A=[]),a=xf(this.A,0,a,this.a,this.c),this.A.length=0===a.length?0:a[a.length-1],this.u()):this.ba("XY",null,this.c)};k.ba=function(a,b,c){kf(this,a,b);this.c=c;this.u()};
+function Mo(a,b){var c=a.ja,d=[],e=[],f;var g=0;for(f=b.length;g<f;++g){var h=b[g];0===g&&(c=h.ja);gc(d,h.da());e.push(d.length)}a.ba(c,d,e)};function No(a,b){hf.call(this);this.na(a,b)}w(No,hf);k=No.prototype;k.Ik=function(a){this.A?gc(this.A,a.da()):this.A=a.da().slice();this.u()};k.clone=function(){var a=new No(null);a.ba(this.ja,this.A.slice());return a};k.Nb=function(a,b,c,d){if(d<Ha(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=ua(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.W=function(){return yf(this.A,0,this.A.length,this.a)};
+k.Ll=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.de=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.S=function(){return"MultiPoint"};k.$a=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(Ka(a,f,g))return!0}return!1};
+k.na=function(a,b){a?(lf(this,b,a,1),this.A||(this.A=[]),this.A.length=wf(this.A,0,a,this.a),this.u()):this.ba("XY",null)};k.ba=function(a,b){kf(this,a,b);this.u()};function Q(a,b){hf.call(this);this.c=[];this.o=-1;this.D=null;this.T=this.C=this.B=-1;this.j=null;this.na(a,b)}w(Q,hf);k=Q.prototype;k.Jk=function(a){if(this.A){var b=this.A.length;gc(this.A,a.da());a=a.pb().slice();var c;var d=0;for(c=a.length;d<c;++d)a[d]+=b}else this.A=a.da().slice(),a=a.pb().slice(),this.c.push();this.c.push(a);this.u()};k.clone=function(){for(var a=new Q(null),b=this.c.length,c=Array(b),d=0;d<b;++d)c[d]=this.c[d].slice();a.ba(this.ja,this.A.slice(),c);return a};
+k.Nb=function(a,b,c,d){if(d<Ha(this.G(),a,b))return d;if(this.C!=this.g){var e=this.c,f=0,g=0,h;var l=0;for(h=e.length;l<h;++l){var m=e[l];g=qf(this.A,f,m,this.a,g);f=m[m.length-1]}this.B=Math.sqrt(g);this.C=this.g}e=Ii(this);f=this.c;g=this.a;l=this.B;h=0;m=[NaN,NaN];var n;var p=0;for(n=f.length;p<n;++p){var q=f[p];d=uf(e,h,q,g,l,!0,a,b,c,d,m);h=q[q.length-1]}return d};
+k.Zc=function(a,b){a:{var c=Ii(this),d=this.c,e=0;if(0!==d.length){var f;var g=0;for(f=d.length;g<f;++g){var h=d[g];if(Hf(c,e,h,this.a,a,b)){a=!0;break a}e=h[h.length-1]}}a=!1}return a};k.Xn=function(){var a=Ii(this),b=this.c,c=0,d=0,e;var f=0;for(e=b.length;f<e;++f){var g=b[f];d+=nf(a,c,g,this.a);c=g[g.length-1]}return d};k.W=function(a){if(void 0!==a){var b=Ii(this).slice();Pf(b,this.c,this.a,a)}else b=this.A;return Af(b,0,this.c,this.a)};k.td=function(){return this.c};
+function Ji(a){if(a.o!=a.g){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=Qa(b,e,l[0],d);f.push((e[0]+e[2])/2,(e[1]+e[3])/2);e=l[l.length-1]}b=Ii(a);c=a.c;d=a.a;h=0;g=[];l=0;for(e=c.length;l<e;++l){var m=c[l];g=If(b,h,m,d,f,2*l,g);h=m[m.length-1]}a.D=g;a.o=a.g}return a.D}k.ul=function(){var a=new No(null);a.ba("XYM",Ji(this).slice());return a};
+function Ii(a){if(a.T!=a.g){var b=a.A;a:{var c=a.c;var d;var e=0;for(d=c.length;e<d;++e)if(!Nf(b,c[e],a.a,void 0)){c=!1;break a}c=!0}c?a.j=b:(a.j=b.slice(),a.j.length=Pf(a.j,a.c,a.a));a.T=a.g}return a.j}k.xd=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=Cf(d,g,n,f,a,b,h,p);c.push(p);g=n[n.length-1]}b.length=h;d=new Q(null);d.ba("XY",b,c);return d};
+k.Ml=function(a){if(0>a||this.c.length<=a)return null;if(0===a)var b=0;else b=this.c[a-1],b=b[b.length-1];a=this.c[a].slice();var c=a[a.length-1];if(0!==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.Vd=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(0!==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.S=function(){return"MultiPolygon"};k.$a=function(a){a:{var b=Ii(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(Lf(b,e,h,d,a)){a=!0;break a}e=h[h.length-1]}a=!1}return a};
+k.na=function(a,b){if(a){lf(this,b,a,3);this.A||(this.A=[]);b=this.A;var c=this.a,d=this.c,e=0;d=d?d:[];var f=0,g;var h=0;for(g=a.length;h<g;++h)e=xf(b,e,a[h],c,d[f]),d[f++]=e,e=e[e.length-1];d.length=f;0===d.length?this.A.length=0:(a=d[d.length-1],this.A.length=0===a.length?0:a[a.length-1]);this.u()}else this.ba("XY",null,this.c)};k.ba=function(a,b,c){kf(this,a,b);this.c=c;this.u()};
+function Oo(a,b){var c=a.ja,d=[],e=[],f;var g=0;for(f=b.length;g<f;++g){var h=b[g];0===g&&(c=h.ja);var l=d.length;var m=h.pb();var n;var p=0;for(n=m.length;p<n;++p)m[p]+=l;gc(d,h.da());e.push(m)}a.ba(c,d,e)};function Po(a){a=a?a:{};Go.call(this);this.b=a.geometryName}w(Po,Ko);
+function Qo(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=Ro(a),f=[],g=[];c=[];var h;var l=0;for(h=d.length;l<h;++l)f.length=0,wf(f,0,d[l],e.length),Mf(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(La((new Df(g[l][0])).G(),(new Df(d)).G())){g[l].push(d);e=!0;break}e||
+g.push([d.reverse()])}a=kb({},a);1===g.length?(c="Polygon",a.rings=g[0]):(c="MultiPolygon",a.rings=g)}return Jo((0,So[c])(a),!1,b)}function Ro(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 To(a){a=a.ja;return{hasZ:"XYZ"===a||"XYZM"===a,hasM:"XYM"===a||"XYZM"===a}}
+var So={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 I(a.paths[0],Ro(a))},Polygon:function(a){return new D(a.rings,Ro(a))},MultiPoint:function(a){return new No(a.points,Ro(a))},MultiLineString:function(a){return new P(a.paths,Ro(a))},MultiPolygon:function(a){return new Q(a.rings,Ro(a))}},Uo={Point:function(a){var b=a.W(),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]}:oa(!1,34);return c},LineString:function(a){var b=To(a);return{hasZ:b.hasZ,hasM:b.hasM,paths:[a.W()]}},Polygon:function(a){var b=To(a);return{hasZ:b.hasZ,hasM:b.hasM,rings:a.W(!1)}},MultiPoint:function(a){var b=To(a);return{hasZ:b.hasZ,hasM:b.hasM,points:a.W()}},MultiLineString:function(a){var b=To(a);return{hasZ:b.hasZ,hasM:b.hasM,paths:a.W()}},MultiPolygon:function(a){var b=
+To(a);a=a.W(!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=Po.prototype;k.dd=function(a,b){var c=Qo(a.geometry,b),d=new Hk;this.b&&d.Lc(this.b);d.Va(c);b&&b.pg&&a.attributes[b.pg]&&d.qc(a.attributes[b.pg]);a.attributes&&d.H(a.attributes);return d};k.Mg=function(a,b){b=b?b:{};if(a.features){var c=[],d=a.features,e;b.pg=a.objectIdFieldName;a=0;for(e=d.length;a<e;++a)c.push(this.dd(d[a],b));return c}return[this.dd(a,b)]};
+k.Qg=function(a,b){return Qo(a,b)};k.Tg=function(a){return a.spatialReference&&a.spatialReference.wkid?Ob("EPSG:"+a.spatialReference.wkid):null};function Vo(a,b){return(0,Uo[a.S()])(Jo(a,!0,b),b)}k.se=function(a,b){return Vo(a,Io(this,b))};k.ld=function(a,b){b=Io(this,b);var c={},d=a.U();d&&(c.geometry=Vo(d,b),b&&b.featureProjection&&(c.geometry.spatialReference={wkid:Ob(b.featureProjection).wb.split(":").pop()}));b=a.L();delete b[a.a];c.attributes=nb(b)?{}:b;return c};
+k.qe=function(a,b){b=Io(this,b);var c=[],d;var e=0;for(d=a.length;e<d;++e)c.push(this.ld(a[e],b));return{features:c}};function Wo(){this.g=new XMLSerializer;Go.call(this)}w(Wo,Go);k=Wo.prototype;k.S=function(){return"xml"};k.Yb=function(a,b){return qo(a)?Xo(this,a,b):ro(a)?this.Lg(a,b):"string"===typeof a?(a=so(a),Xo(this,a,b)):null};function Xo(a,b,c){a=Yo(a,b,c);return 0<a.length?a[0]:null}k.Lg=function(){return null};k.Qa=function(a,b){return qo(a)?Yo(this,a,b):ro(a)?this.Kc(a,b):"string"===typeof a?(a=so(a),Yo(this,a,b)):[]};
+function Yo(a,b,c){var d=[];for(b=b.firstChild;b;b=b.nextSibling)b.nodeType==Node.ELEMENT_NODE&&gc(d,a.Kc(b,c));return d}k.ed=function(a,b){if(qo(a))return null;if(ro(a))return this.vj(a,b);"string"===typeof a&&so(a);return null};k.vj=function(){return null};k.sb=function(a){return qo(a)?this.Sg(a):ro(a)?this.uf(a):"string"===typeof a?(a=so(a),this.Sg(a)):null};k.Sg=function(){return this.defaultDataProjection};k.uf=function(){return this.defaultDataProjection};k.Jd=function(){return this.g.serializeToString(this.mh())};
+k.mh=function(){return null};k.ac=function(a,b){a=this.bc(a,b);return this.g.serializeToString(a)};k.bc=function(){return null};k.md=function(a,b){a=this.re(a,b);return this.g.serializeToString(a)};k.re=function(){return null};function Zo(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:vo(Zo.prototype.ge),featureMembers:vo(Zo.prototype.ge)};Wo.call(this)}w(Zo,Wo);var $o=/^[\s\xa0]*$/;k=Zo.prototype;
+k.ge=function(a,b){var c=a.localName,d=null;if("FeatureCollection"==c)"http://www.opengis.net/wfs"===a.namespaceURI?d=O([],this.b,a,b,this):d=O(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;for(var 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);e={};f=Array.isArray(f)?f:[f];for(var 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?uo(this.Kg,this):vo(this.Kg,this));e[g[u]]=n}"featureMember"==c?d=O(void 0,e,a,b):d=O([],e,a,b)}null===d&&(d=[]);return d};
+k.rf=function(a,b){var c=b[0];c.srsName=a.firstElementChild.getAttribute("srsName");c.srsDimension=a.firstElementChild.getAttribute("srsDimension");if(a=O(null,this.qh,a,b,this))return Jo(a,!1,c)};
+k.Kg=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=oo(a,!1);$o.test(g)&&(g=void 0);d[f]=g}else"boundedBy"!==f&&(e=f),d[f]=this.rf(a,b)}b=new Hk(d);e&&b.Lc(e);c&&b.qc(c);return b};
+k.Aj=function(a,b){if(a=this.qf(a,b))return b=new C(null),b.ba("XYZ",a),b};k.yj=function(a,b){if(a=O([],this.kk,a,b,this))return new No(a)};k.xj=function(a,b){if(a=O([],this.jk,a,b,this))return b=new P(null),Mo(b,a),b};k.zj=function(a,b){if(a=O([],this.lk,a,b,this))return b=new Q(null),Oo(b,a),b};k.qj=function(a,b){Co(this.pk,a,b,this)};k.fi=function(a,b){Co(this.hk,a,b,this)};k.rj=function(a,b){Co(this.qk,a,b,this)};k.sf=function(a,b){if(a=this.qf(a,b))return b=new I(null),b.ba("XYZ",a),b};
+k.Xp=function(a,b){if(a=O(null,this.te,a,b,this))return a};k.wj=function(a,b){if(a=this.qf(a,b))return b=new Df(null),Ef(b,"XYZ",a),b};k.tf=function(a,b){if((a=O([null],this.Gf,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)gc(c,a[f]),d.push(c.length);b.ba("XYZ",c,d);return b}};k.qf=function(a,b){return O(null,this.te,a,b,this)};k.kk={"http://www.opengis.net/gml":{pointMember:uo(Zo.prototype.qj),pointMembers:uo(Zo.prototype.qj)}};
+k.jk={"http://www.opengis.net/gml":{lineStringMember:uo(Zo.prototype.fi),lineStringMembers:uo(Zo.prototype.fi)}};k.lk={"http://www.opengis.net/gml":{polygonMember:uo(Zo.prototype.rj),polygonMembers:uo(Zo.prototype.rj)}};k.pk={"http://www.opengis.net/gml":{Point:uo(Zo.prototype.qf)}};k.hk={"http://www.opengis.net/gml":{LineString:uo(Zo.prototype.sf)}};k.qk={"http://www.opengis.net/gml":{Polygon:uo(Zo.prototype.tf)}};k.ue={"http://www.opengis.net/gml":{LinearRing:vo(Zo.prototype.Xp)}};
+k.vj=function(a,b){return(a=this.rf(a,[Ho(this,a,b?b:{})]))?a:null};k.Kc=function(a,b){var c={featureType:this.featureType,featureNS:this.featureNS};b&&kb(c,Ho(this,a,b));return this.ge(a,[c])||[]};k.uf=function(a){return Ob(this.srsName?this.srsName:a.firstElementChild.getAttribute("srsName"))};function ap(a){a=oo(a,!1);return bp(a)}function bp(a){if(a=/^\s*(true|1)|(false|0)\s*$/.exec(a))return void 0!==a[1]||!1}function cp(a){a=oo(a,!1);a=Date.parse(a);return isNaN(a)?void 0:a/1E3}function dp(a){a=oo(a,!1);return ep(a)}function ep(a){if(a=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(a))return parseFloat(a[1])}function fp(a){a=oo(a,!1);return gp(a)}function gp(a){if(a=/^\s*(\d+)\s*$/.exec(a))return parseInt(a[1],10)}function R(a){return oo(a,!1).trim()}
+function hp(a,b){ip(a,b?"1":"0")}function Ip(a,b){a.appendChild(mo.createTextNode(b.toPrecision()))}function Jp(a,b){a.appendChild(mo.createTextNode(b.toString()))}function ip(a,b){a.appendChild(mo.createTextNode(b))};function Kp(a){a=a?a:{};Zo.call(this,a);this.s=void 0!==a.surface?a.surface:!1;this.c=void 0!==a.curve?a.curve:!1;this.f=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";this.hasZ=void 0!==a.hasZ?a.hasZ:!1}w(Kp,Zo);k=Kp.prototype;
+k.aq=function(a,b){if(a=O([],this.ik,a,b,this))return b=new P(null),Mo(b,a),b};k.bq=function(a,b){if(a=O([],this.mk,a,b,this))return b=new Q(null),Oo(b,a),b};k.Gh=function(a,b){Co(this.ek,a,b,this)};k.Xj=function(a,b){Co(this.sk,a,b,this)};k.fq=function(a,b){return O([null],this.nk,a,b,this)};k.iq=function(a,b){return O([null],this.rk,a,b,this)};k.gq=function(a,b){return O([null],this.Gf,a,b,this)};k.$p=function(a,b){return O([null],this.te,a,b,this)};
+k.Fm=function(a,b){(a=O(void 0,this.ue,a,b,this))&&b[b.length-1].push(a)};k.Zk=function(a,b){(a=O(void 0,this.ue,a,b,this))&&(b[b.length-1][0]=a)};k.Bj=function(a,b){if((a=O([null],this.tk,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)gc(c,a[f]),d.push(c.length);b.ba("XYZ",c,d);return b}};k.tj=function(a,b){if(a=O([null],this.fk,a,b,this))return b=new I(null),b.ba("XYZ",a),b};
+k.Wp=function(a,b){a=O([null],this.gk,a,b,this);return Na(a[1][0],a[1][1],a[2][0],a[2][1])};k.Yp=function(a,b){var c=oo(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=Ob(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(0!==b)return a}};
+k.Pg=function(a,b){var c=oo(a,!1).replace(/^\s*|\s*$/g,"");b=b[0];var d=b.srsName,e=b.srsDimension;b="enu";d&&(b=Ob(d).b);c=c.split(/\s+/);d=2;a.getAttribute("srsDimension")?d=gp(a.getAttribute("srsDimension")):a.getAttribute("dimension")?d=gp(a.getAttribute("dimension")):a.parentNode.getAttribute("srsDimension")?d=gp(a.parentNode.getAttribute("srsDimension")):e&&(d=gp(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.te={"http://www.opengis.net/gml":{pos:vo(Kp.prototype.Yp),posList:vo(Kp.prototype.Pg)}};k.Gf={"http://www.opengis.net/gml":{interior:Kp.prototype.Fm,exterior:Kp.prototype.Zk}};
+k.qh={"http://www.opengis.net/gml":{Point:vo(Zo.prototype.Aj),MultiPoint:vo(Zo.prototype.yj),LineString:vo(Zo.prototype.sf),MultiLineString:vo(Zo.prototype.xj),LinearRing:vo(Zo.prototype.wj),Polygon:vo(Zo.prototype.tf),MultiPolygon:vo(Zo.prototype.zj),Surface:vo(Kp.prototype.Bj),MultiSurface:vo(Kp.prototype.bq),Curve:vo(Kp.prototype.tj),MultiCurve:vo(Kp.prototype.aq),Envelope:vo(Kp.prototype.Wp)}};k.ik={"http://www.opengis.net/gml":{curveMember:uo(Kp.prototype.Gh),curveMembers:uo(Kp.prototype.Gh)}};
+k.mk={"http://www.opengis.net/gml":{surfaceMember:uo(Kp.prototype.Xj),surfaceMembers:uo(Kp.prototype.Xj)}};k.ek={"http://www.opengis.net/gml":{LineString:uo(Zo.prototype.sf),Curve:uo(Kp.prototype.tj)}};k.sk={"http://www.opengis.net/gml":{Polygon:uo(Zo.prototype.tf),Surface:uo(Kp.prototype.Bj)}};k.tk={"http://www.opengis.net/gml":{patches:vo(Kp.prototype.fq)}};k.fk={"http://www.opengis.net/gml":{segments:vo(Kp.prototype.iq)}};k.gk={"http://www.opengis.net/gml":{lowerCorner:uo(Kp.prototype.Pg),upperCorner:uo(Kp.prototype.Pg)}};
+k.nk={"http://www.opengis.net/gml":{PolygonPatch:vo(Kp.prototype.gq)}};k.rk={"http://www.opengis.net/gml":{LineStringSegment:vo(Kp.prototype.$p)}};function Lp(a,b,c){var d=c[c.length-1];c=d.hasZ;a.setAttribute("srsDimension",c?3:2);d=d.srsName;b=b.W();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=Ob(d).b);n="en"===n.substr(0,2)?g[0]+" "+g[1]:g[1]+" "+g[0];m&&(n+=" "+(g[2]||0));f[l]=n}ip(a,f.join(" "))}
+k.Hi=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);d=no(a.namespaceURI,"pos");a.appendChild(d);c=c[c.length-1];a=c.hasZ;d.setAttribute("srsDimension",a?3:2);var e=c.srsName;c="enu";e&&(c=Ob(e).b);b=b.W();c="en"===c.substr(0,2)?b[0]+" "+b[1]:b[1]+" "+b[0];a&&(c+=" "+(b[2]||0));ip(d,c)};var Mp={"http://www.opengis.net/gml":{lowerCorner:M(ip),upperCorner:M(ip)}};k=Kp.prototype;
+k.Pn=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);Do({node:a},Mp,Ao,[b[0]+" "+b[1],b[2]+" "+b[3]],c,["lowerCorner","upperCorner"],this)};k.Ei=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);d=no(a.namespaceURI,"posList");a.appendChild(d);Lp(d,b,c)};k.On=function(a,b){a=b[b.length-1];b=a.node;var c=a.exteriorWritten;void 0===c&&(a.exteriorWritten=!0);return no(b.namespaceURI,void 0!==c?"interior":"exterior")};
+k.af=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.Ud(),Do({node:a,hasZ:e,srsName:d},Np,this.On,b,c,void 0,this)):"Surface"===a.nodeName&&(e=no(a.namespaceURI,"patches"),a.appendChild(e),a=no(e.namespaceURI,"PolygonPatch"),e.appendChild(a),this.af(a,b,c))};
+k.$e=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=no(a.namespaceURI,"posList"),a.appendChild(d),Lp(d,b,c)):"Curve"===a.nodeName&&(d=no(a.namespaceURI,"segments"),a.appendChild(d),a=no(d.namespaceURI,"LineStringSegment"),d.appendChild(a),this.$e(a,b,c))};
+k.Gi=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.Vd();Do({node:a,hasZ:e,srsName:f,surface:d},Op,this.l,b,c,void 0,this)};k.Qn=function(a,b,c){var d=c[c.length-1],e=d.srsName;d=d.hasZ;e&&a.setAttribute("srsName",e);b=b.de();Do({node:a,hasZ:d,srsName:e},Pp,yo("pointMember"),b,c,void 0,this)};
+k.Fi=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.wd();Do({node:a,hasZ:e,srsName:f,curve:d},Qp,this.l,b,c,void 0,this)};k.Ii=function(a,b,c){var d=no(a.namespaceURI,"LinearRing");a.appendChild(d);this.Ei(d,b,c)};k.Ji=function(a,b,c){var d=this.a(b,c);d&&(a.appendChild(d),this.af(d,b,c))};k.Rn=function(a,b,c){var d=no(a.namespaceURI,"Point");a.appendChild(d);this.Hi(d,b,c)};
+k.Di=function(a,b,c){var d=this.a(b,c);d&&(a.appendChild(d),this.$e(d,b,c))};k.Yc=function(a,b,c){var d=c[c.length-1],e=kb({},d);e.node=a;var f;Array.isArray(b)?d.dataProjection?f=bc(b,d.featureProjection,d.dataProjection):f=b:f=Jo(b,!0,d);Do(e,Rp,this.a,[f],c,void 0,this)};
+k.Ci=function(a,b,c){var d=b.c;d&&a.setAttribute("fid",d);d=c[c.length-1];var e=d.featureNS,f=b.a;d.tb||(d.tb={},d.tb[e]={});var g=b.L();b=[];var h=[];for(m in g){var l=g[m];null!==l&&(b.push(m),h.push(l),m==f||l instanceof gf?m in d.tb[e]||(d.tb[e][m]=M(this.Yc,this)):m in d.tb[e]||(d.tb[e][m]=M(ip)))}var m=kb({},d);m.node=a;Do(m,d.tb,yo(void 0,e),h,c,b)};
+var Op={"http://www.opengis.net/gml":{surfaceMember:M(Kp.prototype.Ji),polygonMember:M(Kp.prototype.Ji)}},Pp={"http://www.opengis.net/gml":{pointMember:M(Kp.prototype.Rn)}},Qp={"http://www.opengis.net/gml":{lineStringMember:M(Kp.prototype.Di),curveMember:M(Kp.prototype.Di)}},Np={"http://www.opengis.net/gml":{exterior:M(Kp.prototype.Ii),interior:M(Kp.prototype.Ii)}},Rp={"http://www.opengis.net/gml":{Curve:M(Kp.prototype.$e),MultiCurve:M(Kp.prototype.Fi),Point:M(Kp.prototype.Hi),MultiPoint:M(Kp.prototype.Qn),
+LineString:M(Kp.prototype.$e),MultiLineString:M(Kp.prototype.Fi),LinearRing:M(Kp.prototype.Ei),Polygon:M(Kp.prototype.af),MultiPolygon:M(Kp.prototype.Gi),Surface:M(Kp.prototype.af),MultiSurface:M(Kp.prototype.Gi),Envelope:M(Kp.prototype.Pn)}},Sp={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};Kp.prototype.l=function(a,b){return no("http://www.opengis.net/gml",Sp[b[b.length-1].node.nodeName])};
+Kp.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.S(),"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 no("http://www.opengis.net/gml",a)};
+Kp.prototype.re=function(a,b){b=Io(this,b);var c=no("http://www.opengis.net/gml","geom"),d={node:c,hasZ:this.hasZ,srsName:this.srsName,curve:this.c,surface:this.s,multiSurface:this.j,multiCurve:this.f};b&&kb(d,b);this.Yc(c,a,[d]);return c};
+Kp.prototype.bc=function(a,b){b=Io(this,b);var c=no("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.s,multiSurface:this.j,multiCurve:this.f,featureNS:this.featureNS,featureType:this.featureType};b&&kb(d,b);b=[d];var e=b[b.length-1];d=e.featureType;var f=e.featureNS,g={};g[f]={};g[f][d]=M(this.Ci,this);e=kb({},e);e.node=c;Do(e,g,
+yo(d,f),a,b);return c};function Tp(a){a=a?a:{};Zo.call(this,a);this.b["http://www.opengis.net/gml"].featureMember=uo(Zo.prototype.ge);this.schemaLocation=a.schemaLocation?a.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd"}w(Tp,Zo);k=Tp.prototype;
+k.uj=function(a,b){a=oo(a,!1).replace(/^\s*|\s*$/g,"");var c=b[0].srsName;b="enu";c&&(c=Ob(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.Up=function(a,b){a=O([null],this.dk,a,b,this);return Na(a[1][0],a[1][1],a[1][3],a[1][4])};k.Dm=function(a,b){(a=O(void 0,this.ue,a,b,this))&&b[b.length-1].push(a)};
+k.Ep=function(a,b){(a=O(void 0,this.ue,a,b,this))&&(b[b.length-1][0]=a)};k.te={"http://www.opengis.net/gml":{coordinates:vo(Tp.prototype.uj)}};k.Gf={"http://www.opengis.net/gml":{innerBoundaryIs:Tp.prototype.Dm,outerBoundaryIs:Tp.prototype.Ep}};k.dk={"http://www.opengis.net/gml":{coordinates:uo(Tp.prototype.uj)}};
+k.qh={"http://www.opengis.net/gml":{Point:vo(Zo.prototype.Aj),MultiPoint:vo(Zo.prototype.yj),LineString:vo(Zo.prototype.sf),MultiLineString:vo(Zo.prototype.xj),LinearRing:vo(Zo.prototype.wj),Polygon:vo(Zo.prototype.tf),MultiPolygon:vo(Zo.prototype.zj),Box:vo(Tp.prototype.Up)}};
+k.wg=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.S(),"MultiPolygon"===a&&!0===b?a="MultiSurface":"Polygon"===a&&!0===d?a="Surface":"MultiLineString"===a&&!0===c&&(a="MultiCurve"));return no("http://www.opengis.net/gml",a)};k.ui=function(a,b,c){var d=c[c.length-1],e=kb({},d);e.node=a;var f;Array.isArray(b)?d.dataProjection?f=bc(b,d.featureProjection,d.dataProjection):f=b:f=Jo(b,!0,d);Do(e,Up,this.wg,[f],c,void 0,this)};
+k.Ye=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=Vp(a.namespaceURI),a.appendChild(d),Wp(d,b,c)):"Curve"===a.nodeName&&(d=no(a.namespaceURI,"segments"),a.appendChild(d),a=no(d.namespaceURI,"LineStringSegment"),d.appendChild(a),this.Ye(a,b,c))};function Vp(a){a=no(a,"coordinates");a.setAttribute("decimal",".");a.setAttribute("cs",",");a.setAttribute("ts"," ");return a}
+function Wp(a,b,c){var d=c[c.length-1];c=d.hasZ;d=d.srsName;b=b.W();for(var e=b.length,f=Array(e),g,h=0;h<e;++h)g=b[h],f[h]=Xp(g,d,c);ip(a,f.join(" "))}
+k.Ze=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.Ud(),Do({node:a,hasZ:e,srsName:d},Yp,this.Kn,b,c,void 0,this)):"Surface"===a.nodeName&&(e=no(a.namespaceURI,"patches"),a.appendChild(e),a=no(e.namespaceURI,"PolygonPatch"),e.appendChild(a),this.Ze(a,b,c))};
+k.Kn=function(a,b){a=b[b.length-1];b=a.node;var c=a.exteriorWritten;void 0===c&&(a.exteriorWritten=!0);return no(b.namespaceURI,void 0!==c?"innerBoundaryIs":"outerBoundaryIs")};k.Ai=function(a,b,c){var d=no(a.namespaceURI,"LinearRing");a.appendChild(d);this.wi(d,b,c)};function Xp(a,b,c){var d="enu";b&&(d=Ob(b).b);b="en"===d.substr(0,2)?a[0]+","+a[1]:a[1]+","+a[0];c&&(b+=","+(a[2]||0));return b}
+k.xi=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.wd();Do({node:a,hasZ:e,srsName:f,curve:d},Zp,this.a,b,c,void 0,this)};k.zi=function(a,b,c){var d=c[c.length-1];c=d.hasZ;var e=d.srsName;e&&a.setAttribute("srsName",e);d=Vp(a.namespaceURI);a.appendChild(d);a=b.W();a=Xp(a,e,c);ip(d,a)};
+k.Mn=function(a,b,c){var d=c[c.length-1],e=d.hasZ;(d=d.srsName)&&a.setAttribute("srsName",d);b=b.de();Do({node:a,hasZ:e,srsName:d},$p,yo("pointMember"),b,c,void 0,this)};k.Nn=function(a,b,c){var d=no(a.namespaceURI,"Point");a.appendChild(d);this.zi(d,b,c)};k.vi=function(a,b,c){var d=this.wg(b,c);d&&(a.appendChild(d),this.Ye(d,b,c))};k.wi=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);d=Vp(a.namespaceURI);a.appendChild(d);Wp(d,b,c)};
+k.yi=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.Vd();Do({node:a,hasZ:e,srsName:f,surface:d},aq,this.a,b,c,void 0,this)};k.Bi=function(a,b,c){var d=this.wg(b,c);d&&(a.appendChild(d),this.Ze(d,b,c))};k.Ln=function(a,b,c){var d=c[c.length-1].srsName;d&&a.setAttribute("srsName",d);Do({node:a},bq,Ao,[b[0]+" "+b[1],b[2]+" "+b[3]],c,["lowerCorner","upperCorner"],this)};
+var Up={"http://www.opengis.net/gml":{Curve:M(Tp.prototype.Ye),MultiCurve:M(Tp.prototype.xi),Point:M(Tp.prototype.zi),MultiPoint:M(Tp.prototype.Mn),LineString:M(Tp.prototype.Ye),MultiLineString:M(Tp.prototype.xi),LinearRing:M(Tp.prototype.wi),Polygon:M(Tp.prototype.Ze),MultiPolygon:M(Tp.prototype.yi),Surface:M(Tp.prototype.Ze),MultiSurface:M(Tp.prototype.yi),Envelope:M(Tp.prototype.Ln)}},Yp={"http://www.opengis.net/gml":{outerBoundaryIs:M(Tp.prototype.Ai),innerBoundaryIs:M(Tp.prototype.Ai)}},$p={"http://www.opengis.net/gml":{pointMember:M(Tp.prototype.Nn)}},
+Zp={"http://www.opengis.net/gml":{lineStringMember:M(Tp.prototype.vi),curveMember:M(Tp.prototype.vi)}};Tp.prototype.a=function(a,b){return no("http://www.opengis.net/gml",cq[b[b.length-1].node.nodeName])};var cq={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"},aq={"http://www.opengis.net/gml":{surfaceMember:M(Tp.prototype.Bi),polygonMember:M(Tp.prototype.Bi)}},bq={"http://www.opengis.net/gml":{lowerCorner:M(ip),upperCorner:M(ip)}};function dq(a){a=a?a:{};Wo.call(this);this.defaultDataProjection=Ob("EPSG:4326");this.b=a.readExtensions}w(dq,Wo);var eq=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function fq(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 gq(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 hq(a,b){var c=b[b.length-1],d=a.getAttribute("href");null!==d&&(c.link=d);Co(iq,a,b)}function jq(a,b){b[b.length-1].extensionsNode_=a}
+function kq(a,b){var c=b[0];if(a=O({flatCoordinates:[],layoutOptions:{}},lq,a,b)){b=a.flatCoordinates;delete a.flatCoordinates;var d=a.layoutOptions;delete a.layoutOptions;d=gq(d,b);var e=new I(null);e.ba(d,b);Jo(e,!1,c);c=new Hk(e);c.H(a);return c}}
+function mq(a,b){var c=b[0];if(a=O({flatCoordinates:[],ends:[],layoutOptions:{}},nq,a,b)){b=a.flatCoordinates;delete a.flatCoordinates;var d=a.ends;delete a.ends;var e=a.layoutOptions;delete a.layoutOptions;e=gq(e,b,d);var f=new P(null);f.ba(e,b,d);Jo(f,!1,c);c=new Hk(f);c.H(a);return c}}function oq(a,b){var c=b[0];if(b=O({},pq,a,b)){var d={};a=fq([],d,a,b);d=gq(d,a);a=new C(a,d);Jo(a,!1,c);c=new Hk(a);c.H(b);return c}}
+var qq={rte:kq,trk:mq,wpt:oq},rq=N(eq,{rte:uo(kq),trk:uo(mq),wpt:uo(oq)}),iq=N(eq,{text:L(R,"linkText"),type:L(R,"linkType")}),lq=N(eq,{name:L(R),cmt:L(R),desc:L(R),src:L(R),link:hq,number:L(fp),extensions:jq,type:L(R),rtept:function(a,b){var c=O({},sq,a,b);c&&(b=b[b.length-1],fq(b.flatCoordinates,b.layoutOptions,a,c))}}),sq=N(eq,{ele:L(dp),time:L(cp)}),nq=N(eq,{name:L(R),cmt:L(R),desc:L(R),src:L(R),link:hq,number:L(fp),type:L(R),extensions:jq,trkseg:function(a,b){var c=b[b.length-1];Co(tq,a,b);c.ends.push(c.flatCoordinates.length)}}),
+tq=N(eq,{trkpt:function(a,b){var c=O({},uq,a,b);c&&(b=b[b.length-1],fq(b.flatCoordinates,b.layoutOptions,a,c))}}),uq=N(eq,{ele:L(dp),time:L(cp)}),pq=N(eq,{ele:L(dp),time:L(cp),magvar:L(dp),geoidheight:L(dp),name:L(R),cmt:L(R),desc:L(R),src:L(R),link:hq,sym:L(R),type:L(R),fix:L(R),sat:L(fp),hdop:L(dp),vdop:L(dp),pdop:L(dp),ageofdgpsdata:L(dp),dgpsid:L(fp),extensions:jq});
+function vq(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)}}dq.prototype.Lg=function(a,b){if(!ec(eq,a.namespaceURI))return null;var c=qq[a.localName];if(!c)return null;a=c(a,[Ho(this,a,b)]);if(!a)return null;vq(this,[a]);return a};dq.prototype.Kc=function(a,b){return ec(eq,a.namespaceURI)?"gpx"==a.localName&&(a=O([],rq,a,[Ho(this,a,b)]))?(vq(this,a),a):[]:[]};
+function wq(a,b,c){a.setAttribute("href",b);b=c[c.length-1].properties;Do({node:a},xq,Ao,[b.linkText,b.linkType],c,yq)}function zq(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":0!==b[3]&&(f.time=b[3]);case "XYZ":0!==b[2]&&(f.ele=b[2]);break;case "XYM":0!==b[2]&&(f.time=b[2])}b="rtept"==a.nodeName?Aq[e]:Bq[e];d=Bo(f,b);Do({node:a,properties:f},Cq,Ao,d,c,b)}
+var yq=["text","type"],xq=N(eq,{text:M(ip),type:M(ip)}),Dq=N(eq,"name cmt desc src link number type rtept".split(" ")),Eq=N(eq,{name:M(ip),cmt:M(ip),desc:M(ip),src:M(ip),link:M(wq),number:M(Jp),type:M(ip),rtept:xo(M(zq))}),Aq=N(eq,["ele","time"]),Fq=N(eq,"name cmt desc src link number type trkseg".split(" ")),Iq=N(eq,{name:M(ip),cmt:M(ip),desc:M(ip),src:M(ip),link:M(wq),number:M(Jp),type:M(ip),trkseg:xo(M(function(a,b,c){Do({node:a,geometryLayout:b.ja,properties:{}},Gq,Hq,b.W(),c)}))}),Hq=yo("trkpt"),
+Gq=N(eq,{trkpt:M(zq)}),Bq=N(eq,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),Cq=N(eq,{ele:M(Ip),time:M(function(a,b){b=new Date(1E3*b);a.appendChild(mo.createTextNode(b.getUTCFullYear()+"-"+xe(b.getUTCMonth()+1)+"-"+xe(b.getUTCDate())+"T"+xe(b.getUTCHours())+":"+xe(b.getUTCMinutes())+":"+xe(b.getUTCSeconds())+"Z"))}),magvar:M(Ip),geoidheight:M(Ip),name:M(ip),cmt:M(ip),desc:M(ip),src:M(ip),link:M(wq),sym:M(ip),type:M(ip),fix:M(ip),
+sat:M(Jp),hdop:M(Ip),vdop:M(Ip),pdop:M(Ip),ageofdgpsdata:M(Ip),dgpsid:M(Jp)}),Jq={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function Kq(a,b){if(a=a.U())if(a=Jq[a.S()])return no(b[b.length-1].node.namespaceURI,a)}
+var Lq=N(eq,{rte:M(function(a,b,c){var d=c[0],e=b.L();a={node:a,properties:e};if(b=b.U())b=Jo(b,!0,d),a.geometryLayout=b.ja,e.rtept=b.W();d=Dq[c[c.length-1].node.namespaceURI];e=Bo(e,d);Do(a,Eq,Ao,e,c,d)}),trk:M(function(a,b,c){var d=c[0],e=b.L();a={node:a,properties:e};if(b=b.U())b=Jo(b,!0,d),e.trkseg=b.wd();d=Fq[c[c.length-1].node.namespaceURI];e=Bo(e,d);Do(a,Iq,Ao,e,c,d)}),wpt:M(function(a,b,c){var d=c[0],e=c[c.length-1];e.properties=b.L();if(b=b.U())b=Jo(b,!0,d),e.geometryLayout=b.ja,zq(a,b.W(),
+c)})});dq.prototype.bc=function(a,b){b=Io(this,b);var c=no("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");Do({node:c},Lq,Kq,a,[b]);return c};function Mq(a){gf.call(this);this.a=a?a:null;Nq(this)}w(Mq,gf);function Oq(a){var b=[],c;var d=0;for(c=a.length;d<c;++d)b.push(a[d].clone());return b}function Pq(a){var b;if(a.a){var c=0;for(b=a.a.length;c<b;++c)Mc(a.a[c],"change",a.u,a)}}function Nq(a){var b;if(a.a){var c=0;for(b=a.a.length;c<b;++c)y(a.a[c],"change",a.u,a)}}k=Mq.prototype;k.clone=function(){var a=new Mq(null);a.Kj(this.a);return a};
+k.Nb=function(a,b,c,d){if(d<Ha(this.G(),a,b))return d;var e=this.a,f;var g=0;for(f=e.length;g<f;++g)d=e[g].Nb(a,b,c,d);return d};k.Zc=function(a,b){var c=this.a,d;var e=0;for(d=c.length;e<d;++e)if(c[e].Zc(a,b))return!0;return!1};k.Ae=function(a){Oa(a);for(var b=this.a,c=0,d=b.length;c<d;++c)Ta(a,b[c].G());return a};k.vd=function(){return Oq(this.a)};
+k.Wd=function(a){this.l!=this.g&&(lb(this.i),this.f=0,this.l=this.g);if(0>a||0!==this.f&&a<this.f)return this;var b=a.toString();if(this.i.hasOwnProperty(b))return this.i[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.Wd(a);c.push(l);l!==h&&(e=!0)}if(e)return a=new Mq(null),Pq(a),a.a=c,Nq(a),a.u(),this.i[b]=a;this.f=a;return this};k.S=function(){return"GeometryCollection"};k.$a=function(a){var b=this.a,c;var d=0;for(c=b.length;d<c;++d)if(b[d].$a(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.u()};k.scale=function(a,b,c){c||(c=eb(this.G()));for(var d=this.a,e=0,f=d.length;e<f;++e)d[e].scale(a,b,c);this.u()};k.Kj=function(a){a=Oq(a);Pq(this);this.a=a;Nq(this);this.u()};k.Rc=function(a){var b=this.a,c;var d=0;for(c=b.length;d<c;++d)b[d].Rc(a);this.u()};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.u()};k.ia=function(){Pq(this);gf.prototype.ia.call(this)};function Qq(a){a=a?a:{};Go.call(this);this.defaultDataProjection=Ob(a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326");a.featureProjection&&(this.i=Ob(a.featureProjection));this.b=a.geometryName;this.a=a.extractGeometryName}w(Qq,Ko);function Rq(a,b){return a?Jo((0,Sq[a.type])(a),!1,b):null}function Tq(a,b){return(0,Uq[a.S()])(Jo(a,!0,b),b)}
+var Sq={Point:function(a){return new C(a.coordinates)},LineString:function(a){return new I(a.coordinates)},Polygon:function(a){return new D(a.coordinates)},MultiPoint:function(a){return new No(a.coordinates)},MultiLineString:function(a){return new P(a.coordinates)},MultiPolygon:function(a){return new Q(a.coordinates)},GeometryCollection:function(a,b){a=a.geometries.map(function(a){return Rq(a,b)});return new Mq(a)}},Uq={Point:function(a){return{type:"Point",coordinates:a.W()}},LineString:function(a){return{type:"LineString",
+coordinates:a.W()}},Polygon:function(a,b){if(b)var c=b.rightHanded;return{type:"Polygon",coordinates:a.W(c)}},MultiPoint:function(a){return{type:"MultiPoint",coordinates:a.W()}},MultiLineString:function(a){return{type:"MultiLineString",coordinates:a.W()}},MultiPolygon:function(a,b){if(b)var c=b.rightHanded;return{type:"MultiPolygon",coordinates:a.W(c)}},GeometryCollection:function(a,b){return{type:"GeometryCollection",geometries:a.a.map(function(a){var c=kb({},b);delete c.featureProjection;return Tq(a,
+c)})}},Circle:function(){return{type:"GeometryCollection",geometries:[]}}};k=Qq.prototype;k.dd=function(a,b){a="Feature"===a.type?a:{type:"Feature",geometry:a};b=Rq(a.geometry,b);var c=new Hk;this.b?c.Lc(this.b):this.a&&void 0!==a.geometry_name&&c.Lc(a.geometry_name);c.Va(b);void 0!==a.id&&c.qc(a.id);a.properties&&c.H(a.properties);return c};
+k.Mg=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.dd(a[e],b))}else c=[this.dd(a,b)];return c};k.Qg=function(a,b){return Rq(a,b)};k.Tg=function(a){a=a.crs;var b;a?"name"==a.type?b=Ob(a.properties.name):oa(!1,36):b=this.defaultDataProjection;return b};
+k.ld=function(a,b){b=Io(this,b);var c={type:"Feature"},d=a.c;void 0!==d&&(c.id=d);(d=a.U())?c.geometry=Tq(d,b):c.geometry=null;b=a.L();delete b[a.a];nb(b)?c.properties=null:c.properties=b;return c};k.qe=function(a,b){b=Io(this,b);var c=[],d;var e=0;for(d=a.length;e<d;++e)c.push(this.ld(a[e],b));return{type:"FeatureCollection",features:c}};k.se=function(a,b){return Tq(a,Io(this,b))};function Vq(){Go.call(this)}w(Vq,Go);function Wq(a){return"string"===typeof a?a:""}k=Vq.prototype;k.S=function(){return"text"};k.Yb=function(a,b){return this.fe(Wq(a),Io(this,b))};k.Qa=function(a,b){return this.Ng(Wq(a),Io(this,b))};k.ed=function(a,b){return this.Gd(Wq(a),Io(this,b))};k.sb=function(){return this.defaultDataProjection};k.Jd=function(a,b){return this.pe(a,Io(this,b))};k.ac=function(a,b){return this.nh(a,Io(this,b))};k.md=function(a,b){return this.Kd(a,Io(this,b))};function Xq(a){a=a?a:{};Go.call(this);this.defaultDataProjection=Ob("EPSG:4326");this.b=a.altitudeMode?a.altitudeMode:"none"}w(Xq,Vq);var Yq=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,Zq=/^H.([A-Z]{3}).*?:(.*)/,$q=/^HFDTE(\d{2})(\d{2})(\d{2})/,ar=/\r\n|\r|\n/;k=Xq.prototype;
+k.fe=function(a,b){var c=this.b,d=a.split(ar);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=Yq.exec(p)){p=parseInt(q[1],10);var r=parseInt(q[2],10),u=parseInt(q[3],10),v=parseInt(q[4],10)+parseInt(q[5],10)/6E4;"S"==q[6]&&(v=-v);var z=parseInt(q[7],10)+parseInt(q[8],10)/6E4;"W"==q[9]&&(z=-z);e.push(z,v);"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=$q.exec(p))?(h=parseInt(q[1],10),g=parseInt(q[2],10)-1,f=2E3+parseInt(q[3],10)):(q=Zq.exec(p))&&(a[q[1]]=q[2].trim()))}if(0===e.length)return null;d=new I(null);d.ba("none"==c?"XYM":"XYZM",e);b=new Hk(Jo(d,!1,b));b.H(a);return b};k.Ng=function(a,b){return(a=this.fe(a,b))?[a]:[]};k.pe=function(){};k.nh=function(){};k.Kd=function(){};k.Gd=function(){};function br(a,b,c,d,e,f){Sc.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.f=f;this.i=null;this.g=e;this.a=c;this.l=b;this.s=!1;2==this.g&&cr(this)}w(br,Sc);function cr(a){var b=hg(1,1);try{b.drawImage(a.M,0,0),b.getImageData(0,0,1,1)}catch(c){a.s=!0}}br.prototype.v=function(){this.g=3;this.i.forEach(Gc);this.i=null;this.b("change")};
+br.prototype.o=function(){this.g=2;this.a&&(this.M.width=this.a[0],this.M.height=this.a[1]);this.a=[this.M.width,this.M.height];this.i.forEach(Gc);this.i=null;cr(this);if(!this.s&&null!==this.f){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.f[0]/255,e=this.f[1]/255,f=this.f[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")};
+br.prototype.Y=function(){return this.c?this.c:this.M};br.prototype.load=function(){if(0==this.g){this.g=1;this.i=[Lc(this.M,"error",this.v,this),Lc(this.M,"load",this.o,this)];try{this.M.src=this.l}catch(a){this.v()}}};function dr(a){a=a||{};this.l=void 0!==a.anchor?a.anchor:[.5,.5];this.o=null;this.g=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.qa=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;oa(!(void 0!==d&&b),4);oa(!b||b&&c,5);void 0!==d&&0!==d.length||!b||(d=b.src||x(b).toString());oa(void 0!==d&&0<d.length,6);var e=
+void 0!==a.src?0:2;this.j=void 0!==a.color?vi(a.color):null;var f=this.qa,g=this.j,h=ej.get(d,f,g);h||(h=new br(b,d,c,f,e,g),ej.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.N=null;this.D=void 0!==a.size?a.size:null;vk.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(dr,vk);k=dr.prototype;k.clone=function(){return new dr({anchor:this.l.slice(),anchorOrigin:this.g,anchorXUnits:this.C,anchorYUnits:this.B,crossOrigin:this.qa,color:this.j&&this.j.slice?this.j.slice():this.j||void 0,src:this.b.l,offset:this.oa.slice(),offsetOrigin:this.c,size:null!==this.D?this.D.slice():void 0,opacity:this.i,scale:this.a,snapToPixel:this.v,rotation:this.f,rotateWithView:this.s})};
+k.Vc=function(){if(this.o)return this.o;var a=this.l,b=this.oc();if("fraction"==this.C||"fraction"==this.B){if(!b)return null;a=this.l.slice();"fraction"==this.C&&(a[0]*=b[0]);"fraction"==this.B&&(a[1]*=b[1])}if("top-left"!=this.g){if(!b)return null;a===this.l&&(a=this.l.slice());if("top-right"==this.g||"bottom-right"==this.g)a[0]=-a[0]+b[0];if("bottom-left"==this.g||"bottom-right"==this.g)a[1]=-a[1]+b[1]}return this.o=a};k.np=function(){return this.j};k.Y=function(a){return this.b.Y(a)};k.He=function(){return this.b.a};
+k.gf=function(){return this.b.g};k.Eg=function(){var a=this.b;if(!a.j)if(a.s){var b=a.a[0],c=a.a[1],d=hg(b,c);d.fillRect(0,0,b,c);a.j=d.canvas}else a.j=a.M;return a.j};k.bd=function(){if(this.N)return this.N;var a=this.oa;if("top-left"!=this.c){var b=this.oc(),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.N=a};k.op=function(){return this.b.l};
+k.oc=function(){return this.D?this.D:this.b.a};k.gi=function(a,b){y(this.b,"change",a,b)};k.load=function(){this.b.load()};k.Yj=function(a,b){Mc(this.b,"change",a,b)};function er(a){a=a?a:{};Wo.call(this);fr||(gr=[255,255,255,1],hr=new zk({color:gr}),ir=[20,2],jr=kr="pixels",lr=[64,64],mr="https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png",nr=.5,or=new dr({anchor:ir,anchorOrigin:"bottom-left",anchorXUnits:kr,anchorYUnits:jr,crossOrigin:"anonymous",rotation:0,scale:nr,size:lr,src:mr}),pr="NO_IMAGE",qr=new Ak({color:gr,width:1}),rr=new Ak({color:[51,51,51,1],width:2}),sr=new J({font:"bold 16px Helvetica",fill:hr,stroke:rr,scale:.8}),tr=new Bk({fill:hr,
+image:or,text:sr,stroke:qr,zIndex:0}),fr=[tr]);this.defaultDataProjection=Ob("EPSG:4326");this.a=a.defaultStyle?a.defaultStyle:fr;this.c=void 0!==a.extractStyles?a.extractStyles:!0;this.j=void 0!==a.writeStyles?a.writeStyles:!0;this.b={};this.f=void 0!==a.showPointNames?a.showPointNames:!0}var fr,gr,hr,ir,kr,jr,lr,mr,nr,or,pr,qr,rr,sr,tr;w(er,Wo);
+var ur=["http://www.google.com/kml/ext/2.2"],vr=[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"],wr={fraction:"fraction",pixels:"pixels",insetPixels:"pixels"};
+function xr(a,b){var c=[0,0],d="start";if(a.Y()){var e=a.Y().He();null===e&&(e=lr);2==e.length&&(d=a.Y().a,c[0]=d*e[0]/2,c[1]=-d*e[1]/2,d="left")}null!==a.Ka()?(e=a.Ka(),a=e.clone(),a.Jj(e.a||sr.a),a.lj(e.b||sr.b),a.yf(e.Fa()||sr.Fa()),a.Af(e.Ga()||rr)):a=sr.clone();a.Hd(b);a.Nj(c[0]);a.Oj(c[1]);a.Qj(d);return new Bk({text:a})}
+function yr(a,b,c,d,e){return function(){var f=e,g="";f&&this.U()&&(f="Point"===this.U().S());f&&(g=this.get("name"),f=f&&g);if(a)return f?(f=xr(a[0],g),a.concat(f)):a;if(b){var h=zr(b,c,d);return f?(f=xr(h[0],g),h.concat(f)):h}return f?(f=xr(c[0],g),c.concat(f)):c}}function zr(a,b,c){return Array.isArray(a)?a:"string"===typeof a?(!(a in c)&&"#"+a in c&&(a="#"+a),zr(c[a],b,c)):b}
+function Ar(a){a=oo(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 Br(a){a=oo(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 Cr(a){var b=oo(a,!1).trim();a=a.baseURI;a&&"about:blank"!=a||(a=window.location.href);return a?(new URL(b,a)).href:b}function Dr(a){return dp(a)}function Er(a,b){return O(null,Fr,a,b)}function Gr(a,b){if(b=O({A:[],ak:[]},Hr,a,b)){a=b.A;b=b.ak;var c;var d=0;for(c=Math.min(a.length,b.length);d<c;++d)a[4*d+3]=b[d];b=new I(null);b.ba("XYZM",a);return b}}function Ir(a,b){var c=O({},Jr,a,b);if(a=O(null,Kr,a,b))return b=new I(null),b.ba("XYZ",a),b.H(c),b}
+function Lr(a,b){var c=O({},Jr,a,b);if(a=O(null,Kr,a,b))return b=new D(null),b.ba("XYZ",a,[a.length]),b.H(c),b}
+function Mr(a,b){a=O([],Nr,a,b);if(!a)return null;if(0===a.length)return new Mq(a);var c=!0,d=a[0].S(),e;var f=1;for(e=a.length;f<e;++f)if(b=a[f],b.S()!=d){c=!1;break}if(c)if("Point"==d){var g=a[0];c=g.ja;d=g.da();f=1;for(e=a.length;f<e;++f)b=a[f],gc(d,b.da());g=new No(null);g.ba(c,d);Or(g,a)}else"LineString"==d?(g=new P(null),Mo(g,a),Or(g,a)):"Polygon"==d?(g=new Q(null),Oo(g,a),Or(g,a)):"GeometryCollection"==d?g=new Mq(a):oa(!1,37);else g=new Mq(a);return g}
+function Pr(a,b){var c=O({},Jr,a,b);if(a=O(null,Kr,a,b))return b=new C(null),b.ba("XYZ",a),b.H(c),b}function Qr(a,b){var c=O({},Jr,a,b);if((a=O([null],Rr,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)gc(d,a[g]),e.push(d.length);b.ba("XYZ",d,e);b.H(c);return b}}
+function Sr(a,b){b=O({},Tr,a,b);if(!b)return null;a="fillStyle"in b?b.fillStyle:hr;var c=b.fill;void 0===c||c||(a=null);c="imageStyle"in b?b.imageStyle:or;c==pr&&(c=void 0);var d="textStyle"in b?b.textStyle:sr,e="strokeStyle"in b?b.strokeStyle:qr;b=b.outline;void 0===b||b||(e=null);return[new Bk({fill:a,image:c,stroke:e,text:d,zIndex:void 0})]}
+function Or(a,b){var c=b.length,d=Array(b.length),e=Array(b.length),f=Array(b.length),g,h,l;var m=h=l=!1;for(g=0;g<c;++g){var n=b[g];d[g]=n.get("extrude");e[g]=n.get("tessellate");f[g]=n.get("altitudeMode");m=m||void 0!==d[g];h=h||void 0!==e[g];l=l||f[g]}m&&a.set("extrude",d);h&&a.set("tessellate",e);l&&a.set("altitudeMode",f)}function Ur(a,b){Co(Vr,a,b)}function Wr(a,b){Co(Xr,a,b)}
+var Yr=N(vr,{displayName:L(R),value:L(R)}),Vr=N(vr,{Data:function(a,b){var c=a.getAttribute("name");Co(Yr,a,b);a=b[b.length-1];null!==c?a[c]=a.value:null!==a.displayName&&(a[a.displayName]=a.value);delete a.value},SchemaData:function(a,b){Co(Zr,a,b)}}),Xr=N(vr,{LatLonAltBox:function(a,b){if(a=O({},$r,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=O({},as,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)}}),$r=N(vr,{altitudeMode:L(R),minAltitude:L(dp),maxAltitude:L(dp),north:L(dp),south:L(dp),east:L(dp),west:L(dp)}),as=N(vr,{minLodPixels:L(dp),maxLodPixels:L(dp),minFadeExtent:L(dp),maxFadeExtent:L(dp)}),Jr=N(vr,{extrude:L(ap),tessellate:L(ap),altitudeMode:L(R)}),Fr=N(vr,{coordinates:vo(Br)}),
+Rr=N(vr,{innerBoundaryIs:function(a,b){(a=O(void 0,bs,a,b))&&b[b.length-1].push(a)},outerBoundaryIs:function(a,b){(a=O(void 0,cs,a,b))&&(b[b.length-1][0]=a)}}),Hr=N(vr,{when:function(a,b){b=b[b.length-1].ak;a=oo(a,!1);a=Date.parse(a);b.push(isNaN(a)?0:a)}},N(ur,{coord:function(a,b){b=b[b.length-1].A;a=oo(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)}})),Kr=N(vr,{coordinates:vo(Br)}),ds=N(vr,{href:L(Cr)},N(ur,{x:L(dp),y:L(dp),w:L(dp),h:L(dp)})),es=N(vr,{Icon:L(function(a,b){return(a=O({},ds,a,b))?a:null}),heading:L(dp),hotSpot:L(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")),oh:wr[b],y:parseFloat(a.getAttribute("y")),ph:wr[c],origin:d}}),scale:L(Dr)}),
+bs=N(vr,{LinearRing:vo(Er)}),fs=N(vr,{color:L(Ar),scale:L(Dr)}),gs=N(vr,{color:L(Ar),width:L(dp)}),Nr=N(vr,{LineString:uo(Ir),LinearRing:uo(Lr),MultiGeometry:uo(Mr),Point:uo(Pr),Polygon:uo(Qr)}),hs=N(ur,{Track:uo(Gr)}),js=N(vr,{ExtendedData:Ur,Region:Wr,Link:function(a,b){Co(is,a,b)},address:L(R),description:L(R),name:L(R),open:L(ap),phoneNumber:L(R),visibility:L(ap)}),is=N(vr,{href:L(Cr)}),cs=N(vr,{LinearRing:vo(Er)}),ks=N(vr,{Style:L(Sr),key:L(R),styleUrl:L(Cr)}),ms=N(vr,{ExtendedData:Ur,Region:Wr,
+MultiGeometry:L(Mr,"geometry"),LineString:L(Ir,"geometry"),LinearRing:L(Lr,"geometry"),Point:L(Pr,"geometry"),Polygon:L(Qr,"geometry"),Style:L(Sr),StyleMap:function(a,b){if(a=O(void 0,ls,a,b))b=b[b.length-1],Array.isArray(a)?b.Style=a:"string"===typeof a?b.styleUrl=a:oa(!1,38)},address:L(R),description:L(R),name:L(R),open:L(ap),phoneNumber:L(R),styleUrl:L(Cr),visibility:L(ap)},N(ur,{MultiTrack:L(function(a,b){if(a=O([],hs,a,b))return b=new P(null),Mo(b,a),b},"geometry"),Track:L(Gr,"geometry")})),
+ns=N(vr,{color:L(Ar),fill:L(ap),outline:L(ap)}),Zr=N(vr,{SimpleData:function(a,b){var c=a.getAttribute("name");null!==c&&(a=R(a),b[b.length-1][c]=a)}}),Tr=N(vr,{IconStyle:function(a,b){if(a=O({},es,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=mr);f="bottom-left";var g=a.hotSpot;if(g){var h=[g.x,g.y];var l=g.oh;var m=g.ph;f=g.origin}else e===mr?(h=ir,l=kr,m=jr):/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(e)&&(h=[.5,0],m=l="fraction");
+var n;g=c.x;var 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=va(c));a=a.scale;d?(e==mr&&(q=lr,void 0===a&&(a=nr)),e=new dr({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=pr}},LabelStyle:function(a,b){(a=O({},fs,a,b))&&(b[b.length-1].textStyle=new J({fill:new zk({color:"color"in a?a.color:gr}),
+scale:a.scale}))},LineStyle:function(a,b){(a=O({},gs,a,b))&&(b[b.length-1].strokeStyle=new Ak({color:"color"in a?a.color:gr,width:"width"in a?a.width:1}))},PolyStyle:function(a,b){if(a=O({},ns,a,b)){b=b[b.length-1];b.fillStyle=new zk({color:"color"in a?a.color:gr});var c=a.fill;void 0!==c&&(b.fill=c);a=a.outline;void 0!==a&&(b.outline=a)}}}),ls=N(vr,{Pair:function(a,b){if(a=O({},ks,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=er.prototype;
+k.Jg=function(a,b){var c=N(vr,{Document:to(this.Jg,this),Folder:to(this.Jg,this),Placemark:uo(this.Rg,this),Style:this.kq.bind(this),StyleMap:this.jq.bind(this)});if(a=O([],c,a,b,this))return a};k.Rg=function(a,b){var c=O({geometry:null},ms,a,b);if(c){var d=new Hk;a=a.getAttribute("id");null!==a&&d.qc(a);b=b[0];(a=c.geometry)&&Jo(a,!1,b);d.Va(a);delete c.geometry;this.c&&d.sg(yr(c.Style,c.styleUrl,this.a,this.b,this.f));delete c.Style;d.H(c);return d}};
+k.kq=function(a,b){var c=a.getAttribute("id");null!==c&&(b=Sr(a,b))&&(a=a.baseURI,a&&"about:blank"!=a||(a=window.location.href),c=a?(new URL("#"+c,a)).href:"#"+c,this.b[c]=b)};k.jq=function(a,b){var c=a.getAttribute("id");null!==c&&(b=O(void 0,ls,a,b))&&(a=a.baseURI,a&&"about:blank"!=a||(a=window.location.href),c=a?(new URL("#"+c,a)).href:"#"+c,this.b[c]=b)};k.Lg=function(a,b){return ec(vr,a.namespaceURI)?(a=this.Rg(a,[Ho(this,a,b)]))?a:null:null};
+k.Kc=function(a,b){if(!ec(vr,a.namespaceURI))return[];var c=a.localName;if("Document"==c||"Folder"==c)return(c=this.Jg(a,[Ho(this,a,b)]))?c:[];if("Placemark"==c)return(b=this.Rg(a,[Ho(this,a,b)]))?[b]:[];if("kml"==c){c=[];for(a=a.firstElementChild;a;a=a.nextElementSibling){var d=this.Kc(a,b);d&&gc(c,d)}return c}return[]};k.cq=function(a){if(qo(a))return os(this,a);if(ro(a))return ps(this,a);if("string"===typeof a)return a=so(a),os(this,a)};
+function os(a,b){for(b=b.firstChild;b;b=b.nextSibling)if(b.nodeType==Node.ELEMENT_NODE){var c=ps(a,b);if(c)return c}}function ps(a,b){var c;for(c=b.firstElementChild;c;c=c.nextElementSibling)if(ec(vr,c.namespaceURI)&&"name"==c.localName)return R(c);for(c=b.firstElementChild;c;c=c.nextElementSibling)if(b=c.localName,ec(vr,c.namespaceURI)&&("Document"==b||"Folder"==b||"Placemark"==b||"kml"==b)&&(b=ps(a,c)))return b}
+k.eq=function(a){var b=[];qo(a)?gc(b,qs(this,a)):ro(a)?gc(b,rs(this,a)):"string"===typeof a&&(a=so(a),gc(b,qs(this,a)));return b};function qs(a,b){var c=[];for(b=b.firstChild;b;b=b.nextSibling)b.nodeType==Node.ELEMENT_NODE&&gc(c,rs(a,b));return c}
+function rs(a,b){var c,d=[];for(c=b.firstElementChild;c;c=c.nextElementSibling)if(ec(vr,c.namespaceURI)&&"NetworkLink"==c.localName){var e=O({},js,c,[]);d.push(e)}for(c=b.firstElementChild;c;c=c.nextElementSibling)b=c.localName,!ec(vr,c.namespaceURI)||"Document"!=b&&"Folder"!=b&&"kml"!=b||gc(d,rs(a,c));return d}k.hq=function(a){var b=[];qo(a)?gc(b,ss(this,a)):ro(a)?gc(b,this.vf(a)):"string"===typeof a&&(a=so(a),gc(b,ss(this,a)));return b};
+function ss(a,b){var c=[];for(b=b.firstChild;b;b=b.nextSibling)b.nodeType==Node.ELEMENT_NODE&&gc(c,a.vf(b));return c}k.vf=function(a){var b,c=[];for(b=a.firstElementChild;b;b=b.nextElementSibling)if(ec(vr,b.namespaceURI)&&"Region"==b.localName){var d=O({},Xr,b,[]);c.push(d)}for(b=a.firstElementChild;b;b=b.nextElementSibling)a=b.localName,!ec(vr,b.namespaceURI)||"Document"!=a&&"Folder"!=a&&"kml"!=a||gc(c,this.vf(b));return c};
+function ts(a,b){b=vi(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}ip(a,b.join(""))}function us(a,b,c){a={node:a};var d=b.S();if("GeometryCollection"==d){var e=b.vd();var f=vs}else"MultiPoint"==d?(e=b.de(),f=ws):"MultiLineString"==d?(e=b.wd(),f=xs):"MultiPolygon"==d?(e=b.Vd(),f=ys):oa(!1,39);Do(a,zs,f,e,c)}function As(a,b,c){Do({node:a},Bs,Cs,[b],c)}
+function Ds(a,b,c){var d={node:a};b.c&&a.setAttribute("id",b.c);a=b.L();var e={address:1,description:1,name:1,open:1,phoneNumber:1,styleUrl:1,visibility:1};e[b.a]=1;var f=Object.keys(a||{}).sort().filter(function(a){return!e[a]});if(0<f.length){var g=Bo(a,f);Do(d,Es,Fs,[{names:f,values:g}],c)}if(f=b.ib())if(f=f.call(b,0))f=Array.isArray(f)?f[0]:f,this.j&&(a.Style=f),(f=f.Ka())&&(a.name=f.Ka());f=Gs[c[c.length-1].node.namespaceURI];a=Bo(a,f);Do(d,Es,Ao,a,c,f);a=c[0];(b=b.U())&&(b=Jo(b,!0,a));Do(d,
+Es,vs,[b],c)}function Hs(a,b,c){var d=b.da();a={node:a};a.layout=b.ja;a.stride=b.pa();b=b.L();b.coordinates=d;d=Is[c[c.length-1].node.namespaceURI];b=Bo(b,d);Do(a,Js,Ao,b,c,d)}function Ks(a,b,c){b=b.Ud();var d=b.shift();a={node:a};Do(a,Ls,Ms,b,c);Do(a,Ls,Ns,[d],c)}function Os(a,b){Ip(a,Math.round(1E6*b)/1E6)}
+var Ps=N(vr,["Document","Placemark"]),Ss=N(vr,{Document:M(function(a,b,c){Do({node:a},Qs,Rs,b,c,void 0,this)}),Placemark:M(Ds)}),Qs=N(vr,{Placemark:M(Ds)}),Ts=N(vr,{Data:M(function(a,b,c){a.setAttribute("name",b.name);a={node:a};b=b.value;"object"==typeof b?(null!==b&&b.displayName&&Do(a,Ts,Ao,[b.displayName],c,["displayName"]),null!==b&&b.value&&Do(a,Ts,Ao,[b.value],c,["value"])):Do(a,Ts,Ao,[b],c,["value"])}),value:M(function(a,b){ip(a,b)}),displayName:M(function(a,b){a.appendChild(mo.createCDATASection(b))})}),
+Us={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry",GeometryCollection:"MultiGeometry"},Vs=N(vr,["href"],N(ur,["x","y","w","h"])),Ws=N(vr,{href:M(ip)},N(ur,{x:M(Ip),y:M(Ip),w:M(Ip),h:M(Ip)})),Xs=N(vr,["scale","heading","Icon","hotSpot"]),Zs=N(vr,{Icon:M(function(a,b,c){a={node:a};var d=Vs[c[c.length-1].node.namespaceURI],e=Bo(b,d);Do(a,Ws,Ao,e,c,d);d=Vs[ur[0]];e=Bo(b,d);Do(a,Ws,
+Ys,e,c,d)}),heading:M(Ip),hotSpot:M(function(a,b){a.setAttribute("x",b.x);a.setAttribute("y",b.y);a.setAttribute("xunits",b.oh);a.setAttribute("yunits",b.ph)}),scale:M(Os)}),$s=N(vr,["color","scale"]),at=N(vr,{color:M(ts),scale:M(Os)}),bt=N(vr,["color","width"]),ct=N(vr,{color:M(ts),width:M(Ip)}),Bs=N(vr,{LinearRing:M(Hs)}),zs=N(vr,{LineString:M(Hs),Point:M(Hs),Polygon:M(Ks),GeometryCollection:M(us)}),Gs=N(vr,"name open visibility address phoneNumber description styleUrl Style".split(" ")),Es=N(vr,
+{ExtendedData:M(function(a,b,c){a={node:a};var d=b.names;b=b.values;for(var e=d.length,f=0;f<e;f++)Do(a,Ts,dt,[{name:d[f],value:b[f]}],c)}),MultiGeometry:M(us),LineString:M(Hs),LinearRing:M(Hs),Point:M(Hs),Polygon:M(Ks),Style:M(function(a,b,c){a={node:a};var d={},e=b.Fa(),f=b.Ga(),g=b.Y();b=b.Ka();g instanceof dr&&(d.IconStyle=g);b&&(d.LabelStyle=b);f&&(d.LineStyle=f);e&&(d.PolyStyle=e);b=et[c[c.length-1].node.namespaceURI];d=Bo(d,b);Do(a,ft,Ao,d,c,b)}),address:M(ip),description:M(ip),name:M(ip),
+open:M(hp),phoneNumber:M(ip),styleUrl:M(ip),visibility:M(hp)}),Is=N(vr,["extrude","tessellate","altitudeMode","coordinates"]),Js=N(vr,{extrude:M(hp),tessellate:M(hp),altitudeMode:M(ip),coordinates:M(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:oa(!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]}ip(a,h)})}),Ls=N(vr,{outerBoundaryIs:M(As),innerBoundaryIs:M(As)}),
+gt=N(vr,{color:M(ts)}),et=N(vr,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),ft=N(vr,{IconStyle:M(function(a,b,c){a={node:a};var d={},e=b.oc(),f=b.He(),g={href:b.b.l};if(e){g.w=e[0];g.h=e[1];var h=b.Vc(),l=b.bd();l&&f&&0!==l[0]&&l[1]!==e[1]&&(g.x=l[0],g.y=f[1]-(l[1]+e[1]));!h||h[0]===e[0]/2&&h[1]===e[1]/2||(d.hotSpot={x:h[0],oh:"pixels",y:e[1]-h[1],ph:"pixels"})}d.Icon=g;e=b.a;1!==e&&(d.scale=e);b=b.f;0!==b&&(d.heading=b);b=Xs[c[c.length-1].node.namespaceURI];d=Bo(d,b);Do(a,Zs,Ao,d,c,b)}),LabelStyle:M(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=$s[c[c.length-1].node.namespaceURI];d=Bo(d,b);Do(a,at,Ao,d,c,b)}),LineStyle:M(function(a,b,c){a={node:a};var d=bt[c[c.length-1].node.namespaceURI];b=Bo({color:b.a,width:b.c},d);Do(a,ct,Ao,b,c,d)}),PolyStyle:M(function(a,b,c){Do({node:a},gt,ht,[b.b],c)})});function Ys(a,b,c){return no(ur[0],"gx:"+c)}function Rs(a,b){return no(b[b.length-1].node.namespaceURI,"Placemark")}
+function vs(a,b){if(a)return no(b[b.length-1].node.namespaceURI,Us[a.S()])}var ht=yo("color"),dt=yo("Data"),Fs=yo("ExtendedData"),Ms=yo("innerBoundaryIs"),ws=yo("Point"),xs=yo("LineString"),Cs=yo("LinearRing"),ys=yo("Polygon"),Ns=yo("outerBoundaryIs");
+er.prototype.bc=function(a,b){b=Io(this,b);var c=no(vr[4],"kml");c.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:gx",ur[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=Ps[c.namespaceURI];
+e=Bo(e,a);Do(d,Ss,Ao,e,[b],a,this);return c};rj.Ld=function(){};
+(function(a){function b(a){this.tc=ArrayBuffer.isView&&ArrayBuffer.isView(a)?a:new Uint8Array(a||0);this.type=this.ga=0;this.length=this.tc.length}function c(a,b,c){var e=c.tc;var f=e[c.ga++];var g=(f&112)>>4;if(128>f)return d(a,g,b);f=e[c.ga++];g|=(f&127)<<3;if(128>f)return d(a,g,b);f=e[c.ga++];g|=(f&127)<<10;if(128>f)return d(a,g,b);f=e[c.ga++];g|=(f&127)<<17;if(128>f)return d(a,g,b);f=e[c.ga++];g|=(f&127)<<24;if(128>f)return d(a,g,b);f=e[c.ga++];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,v=a[b+e];e+=m;c=v&(1<<-l)-1;v>>=-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*(v?-1:1);f+=Math.pow(2,d);c-=h}return(v?-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 z=d?1:-1,A=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+=z,b/=256,e-=8);d=d<<e|b;for(g+=e;0<g;a[c+n]=d&255,n+=z,d/=256,g-=8);a[c+n-z]|=128*A}};b.c=0;b.g=1;b.b=2;b.a=5;b.prototype={Og:function(a,
+b,c){for(c=c||this.length;this.ga<c;){var d=this.Ua(),e=d>>3,f=this.ga;this.type=d&7;a(e,b,this);this.ga===f&&this.Lq(d)}return b},Zp:function(){var a=e.read(this.tc,this.ga,!0,23,4);this.ga+=4;return a},Vp:function(){var a=e.read(this.tc,this.ga,!0,52,8);this.ga+=8;return a},Ua:function(a){var b=this.tc;var d=b[this.ga++];var e=d&127;if(128>d)return e;d=b[this.ga++];e|=(d&127)<<7;if(128>d)return e;d=b[this.ga++];e|=(d&127)<<14;if(128>d)return e;d=b[this.ga++];e|=(d&127)<<21;if(128>d)return e;d=b[this.ga];
+return c(e|(d&15)<<28,a,this)},lq:function(){return this.Ua(!0)},Ug:function(){var a=this.Ua();return 1===a%2?(a+1)/-2:a/2},Tp:function(){return!!this.Ua()},Vg:function(){for(var a=this.Ua()+this.ga,b=this.tc,c="",d=this.ga;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.ga=a;return c},Lq:function(a){a&=7;if(a===b.c)for(;127<this.tc[this.ga++];);else if(a===b.b)this.ga=this.Ua()+this.ga;else if(a===b.a)this.ga+=4;else if(a===b.g)this.ga+=8;else throw Error("Unimplemented type: "+
+a);}};a["default"]=b})(rj.Ld=rj.Ld||{});rj.Ld=rj.Ld.default;function it(a,b,c,d,e){this.l=e;this.f=a;this.b=b;this.a=this.c=null;this.g=c;this.j=d;this.s=We()}k=it.prototype;k.get=function(a){return this.j[a]};k.pb=it.prototype.td=function(){return this.g};k.G=function(){this.i||(this.i="Point"===this.f?Pa(this.b):Qa(this.b,0,this.b.length,2));return this.i};k.Td=function(){if(!this.c){var a=eb(this.G());this.c=If(this.b,0,this.g,2,a,0)}return this.c};k.Fe=function(){this.a||(this.a=Kk(this.b,0,this.b.length,2,.5));return this.a};
+k.Ge=function(){if(!this.a){this.a=[];for(var a=this.b,b=0,c=this.g,d=0,e=c.length;d<e;++d){var f=c[d];b=Kk(a,b,f,2,.5);gc(this.a,b);b=f}}return this.a};k.Ao=function(){return this.l};k.Xb=function(){return this.b};k.da=it.prototype.Xb;k.U=function(){return this};k.Bo=function(){return this.j};k.Wd=it.prototype.U;k.pa=function(){return 2};k.ib=ea;k.S=function(){return this.f};k.mb=function(a){var b=a.G();a=a.oe;b=db(a)/db(b);var c=this.s;ef(c,a[0],a[3],b,-b,0,0,0);Te(this.b,0,this.b.length,2,c,this.b)};function jt(a){Go.call(this);a=a?a:{};this.defaultDataProjection=new wb({code:"EPSG:3857",units:"tile-pixels"});this.b=a.featureClass?a.featureClass:it;this.g=a.geometryName;this.f=a.layerName?a.layerName:"layer";this.c=a.layers?a.layers:null;this.a=null}w(jt,Go);function kt(a,b,c){if(3===a){a={keys:[],values:[],features:[]};var d=c.Ua()+c.ga;c.Og(lt,a,d);a.length=a.features.length;a.length&&(b[a.name]=a)}}
+function lt(a,b,c){if(15===a)b.version=c.Ua();else if(1===a)b.name=c.Vg();else if(5===a)b.extent=c.Ua();else if(2===a)b.features.push(c.ga);else if(3===a)b.keys.push(c.Vg());else if(4===a){a=null;for(var d=c.Ua()+c.ga;c.ga<d;)a=c.Ua()>>3,a=1===a?c.Vg():2===a?c.Zp():3===a?c.Vp():4===a?c.lq():5===a?c.Ua():6===a?c.Ug():7===a?c.Tp():null;b.values.push(a)}}
+function mt(a,b,c){if(1==a)b.id=c.Ua();else if(2==a)for(a=c.Ua()+c.ga;c.ga<a;){var d=b.layer.keys[c.Ua()],e=b.layer.values[c.Ua()];b.properties[d]=e}else 3==a?b.type=c.Ua():4==a&&(b.geometry=c.ga)}
+function nt(a,b,c){var d=c.type;if(0===d)return null;var e=c.id,f=c.properties;f[a.f]=c.layer.name;var g=[];var h=[],l=h;b.ga=c.geometry;c=b.Ua()+b.ga;for(var m=1,n=0,p=0,q=0,r=0,u=0;b.ga<c;)n||(n=b.Ua(),m=n&7,n>>=3),n--,1===m||2===m?(p+=b.Ug(),q+=b.Ug(),1===m&&r>u&&(l.push(r),u=r),g.push(p,q),r+=2):7===m?r>u&&(g.push(g[u],g[u+1]),r+=2):oa(!1,59);r>u&&l.push(r);b=h.length;var v;1===d?v=1===b?"Point":"MultiPoint":2===d?v=1===b?"LineString":"MultiLineString":3===d&&(v="Polygon");d=v;if(a.b===it)g=new a.b(d,
+g,h,f,e);else{if("Polygon"==d){d=[];l=b=v=0;for(c=h.length;l<c;++l)m=h[l],Mf(g,v,m,2)||(d.push(h.slice(b,l)),b=l),v=m;1<d.length?(h=d,d=new Q(null)):d=new D(null)}else d="Point"===d?new C(null):"LineString"===d?new I(null):"Polygon"===d?new D(null):"MultiPoint"===d?new No(null):"MultiLineString"===d?new P(null):null;d.ba("XY",g,h);g=new a.b;a.g&&g.Lc(a.g);a=Jo(d,!1,Io(a,void 0));g.Va(a);g.qc(e);g.H(f)}return g}k=jt.prototype;k.cg=function(){return this.a};k.S=function(){return"arraybuffer"};
+k.Qa=function(a){var b=this.c;a=new rj.Ld(a);var c=a.Og(kt,{}),d=[],e;for(e in c)if(!b||-1!=b.indexOf(e)){var f=c[e];for(var g,h=0,l=f.length;h<l;++h){g=a;var m=f;g.ga=m.features[h];var n=g.Ua()+g.ga;m={layer:m,type:0,properties:{}};g.Og(mt,m,n);g=m;d.push(nt(this,a,g))}this.a=f?[0,0,f.extent,f.extent]:null}return d};k.sb=function(){return this.defaultDataProjection};k.Sn=function(a){this.c=a};k.Yb=function(){};k.ed=function(){};k.Jd=function(){};k.md=function(){};k.ac=function(){};function ot(){Wo.call(this);this.defaultDataProjection=Ob("EPSG:4326")}w(ot,Wo);function pt(a,b){b[b.length-1].le[a.getAttribute("k")]=a.getAttribute("v")}
+var qt=[null],rt=N(qt,{nd:function(a,b){b[b.length-1].zd.push(a.getAttribute("ref"))},tag:pt}),tt=N(qt,{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.ki[e]=f;a=O({le:{}},st,a,b);nb(a.le)||(f=new C(f),Jo(f,!1,c),c=new Hk(f),c.qc(e),c.H(a.le),d.features.push(c))},way:function(a,b){var c=a.getAttribute("id");a=O({id:c,zd:[],le:{}},rt,a,b);b[b.length-1].lh.push(a)}}),st=N(qt,{tag:pt});
+ot.prototype.Kc=function(a,b){b=Ho(this,a,b);if("osm"==a.localName){a=O({ki:{},lh:[],features:[]},tt,a,[b]);for(var c=0;c<a.lh.length;c++){for(var d=a.lh[c],e=[],f=0,g=d.zd.length;f<g;f++)gc(e,a.ki[d.zd[f]]);d.zd[0]==d.zd[d.zd.length-1]?(f=new D(null),f.ba("XY",e,[e.length])):(f=new I(null),f.ba("XY",e));Jo(f,!1,b);e=new Hk(f);e.qc(d.id);e.H(d.le);a.features.push(e)}if(a.features)return a.features}return[]};ot.prototype.mh=function(){};ot.prototype.bc=function(){};ot.prototype.re=function(){};function ut(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 vt(a){a=a?a:{};Go.call(this);this.defaultDataProjection=Ob("EPSG:4326");this.b=a.factor?a.factor:1E5;this.a=a.geometryLayout?a.geometryLayout:"XY"}w(vt,Vq);function wt(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 xt(a,c?c:1E5)}function yt(a,b,c){var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;a=zt(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 xt(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 zt(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|=(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=vt.prototype;k.fe=function(a,b){a=this.Gd(a,b);return new Hk(a)};k.Ng=function(a,b){return[this.fe(a,b)]};k.Gd=function(a,b){var c=jf(this.a);a=yt(a,c,this.b);ut(a,a.length,c,a);c=yf(a,0,a.length,c);return Jo(new I(c,this.a),!1,Io(this,b))};
+k.pe=function(a,b){if(a=a.U())return this.Kd(a,b);oa(!1,40);return""};k.nh=function(a,b){return this.pe(a[0],b)};k.Kd=function(a,b){a=Jo(a,!0,Io(this,b));b=a.da();a=a.pa();ut(b,b.length,a,b);return wt(b,a,this.b)};function At(a){a=a?a:{};Go.call(this);this.a=a.layerName;this.b=a.layers?a.layers:null;this.defaultDataProjection=Ob(a.defaultDataProjection?a.defaultDataProjection:"EPSG:4326")}w(At,Ko);function Bt(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 Ct(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]=Dt(a[m],b,c,d,e,f,g);return h}function Dt(a,b,c,d,e,f,g){var h=a.type,l=Et[h];c="Point"===h||"MultiPoint"===h?l(a,c,d):l(a,b);b=new Hk;b.Va(Jo(c,!1,g));void 0!==a.id&&b.qc(a.id);a=a.properties;e&&(a||(a={}),a[e]=f);a&&b.H(a);return b}
+At.prototype.Mg=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 v=0;for(m=n.length;v<m;++v){var z=n[v];r+=z[0];u+=z[1];z[0]=r;z[1]=u;Ft(z,p,q)}}}e=[];a=a.objects;g=this.a;for(var A in a)this.b&&-1==this.b.indexOf(A)||("GeometryCollection"===a[A].type?(l=a[A],e.push.apply(e,Ct(l,f,c,d,g,A,b))):(l=a[A],e.push(Dt(l,f,c,d,g,A,b))));return e}return[]};
+function Ft(a,b,c){a[0]=a[0]*b[0]+c[0];a[1]=a[1]*b[1]+c[1]}At.prototype.Tg=function(){return this.defaultDataProjection};
+var Et={Point:function(a,b,c){a=a.coordinates;b&&c&&Ft(a,b,c);return new C(a)},LineString:function(a,b){a=Bt(a.arcs,b);return new I(a)},Polygon:function(a,b){var c=[],d;var e=0;for(d=a.arcs.length;e<d;++e)c[e]=Bt(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)Ft(a[e],b,c)}return new No(a)},MultiLineString:function(a,b){var c=[],d;var e=0;for(d=a.arcs.length;e<d;++e)c[e]=Bt(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]=Bt(g[l],b);c[f]=h}return new Q(c)}};k=At.prototype;k.ld=function(){};k.qe=function(){};k.se=function(){};k.Qg=function(){};k.dd=function(){};function Gt(a){this.rc=a};function Ht(a,b){this.rc=a;this.b=Array.prototype.slice.call(arguments,1);oa(2<=this.b.length,57)}w(Ht,Gt);function It(a){var b=["And"].concat(Array.prototype.slice.call(arguments));Ht.apply(this,b)}w(It,Ht);function Jt(a,b,c){this.rc="BBOX";this.geometryName=a;this.extent=b;this.srsName=c}w(Jt,Gt);function Kt(a,b,c,d){this.rc=a;this.geometryName=b||"the_geom";this.geometry=c;this.srsName=d}w(Kt,Gt);function Lt(a,b,c){Kt.call(this,"Contains",a,b,c)}w(Lt,Kt);function Mt(a,b){this.rc=a;this.b=b}w(Mt,Gt);function Nt(a,b,c){Mt.call(this,"During",a);this.a=b;this.g=c}w(Nt,Mt);function Ot(a,b,c,d){Mt.call(this,a,b);this.g=c;this.a=d}w(Ot,Mt);function Pt(a,b,c){Ot.call(this,"PropertyIsEqualTo",a,b,c)}w(Pt,Ot);function Qt(a,b){Ot.call(this,"PropertyIsGreaterThan",a,b)}w(Qt,Ot);function Rt(a,b){Ot.call(this,"PropertyIsGreaterThanOrEqualTo",a,b)}w(Rt,Ot);function St(a,b,c){Kt.call(this,"Intersects",a,b,c)}w(St,Kt);function Tt(a,b,c){Mt.call(this,"PropertyIsBetween",a);this.a=b;this.g=c}w(Tt,Mt);function Ut(a,b,c,d,e,f){Mt.call(this,"PropertyIsLike",a);this.c=b;this.f=void 0!==c?c:"*";this.i=void 0!==d?d:".";this.g=void 0!==e?e:"!";this.a=f}w(Ut,Mt);function Vt(a){Mt.call(this,"PropertyIsNull",a)}w(Vt,Mt);function Wt(a,b){Ot.call(this,"PropertyIsLessThan",a,b)}w(Wt,Ot);function Xt(a,b){Ot.call(this,"PropertyIsLessThanOrEqualTo",a,b)}w(Xt,Ot);function Yt(a){this.rc="Not";this.condition=a}w(Yt,Gt);function Zt(a,b,c){Ot.call(this,"PropertyIsNotEqualTo",a,b,c)}w(Zt,Ot);function $t(a){var b=["Or"].concat(Array.prototype.slice.call(arguments));Ht.apply(this,b)}w($t,Ht);function au(a,b,c){Kt.call(this,"Within",a,b,c)}w(au,Kt);function bu(a){var b=[null].concat(Array.prototype.slice.call(arguments));return new (Function.prototype.bind.apply(It,b))}function cu(a,b,c){return new Jt(a,b,c)};function du(a){a=a?a:{};this.c=a.featureType;this.a=a.featureNS;this.b=a.gmlFormat?a.gmlFormat:new Kp;this.l=a.schemaLocation?a.schemaLocation:eu["1.1.0"];Wo.call(this)}w(du,Wo);var eu={"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"};
+du.prototype.Kc=function(a,b){var c={featureType:this.c,featureNS:this.a};kb(c,Ho(this,a,b?b:{}));b=[c];this.b.b["http://www.opengis.net/gml"].featureMember=uo(Zo.prototype.ge);(a=O([],this.b.b,a,b,this.b))||(a=[]);return a};du.prototype.j=function(a){if(qo(a))return fu(a);if(ro(a))return O({},gu,a,[]);if("string"===typeof a)return a=so(a),fu(a)};du.prototype.f=function(a){if(qo(a))return hu(this,a);if(ro(a))return iu(this,a);if("string"===typeof a)return a=so(a),hu(this,a)};
+function hu(a,b){for(b=b.firstChild;b;b=b.nextSibling)if(b.nodeType==Node.ELEMENT_NODE)return iu(a,b)}var ju={"http://www.opengis.net/gml":{boundedBy:L(Zo.prototype.rf,"bounds")}};function iu(a,b){var c={},d=gp(b.getAttribute("numberOfFeatures"));c.numberOfFeatures=d;return O(c,ju,b,[],a.b)}
+var ku={"http://www.opengis.net/wfs":{totalInserted:L(fp),totalUpdated:L(fp),totalDeleted:L(fp)}},lu={"http://www.opengis.net/ogc":{FeatureId:uo(function(a){return a.getAttribute("fid")})}},mu={"http://www.opengis.net/wfs":{Feature:function(a,b){Co(lu,a,b)}}},gu={"http://www.opengis.net/wfs":{TransactionSummary:L(function(a,b){return O({},ku,a,b)},"transactionSummary"),InsertResults:L(function(a,b){return O([],mu,a,b)},"insertIds")}};
+function fu(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return O({},gu,a,[])}var nu={"http://www.opengis.net/wfs":{PropertyName:M(ip)}};function ou(a,b){var c=no("http://www.opengis.net/ogc","Filter"),d=no("http://www.opengis.net/ogc","FeatureId");c.appendChild(d);d.setAttribute("fid",b);a.appendChild(c)}function pu(a,b){a=(a?a:"feature")+":";return 0===b.indexOf(a)?b:a+b}
+var qu={"http://www.opengis.net/wfs":{Insert:M(function(a,b,c){var d=c[c.length-1],e=d.gmlVersion;d=no(d.featureNS,d.featureType);a.appendChild(d);if(2===e){a=Tp.prototype;(e=b.c)&&d.setAttribute("fid",e);e=c[c.length-1];var f=e.featureNS,g=b.a;e.tb||(e.tb={},e.tb[f]={});var h=b.L();b=[];var l=[];for(n in h){var m=h[n];null!==m&&(b.push(n),l.push(m),n==g||m instanceof gf?n in e.tb[f]||(e.tb[f][n]=M(a.ui,a)):n in e.tb[f]||(e.tb[f][n]=M(ip)))}var n=kb({},e);n.node=d;Do(n,e.tb,yo(void 0,f),l,c,b)}else Kp.prototype.Ci(d,
+b,c)}),Update:M(function(a,b,c){var d=c[c.length-1];oa(void 0!==b.c,27);var e=d.featurePrefix,f=d.featureNS,g=b.a;a.setAttribute("typeName",pu(e,d.featureType));a.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:"+e,f);e=b.c;if(void 0!==e){f=b.P();for(var h=[],l=0,m=f.length;l<m;l++){var n=b.get(f[l]);if(void 0!==n){var p=f[l];n instanceof gf&&(p=g);h.push({name:p,value:n})}}Do({gmlVersion:d.gmlVersion,node:a,hasZ:d.hasZ,srsName:d.srsName},qu,yo("Property"),h,c);ou(a,e)}}),Delete:M(function(a,
+b,c){c=c[c.length-1];oa(void 0!==b.c,26);var d=c.featurePrefix,e=c.featureNS;a.setAttribute("typeName",pu(d,c.featureType));a.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:"+d,e);b=b.c;void 0!==b&&ou(a,b)}),Property:M(function(a,b,c){var d=no("http://www.opengis.net/wfs","Name"),e=c[c.length-1].gmlVersion;a.appendChild(d);ip(d,b.name);void 0!==b.value&&null!==b.value&&(d=no("http://www.opengis.net/wfs","Value"),a.appendChild(d),b.value instanceof gf?2===e?Tp.prototype.ui(d,b.value,c):Kp.prototype.Yc(d,
+b.value,c):ip(d,b.value))}),Native:M(function(a,b){b.Uq&&a.setAttribute("vendorId",b.Uq);void 0!==b.qq&&a.setAttribute("safeToIgnore",b.qq);void 0!==b.value&&ip(a,b.value)})}};function ru(a,b,c){a={node:a};b=b.b;for(var d=0,e=b.length;d<e;++d){var f=b[d];Do(a,su,yo(f.rc),[f],c)}}function tu(a,b){void 0!==b.a&&a.setAttribute("matchCase",b.a.toString());uu(a,b.b);vu(a,""+b.g)}function wu(a,b,c){a=no("http://www.opengis.net/ogc",a);ip(a,c);b.appendChild(a)}function uu(a,b){wu("PropertyName",a,b)}
+function vu(a,b){wu("Literal",a,b)}function xu(a,b){var c=no("http://www.opengis.net/gml","TimeInstant");a.appendChild(c);a=no("http://www.opengis.net/gml","timePosition");c.appendChild(a);ip(a,b)}
+var su={"http://www.opengis.net/wfs":{Query:M(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?pu(e,b):b);h&&a.setAttribute("srsName",h);f&&a.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:"+e,f);b=kb({},d);b.node=a;Do(b,nu,yo("PropertyName"),g,c);if(d=d.filter)g=no("http://www.opengis.net/ogc","Filter"),a.appendChild(g),Do({node:g},su,yo(d.rc),[d],c)})},"http://www.opengis.net/ogc":{During:M(function(a,b){var c=no("http://www.opengis.net/fes",
+"ValueReference");ip(c,b.b);a.appendChild(c);c=no("http://www.opengis.net/gml","TimePeriod");a.appendChild(c);a=no("http://www.opengis.net/gml","begin");c.appendChild(a);xu(a,b.a);a=no("http://www.opengis.net/gml","end");c.appendChild(a);xu(a,b.g)}),And:M(ru),Or:M(ru),Not:M(function(a,b,c){b=b.condition;Do({node:a},su,yo(b.rc),[b],c)}),BBOX:M(function(a,b,c){c[c.length-1].srsName=b.srsName;uu(a,b.geometryName);Kp.prototype.Yc(a,b.extent,c)}),Contains:M(function(a,b,c){c[c.length-1].srsName=b.srsName;
+uu(a,b.geometryName);Kp.prototype.Yc(a,b.geometry,c)}),Intersects:M(function(a,b,c){c[c.length-1].srsName=b.srsName;uu(a,b.geometryName);Kp.prototype.Yc(a,b.geometry,c)}),Within:M(function(a,b,c){c[c.length-1].srsName=b.srsName;uu(a,b.geometryName);Kp.prototype.Yc(a,b.geometry,c)}),PropertyIsEqualTo:M(tu),PropertyIsNotEqualTo:M(tu),PropertyIsLessThan:M(tu),PropertyIsLessThanOrEqualTo:M(tu),PropertyIsGreaterThan:M(tu),PropertyIsGreaterThanOrEqualTo:M(tu),PropertyIsNull:M(function(a,b){uu(a,b.b)}),
+PropertyIsBetween:M(function(a,b){uu(a,b.b);var c=no("http://www.opengis.net/ogc","LowerBoundary");a.appendChild(c);vu(c,""+b.a);c=no("http://www.opengis.net/ogc","UpperBoundary");a.appendChild(c);vu(c,""+b.g)}),PropertyIsLike:M(function(a,b){a.setAttribute("wildCard",b.f);a.setAttribute("singleChar",b.i);a.setAttribute("escapeChar",b.g);void 0!==b.a&&a.setAttribute("matchCase",b.a.toString());uu(a,b.b);vu(a,""+b.c)})}};
+du.prototype.s=function(a){var b=no("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){oa(a.geometryName,12);var d=cu(a.geometryName,a.bbox,a.srsName);c?c=bu(c,d):c=d}}b.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.l);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:[]};oa(Array.isArray(a.featureTypes),11);a=a.featureTypes;c=[c];d=kb({},c[c.length-1]);d.node=b;Do(d,su,yo("Query"),a,c);return b};
+du.prototype.v=function(a,b,c,d){var e=[],f=no("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",eu[g]);var m=d.featurePrefix?d.featurePrefix:"feature";a&&(g={node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:m,gmlVersion:h,
+hasZ:d.hasZ,srsName:d.srsName},kb(g,l),Do(g,qu,yo("Insert"),a,e));b&&(g={node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:m,gmlVersion:h,hasZ:d.hasZ,srsName:d.srsName},kb(g,l),Do(g,qu,yo("Update"),b,e));c&&Do({node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:m,gmlVersion:h,srsName:d.srsName},qu,yo("Delete"),c,e);d.nativeElements&&Do({node:f,featureNS:d.featureNS,featureType:d.featureType,featurePrefix:m,gmlVersion:h,srsName:d.srsName},qu,yo("Native"),d.nativeElements,
+e);return f};du.prototype.Sg=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.uf(a);return null};du.prototype.uf=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.rf(a,b);return Ob(b.pop().srsName)}return null};function Ku(a){a=a?a:{};Go.call(this);this.b=void 0!==a.splitCollection?a.splitCollection:!1}w(Ku,Vq);function Lu(a){a=a.W();return 0===a.length?"":a.join(" ")}function Mu(a){a=a.W();for(var b=[],c=0,d=a.length;c<d;++c)b.push(a[c].join(" "));return b.join(",")}function Nu(a){var b=[];a=a.Ud();for(var c=0,d=a.length;c<d;++c)b.push("("+Mu(a[c])+")");return b.join(",")}
+function Ou(a){var b=a.S(),c=(0,Pu[b])(a);b=b.toUpperCase();if(a instanceof hf){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 0===c.length?b+" EMPTY":b+"("+c+")"}
+var Pu={Point:Lu,LineString:Mu,Polygon:Nu,MultiPoint:function(a){var b=[];a=a.de();for(var c=0,d=a.length;c<d;++c)b.push("("+Lu(a[c])+")");return b.join(",")},MultiLineString:function(a){var b=[];a=a.wd();for(var c=0,d=a.length;c<d;++c)b.push("("+Mu(a[c])+")");return b.join(",")},MultiPolygon:function(a){var b=[];a=a.Vd();for(var c=0,d=a.length;c<d;++c)b.push("("+Nu(a[c])+")");return b.join(",")},GeometryCollection:function(a){var b=[];a=a.vd();for(var c=0,d=a.length;c<d;++c)b.push(Ou(a[c]));return b.join(",")}};
+k=Ku.prototype;k.fe=function(a,b){return(a=this.Gd(a,b))?(b=new Hk,b.Va(a),b):null};k.Ng=function(a,b){var c=[];a=this.Gd(a,b);this.b&&"GeometryCollection"==a.S()?c=a.a:c=[a];b=[];for(var d=0,e=c.length;d<e;++d)a=new Hk,a.Va(c[d]),b.push(a);return b};k.Gd=function(a,b){a=new Qu(new Ru(a));Su(a);return(a=Tu(a))?Jo(a,!1,b):null};k.pe=function(a,b){return(a=a.U())?this.Kd(a,b):""};
+k.nh=function(a,b){if(1==a.length)return this.pe(a[0],b);for(var c=[],d=0,e=a.length;d<e;++d)c.push(a[d].U());a=new Mq(c);return this.Kd(a,b)};k.Kd=function(a,b){return Ou(Jo(a,!0,b))};function Ru(a){this.a=a;this.b=-1}
+function Uu(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;b=a.b;var 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 Uu(a);if(""===b)c.type=6;else throw Error("Unexpected character: "+b);}return c}function Qu(a){this.g=a;this.a="XY"}function Su(a){a.b=Uu(a.g)}function Vu(a,b){(b=a.b.type==b)&&Su(a);return b}
+function Tu(a){var b=a.b;if(Vu(a,1)){b=b.value;var 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&&Su(a));a.a=c;if("GEOMETRYCOLLECTION"==b){a:{if(Vu(a,2)){b=[];do b.push(Tu(a));while(Vu(a,5));if(Vu(a,3)){a=b;break a}}else if(Wu(a)){a=[];break a}throw Error(Xu(a));}return new Mq(a)}d=Yu[b];c=Zu[b];if(!d||!c)throw Error("Invalid geometry type: "+b);b=d.call(a);return new c(b,a.a)}throw Error(Xu(a));}k=Qu.prototype;
+k.Hg=function(){if(Vu(this,2)){var a=$u(this);if(Vu(this,3))return a}else if(Wu(this))return null;throw Error(Xu(this));};k.Gg=function(){if(Vu(this,2)){var a=av(this);if(Vu(this,3))return a}else if(Wu(this))return[];throw Error(Xu(this));};k.Ig=function(){if(Vu(this,2)){var a=bv(this);if(Vu(this,3))return a}else if(Wu(this))return[];throw Error(Xu(this));};
+k.Hp=function(){if(Vu(this,2)){var a;if(2==this.b.type)for(a=[this.Hg()];Vu(this,5);)a.push(this.Hg());else a=av(this);if(Vu(this,3))return a}else if(Wu(this))return[];throw Error(Xu(this));};k.Gp=function(){if(Vu(this,2)){var a=bv(this);if(Vu(this,3))return a}else if(Wu(this))return[];throw Error(Xu(this));};k.Ip=function(){if(Vu(this,2)){for(var a=[this.Ig()];Vu(this,5);)a.push(this.Ig());if(Vu(this,3))return a}else if(Wu(this))return[];throw Error(Xu(this));};
+function $u(a){for(var b=[],c=a.a.length,d=0;d<c;++d){var e=a.b;if(Vu(a,4))b.push(e.value);else break}if(b.length==c)return b;throw Error(Xu(a));}function av(a){for(var b=[$u(a)];Vu(a,5);)b.push($u(a));return b}function bv(a){for(var b=[a.Gg()];Vu(a,5);)b.push(a.Gg());return b}function Wu(a){var b=1==a.b.type&&"EMPTY"==a.b.value;b&&Su(a);return b}function Xu(a){return"Unexpected `"+a.b.value+"` at position "+a.b.position+" in `"+a.g.a+"`"}
+var Zu={POINT:C,LINESTRING:I,POLYGON:D,MULTIPOINT:No,MULTILINESTRING:P,MULTIPOLYGON:Q},Yu={POINT:Qu.prototype.Hg,LINESTRING:Qu.prototype.Gg,POLYGON:Qu.prototype.Ig,MULTIPOINT:Qu.prototype.Hp,MULTILINESTRING:Qu.prototype.Gp,MULTIPOLYGON:Qu.prototype.Ip};function cv(a){return a.getAttributeNS("http://www.w3.org/1999/xlink","href")};function dv(){}dv.prototype.read=function(a){return qo(a)?this.a(a):ro(a)?this.b(a):"string"===typeof a?(a=so(a),this.a(a)):null};function ev(){this.version=void 0}w(ev,dv);ev.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.b(a);return null};ev.prototype.b=function(a){this.version=a.getAttribute("version").trim();return(a=O({version:this.version},fv,a,[]))?a:null};function gv(a,b){return O({},hv,a,b)}function iv(a,b){return O({},jv,a,b)}function kv(a,b){if(b=gv(a,b))return a=[gp(a.getAttribute("width")),gp(a.getAttribute("height"))],b.size=a,b}
+function lv(a,b){return O([],mv,a,b)}
+var nv=[null,"http://www.opengis.net/wms"],fv=N(nv,{Service:L(function(a,b){return O({},ov,a,b)}),Capability:L(function(a,b){return O({},pv,a,b)})}),pv=N(nv,{Request:L(function(a,b){return O({},qv,a,b)}),Exception:L(function(a,b){return O([],rv,a,b)}),Layer:L(function(a,b){return O({},sv,a,b)})}),ov=N(nv,{Name:L(R),Title:L(R),Abstract:L(R),KeywordList:L(lv),OnlineResource:L(cv),ContactInformation:L(function(a,b){return O({},tv,a,b)}),Fees:L(R),AccessConstraints:L(R),LayerLimit:L(fp),MaxWidth:L(fp),
+MaxHeight:L(fp)}),tv=N(nv,{ContactPersonPrimary:L(function(a,b){return O({},uv,a,b)}),ContactPosition:L(R),ContactAddress:L(function(a,b){return O({},vv,a,b)}),ContactVoiceTelephone:L(R),ContactFacsimileTelephone:L(R),ContactElectronicMailAddress:L(R)}),uv=N(nv,{ContactPerson:L(R),ContactOrganization:L(R)}),vv=N(nv,{AddressType:L(R),Address:L(R),City:L(R),StateOrProvince:L(R),PostCode:L(R),Country:L(R)}),rv=N(nv,{Format:uo(R)}),sv=N(nv,{Name:L(R),Title:L(R),Abstract:L(R),KeywordList:L(lv),CRS:wo(R),
+EX_GeographicBoundingBox:L(function(a,b){var c=O({},wv,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:wo(function(a){var b=[ep(a.getAttribute("minx")),ep(a.getAttribute("miny")),ep(a.getAttribute("maxx")),ep(a.getAttribute("maxy"))],c=[ep(a.getAttribute("resx")),ep(a.getAttribute("resy"))];return{crs:a.getAttribute("CRS"),extent:b,res:c}}),Dimension:wo(function(a){return{name:a.getAttribute("name"),
+units:a.getAttribute("units"),unitSymbol:a.getAttribute("unitSymbol"),"default":a.getAttribute("default"),multipleValues:bp(a.getAttribute("multipleValues")),nearestValue:bp(a.getAttribute("nearestValue")),current:bp(a.getAttribute("current")),values:R(a)}}),Attribution:L(function(a,b){return O({},xv,a,b)}),AuthorityURL:wo(function(a,b){if(b=gv(a,b))return b.name=a.getAttribute("name"),b}),Identifier:wo(R),MetadataURL:wo(function(a,b){if(b=gv(a,b))return b.type=a.getAttribute("type"),b}),DataURL:wo(gv),
+FeatureListURL:wo(gv),Style:wo(function(a,b){return O({},yv,a,b)}),MinScaleDenominator:L(dp),MaxScaleDenominator:L(dp),Layer:wo(function(a,b){var c=b[b.length-1],d=O({},sv,a,b);if(d)return b=bp(a.getAttribute("queryable")),void 0===b&&(b=c.queryable),d.queryable=void 0!==b?b:!1,b=gp(a.getAttribute("cascaded")),void 0===b&&(b=c.cascaded),d.cascaded=b,b=bp(a.getAttribute("opaque")),void 0===b&&(b=c.opaque),d.opaque=void 0!==b?b:!1,b=bp(a.getAttribute("noSubsets")),void 0===b&&(b=c.noSubsets),d.noSubsets=
+void 0!==b?b:!1,(b=ep(a.getAttribute("fixedWidth")))||(b=c.fixedWidth),d.fixedWidth=b,(a=ep(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})}),xv=N(nv,{Title:L(R),OnlineResource:L(cv),LogoURL:L(kv)}),wv=N(nv,{westBoundLongitude:L(dp),
+eastBoundLongitude:L(dp),southBoundLatitude:L(dp),northBoundLatitude:L(dp)}),qv=N(nv,{GetCapabilities:L(iv),GetMap:L(iv),GetFeatureInfo:L(iv)}),jv=N(nv,{Format:wo(R),DCPType:wo(function(a,b){return O({},zv,a,b)})}),zv=N(nv,{HTTP:L(function(a,b){return O({},Av,a,b)})}),Av=N(nv,{Get:L(gv),Post:L(gv)}),yv=N(nv,{Name:L(R),Title:L(R),Abstract:L(R),LegendURL:wo(kv),StyleSheetURL:L(gv),StyleURL:L(gv)}),hv=N(nv,{Format:L(R),OnlineResource:L(cv)}),mv=N(nv,{Keyword:uo(R)});function Bv(a){a=a?a:{};this.a="http://mapserver.gis.umn.edu/mapserver";this.b=new Tp;this.c=a.layers?a.layers:null;Wo.call(this)}w(Bv,Wo);
+Bv.prototype.Kc=function(a,b){var c={};b&&kb(c,Ho(this,a,b));c=[c];a.setAttribute("namespaceURI",this.a);var d=a.localName;b=[];if(0!==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||ec(this.c,l)){l+="_feature";h.featureType=l;h.featureNS=this.a;var m={};m[l]=uo(this.b.Kg,this.b);h=N([h.featureNS,null],m);g.setAttribute("namespaceURI",this.a);(g=O([],
+h,g,c,this.b))&&gc(b,g)}}}"FeatureCollection"==d&&(a=O([],this.b.b,a,[{}],this.b))&&(b=a)}return b};Bv.prototype.mh=function(){};Bv.prototype.bc=function(){};Bv.prototype.re=function(){};function Cv(){}w(Cv,dv);Cv.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.b(a);return null};Cv.prototype.b=function(a){return(a=O({},Dv,a,[]))?a:null};
+var Ev=[null,"http://www.opengis.net/ows/1.1"],Dv=N(Ev,{ServiceIdentification:L(function(a,b){return O({},Fv,a,b)}),ServiceProvider:L(function(a,b){return O({},Gv,a,b)}),OperationsMetadata:L(function(a,b){return O({},Hv,a,b)})}),Iv=N(Ev,{DeliveryPoint:L(R),City:L(R),AdministrativeArea:L(R),PostalCode:L(R),Country:L(R),ElectronicMailAddress:L(R)}),Jv=N(Ev,{Value:wo(function(a){return R(a)})}),Kv=N(Ev,{AllowedValues:L(function(a,b){return O({},Jv,a,b)})}),Mv=N(Ev,{Phone:L(function(a,b){return O({},
+Lv,a,b)}),Address:L(function(a,b){return O({},Iv,a,b)})}),Ov=N(Ev,{HTTP:L(function(a,b){return O({},Nv,a,b)})}),Nv=N(Ev,{Get:wo(function(a,b){var c=cv(a);if(c)return O({href:c},Pv,a,b)}),Post:void 0}),Qv=N(Ev,{DCP:L(function(a,b){return O({},Ov,a,b)})}),Hv=N(Ev,{Operation:function(a,b){var c=a.getAttribute("name");(a=O({},Qv,a,b))&&(b[b.length-1][c]=a)}}),Lv=N(Ev,{Voice:L(R),Facsimile:L(R)}),Pv=N(Ev,{Constraint:wo(function(a,b){var c=a.getAttribute("name");if(c)return O({name:c},Kv,a,b)})}),Rv=N(Ev,
+{IndividualName:L(R),PositionName:L(R),ContactInfo:L(function(a,b){return O({},Mv,a,b)})}),Fv=N(Ev,{Abstract:L(R),AccessConstraints:L(R),Fees:L(R),Title:L(R),ServiceTypeVersion:L(R),ServiceType:L(R)}),Gv=N(Ev,{ProviderName:L(R),ProviderSite:L(cv),ServiceContact:L(function(a,b){return O({},Rv,a,b)})});function Sv(){this.g=new Cv}w(Sv,dv);Sv.prototype.a=function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType==Node.ELEMENT_NODE)return this.b(a);return null};Sv.prototype.b=function(a){var b=a.getAttribute("version").trim(),c=this.g.b(a);if(!c)return null;c.version=b;return(c=O(c,Tv,a,[]))?c:null};function Uv(a){var b=R(a).split(" ");if(b&&2==b.length&&(a=+b[0],b=+b[1],!isNaN(a)&&!isNaN(b)))return[a,b]}
+var Vv=[null,"http://www.opengis.net/wmts/1.0"],Wv=[null,"http://www.opengis.net/ows/1.1"],Tv=N(Vv,{Contents:L(function(a,b){return O({},Xv,a,b)})}),Xv=N(Vv,{Layer:wo(function(a,b){return O({},Yv,a,b)}),TileMatrixSet:wo(function(a,b){return O({},Zv,a,b)})}),Yv=N(Vv,{Style:wo(function(a,b){if(b=O({},$v,a,b))return a="true"===a.getAttribute("isDefault"),b.isDefault=a,b}),Format:wo(R),TileMatrixSetLink:wo(function(a,b){return O({},aw,a,b)}),Dimension:wo(function(a,b){return O({},bw,a,b)}),ResourceURL:wo(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})},N(Wv,{Title:L(R),Abstract:L(R),WGS84BoundingBox:L(function(a,b){a=O([],cw,a,b);if(2==a.length)return Ca(a)}),Identifier:L(R)})),$v=N(Vv,{LegendURL:wo(function(a){var b={};b.format=a.getAttribute("format");b.href=cv(a);return b})},N(Wv,{Title:L(R),Identifier:L(R)})),aw=N(Vv,{TileMatrixSet:L(R),TileMatrixSetLimits:L(function(a,b){return O([],
+dw,a,b)})}),dw=N(Vv,{TileMatrixLimits:uo(function(a,b){return O({},ew,a,b)})}),ew=N(Vv,{TileMatrix:L(R),MinTileRow:L(fp),MaxTileRow:L(fp),MinTileCol:L(fp),MaxTileCol:L(fp)}),bw=N(Vv,{Default:L(R),Value:wo(R)},N(Wv,{Identifier:L(R)})),cw=N(Wv,{LowerCorner:uo(Uv),UpperCorner:uo(Uv)}),Zv=N(Vv,{WellKnownScaleSet:L(R),TileMatrix:wo(function(a,b){return O({},fw,a,b)})},N(Wv,{SupportedCRS:L(R),Identifier:L(R)})),fw=N(Vv,{TopLeftCorner:L(Uv),ScaleDenominator:L(dp),TileWidth:L(fp),TileHeight:L(fp),MatrixWidth:L(fp),
+MatrixHeight:L(fp)},N(Wv,{Identifier:L(R)}));function gw(a,b,c){hf.call(this);this.hh(a,b?b:0,c)}w(gw,hf);k=gw.prototype;k.clone=function(){var a=new gw(null);kf(a,this.ja,this.A.slice());a.u();return a};k.Nb=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(0===b)for(d=0;d<this.a;++d)c[d]=e[d];else for(d=this.Bd()/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];c.length=this.a;return b}return d};k.Zc=function(a,b){var c=this.A;a-=c[0];b-=c[1];return a*a+b*b<=hw(this)};
+k.xa=function(){return this.A.slice(0,this.a)};k.Ae=function(a){var b=this.A,c=b[this.a]-b[0];return Na(b[0]-c,b[1]-c,b[0]+c,b[1]+c,a)};k.Bd=function(){return Math.sqrt(hw(this))};function hw(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.S=function(){return"Circle"};k.$a=function(a){var b=this.G();return hb(a,b)?(b=this.xa(),a[0]<=b[0]&&a[2]>=b[0]||a[1]<=b[1]&&a[3]>=b[1]?!0:Ua(a,this.Bb,this)):!1};
+k.ub=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];kf(this,this.ja,c);this.u()};k.hh=function(a,b,c){if(a){lf(this,c,a,0);this.A||(this.A=[]);c=this.A;a=vf(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 kf(this,"XY",null);this.u()};k.W=function(){};k.na=function(){};k.fd=function(a){this.A[this.a]=this.A[0]+a;this.u()};function iw(a){a=a?a:{};Jg.call(this,{handleEvent:Re});this.j=a.formatConstructors?a.formatConstructors:[];this.s=a.projection?Ob(a.projection):null;this.a=null;this.f=a.source||null;this.target=a.target?a.target:null}w(iw,Jg);function jw(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.l.bind(this,d));e.readAsText(d)}}function kw(a){a.stopPropagation();a.preventDefault();a.dataTransfer.dropEffect="copy"}
+iw.prototype.l=function(a,b){b=b.target.result;var c=this.v,d=this.s;d||(d=c.aa().v);c=this.j;var e=[],f;var g=0;for(f=c.length;g<f;++g){var h=new c[g];var l={featureProjection:d};try{e=h.Qa(b,l)}catch(m){e=null}if(e&&0<e.length)break}this.f&&(this.f.clear(),this.f.Qc(e));this.b(new lw(mw,a,e,d))};function nw(a){var b=a.v;b&&(b=a.target?a.target:b.a,a.a=[y(b,"drop",jw,a),y(b,"dragenter",kw,a),y(b,"dragover",kw,a),y(b,"drop",kw,a)])}
+iw.prototype.Ha=function(a){Jg.prototype.Ha.call(this,a);a?nw(this):ow(this)};iw.prototype.setMap=function(a){ow(this);Jg.prototype.setMap.call(this,a);this.c()&&nw(this)};function ow(a){a.a&&(a.a.forEach(Gc),a.a=null)}var mw="addfeatures";function lw(a,b,c,d){Qc.call(this,a);this.features=c;this.file=b;this.projection=d}w(lw,Qc);function pw(a){a=a?a:{};fh.call(this,{handleDownEvent:qw,handleDragEvent:rw,handleUpEvent:sw});this.s=a.condition?a.condition:bh;this.a=this.f=void 0;this.j=0;this.o=void 0!==a.duration?a.duration:400}w(pw,fh);
+function rw(a){if(dh(a)){var b=a.map,c=b.Cb(),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.aa();b.l.rotation!==re&&void 0!==this.f&&(d=c-this.f,Kg(b,b.Sa()-d));this.f=c;void 0!==this.a&&(c=this.a*(b.Pa()/a),Tg(b,c));void 0!==this.a&&(this.j=this.a/a);this.a=a}}
+function sw(a){if(!dh(a))return!0;a=a.map.aa();bg(a,1,-1);var b=this.j-1,c=a.Sa();c=a.constrainRotation(c,0);Kg(a,c,void 0,void 0);c=a.Pa();var d=this.o;c=a.constrainResolution(c,0,b);Tg(a,c,void 0,d);this.j=0;return!1}function qw(a){return dh(a)&&this.s(a)?(bg(a.map.aa(),1,1),this.a=this.f=void 0,!0):!1};function T(a){a=a?a:{};var b=kb({},a);delete b.style;delete b.renderBuffer;delete b.updateWhileAnimating;delete b.updateWhileInteracting;xg.call(this,b);this.D=void 0!==a.declutter?a.declutter:!1;this.f=void 0!==a.renderBuffer?a.renderBuffer:100;this.C=null;this.V=void 0;this.j(a.style);this.ca=void 0!==a.updateWhileAnimating?a.updateWhileAnimating:!1;this.ra=void 0!==a.updateWhileInteracting?a.updateWhileInteracting:!1;this.l=a.renderMode||"vector";this.type="VECTOR"}w(T,xg);T.prototype.B=function(){return this.C};
+T.prototype.ib=function(){return this.V};T.prototype.j=function(a){this.C=void 0!==a?a:Fk;this.V=null===a?void 0:Dk(this.C);this.u()};var ik="renderOrder";function tw(){return[[-Infinity,-Infinity,Infinity,Infinity]]};function uw(a){Vc.call(this);this.c=Ob(a.projection);this.v=null;this.C=vw(this,a.attributions);this.T=a.logo;this.ra=void 0!==a.state?a.state:"ready";this.D=void 0!==a.wrapX?a.wrapX:!1}w(uw,Vc);
+function vw(a,b){if(!b)return null;if(b instanceof Ec)return a.v=[b],function(){return[b.og]};if(Array.isArray(b)){if(b[0]instanceof Ec){a.v=b;var c=b.map(function(a){return a.og});return function(){return c}}a.v=b.map(function(a){return new Ec({html:a})});return function(){return b}}if("function"===typeof b)return b;a.v=[new Ec({html:b})];return function(){return[b]}}k=uw.prototype;k.wa=ea;k.za=function(){return this.v};k.Aa=function(){return this.T};k.Da=function(){return this.c};k.getState=function(){return this.ra};
+k.sa=function(){this.u()};k.va=function(a){this.C=vw(this,a);this.u()};function ww(a,b){a.ra=b;a.u()};function U(a){a=a||{};uw.call(this,{attributions:a.attributions,logo:a.logo,projection:void 0,state:"ready",wrapX:void 0!==a.wrapX?a.wrapX:!0});this.o=ea;this.O=a.format;this.$=void 0==a.overlaps?!0:a.overlaps;this.V=a.url;void 0!==a.loader?this.o=a.loader:void 0!==this.V&&(oa(this.O,7),this.o=Fo(this.V,this.O));this.ca=void 0!==a.strategy?a.strategy:tw;var b=void 0!==a.useSpatialIndex?a.useSpatialIndex:!0;this.a=b?new um:null;this.B=new um;this.f={};this.j={};this.l={};this.s={};this.i=null;if(a.features instanceof
+B){var c=a.features;var d=c.a}else Array.isArray(a.features)&&(d=a.features);b||void 0!==c||(c=new B(d));void 0!==d&&xw(this,d);void 0!==c&&yw(this,c)}w(U,uw);k=U.prototype;k.Gb=function(a){var b=x(a).toString();if(zw(this,b,a)){Aw(this,b,a);var c=a.U();c?(b=c.G(),this.a&&this.a.Ca(b,a)):this.f[b]=a;this.b(new Bw("addfeature",a))}this.u()};function Aw(a,b,c){a.s[b]=[y(c,"change",a.gj,a),y(c,"propertychange",a.gj,a)]}
+function zw(a,b,c){var d=!0,e=c.c;void 0!==e?e.toString()in a.j?d=!1:a.j[e.toString()]=c:(oa(!(b in a.l),30),a.l[b]=c);return d}k.Qc=function(a){xw(this,a);this.u()};function xw(a,b){var c,d=[],e=[],f=[];var g=0;for(c=b.length;g<c;g++){var h=b[g];var l=x(h).toString();zw(a,l,h)&&e.push(h)}g=0;for(c=e.length;g<c;g++)h=e[g],l=x(h).toString(),Aw(a,l,h),(b=h.U())?(l=b.G(),d.push(l),f.push(h)):a.f[l]=h;a.a&&a.a.load(d,f);g=0;for(c=e.length;g<c;g++)a.b(new Bw("addfeature",e[g]))}
+function yw(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.Gb(a.element),c=!1)},a);y(b,"remove",function(a){c||(c=!0,this.Lb(a.element),c=!1)},a);a.i=b}
+k.clear=function(a){if(a){for(var b in this.s)this.s[b].forEach(Gc);this.i||(this.s={},this.j={},this.l={})}else if(this.a){this.a.forEach(this.Yg,this);for(var c in this.f)this.Yg(this.f[c])}this.i&&this.i.clear();this.a&&this.a.clear();this.B.clear();this.f={};this.b(new Bw("clear"));this.u()};k.Lh=function(a,b){if(this.a)return this.a.forEach(a,b);if(this.i)return this.i.forEach(a,b)};function Cw(a,b,c){a.ec([b[0],b[1],b[0],b[1]],function(a){if(a.U().Bb(b))return c.call(void 0,a)})}
+k.ec=function(a,b,c){if(this.a)return zm(this.a,a,b,c);if(this.i)return this.i.forEach(b,c)};k.Mh=function(a,b,c){return this.ec(a,function(d){if(d.U().$a(a)&&(d=b.call(c,d)))return d})};k.Th=function(){return this.i};k.ee=function(){if(this.i)var a=this.i.a;else this.a&&(a=wm(this.a),nb(this.f)||gc(a,mb(this.f)));return a};k.Sh=function(a){var b=[];Cw(this,a,function(a){b.push(a)});return b};k.Yf=function(a){return xm(this.a,a)};
+k.Oh=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:Re;zm(this.a,h,function(a){if(l(a)){var b=a.U(),m=g;g=b.Nb(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.Rh=function(a){a=this.j[a.toString()];return void 0!==a?a:null};k.ej=function(){return this.O};k.fj=function(){return this.V};
+k.gj=function(a){a=a.target;var b=x(a).toString(),c=a.U();c?(c=c.G(),b in this.f?(delete this.f[b],this.a&&this.a.Ca(c,a)):this.a&&vm(this.a,c,a)):b in this.f||(this.a&&this.a.remove(a),this.f[b]=a);c=a.c;void 0!==c?(c=c.toString(),b in this.l?(delete this.l[b],this.j[c]=a):this.j[c]!==a&&(Dw(this,a),this.j[c]=a)):b in this.l||(Dw(this,a),this.l[b]=a);this.u();this.b(new Bw("changefeature",a))};
+k.ae=function(a,b,c){var d=this.B;a=this.ca(a,b);var e;var f=0;for(e=a.length;f<e;++f){var g=a[f];zm(d,g,function(a){return La(a.extent,g)})||(this.o.call(this,g,b,c),d.Ca(g,{extent:g.slice()}))}};k.Cj=function(a){var b=this.B,c;zm(b,a,function(b){if(Sa(b.extent,a))return c=b,!0});c&&b.remove(c)};k.Lb=function(a){var b=x(a).toString();b in this.f?delete this.f[b]:this.a&&this.a.remove(a);this.Yg(a);this.u()};
+k.Yg=function(a){var b=x(a).toString();this.s[b].forEach(Gc);delete this.s[b];var c=a.c;void 0!==c?delete this.j[c.toString()]:delete this.l[b];this.b(new Bw("removefeature",a))};function Dw(a,b){for(var c in a.j)if(a.j[c]===b){delete a.j[c];break}}k.hj=function(a){this.o=a};function Bw(a,b){Qc.call(this,a);this.feature=b}w(Bw,Qc);function Ew(a){fh.call(this,{handleDownEvent:Fw,handleEvent:Gw,handleUpEvent:Hw});this.V=!1;this.ca=null;this.o=!1;this.ob=a.source?a.source:null;this.La=a.features?a.features:null;this.Xk=a.snapTolerance?a.snapTolerance:12;this.O=a.type;this.f=Iw(this.O);this.$k=!!a.stopClick;this.Ea=a.minPoints?a.minPoints:this.f===Jw?3:2;this.ua=a.maxPoints?a.maxPoints:Infinity;this.Md=a.finishCondition?a.finishCondition:Re;var b=a.geometryFunction;if(!b)if("Circle"===this.O)b=function(a,b){b=b?b:new gw([NaN,NaN]);
+b.hh(a[0],Math.sqrt(He(a[0],a[1])));return b};else{var c,d=this.f;d===Kw?c=C:d===Lw?c=I:d===Jw&&(c=D);b=function(a,b){b?d===Jw?a[0].length?b.na([a[0].concat([a[0][0]])]):b.na([]):b.na(a):b=new c(a);return b}}this.cb=b;this.T=this.C=this.a=this.B=this.j=this.s=null;this.sc=a.clickTolerance?a.clickTolerance*a.clickTolerance:36;this.ra=new T({source:new U({useSpatialIndex:!1,wrapX:a.wrapX?a.wrapX:!1}),style:a.style?a.style:Mw()});this.bb=a.geometryName;this.Wk=a.condition?a.condition:ah;this.If=a.freehand?
+Re:a.freehandCondition?a.freehandCondition:bh;y(this,Xc("active"),this.Ki,this)}w(Ew,fh);function Mw(){var a=Gk();return function(b){return a[b.U().S()]}}k=Ew.prototype;k.setMap=function(a){fh.prototype.setMap.call(this,a);this.Ki()};function Gw(a){this.o=this.f!==Kw&&this.If(a);var b=!0;this.o&&"pointerdrag"===a.type&&null!==this.j?(Nw(this,a),b=!1):this.o&&"pointerdown"===a.type?b=!1:"pointermove"===a.type?b=Ow(this,a):"dblclick"===a.type&&(b=!1);return gh.call(this,a)&&b}
+function Fw(a){this.V=!this.o;return this.o?(this.ca=a.pixel,this.s||Pw(this,a),!0):this.Wk(a)?(this.ca=a.pixel,!0):!1}function Hw(a){var b=!0;Ow(this,a);var c=this.f===Qw;this.V?(this.s?this.o||c?this.Pd():Rw(this,a)?this.Md(a)&&this.Pd():Nw(this,a):(Pw(this,a),this.f===Kw&&this.Pd()),b=!1):this.o&&(this.s=null,Sw(this));!b&&this.$k&&a.stopPropagation();return b}
+function Ow(a,b){if(a.ca&&(!a.o&&a.V||a.o&&!a.V)){var c=a.ca,d=b.pixel,e=c[0]-d[0];c=c[1]-d[1];e=e*e+c*c;a.V=a.o?e>a.sc:e<=a.sc}a.s?(e=b.coordinate,c=a.j.U(),a.f===Kw?d=a.a:a.f===Jw?(d=a.a[0],d=d[d.length-1],Rw(a,b)&&(e=a.s.slice())):(d=a.a,d=d[d.length-1]),d[0]=e[0],d[1]=e[1],a.cb(a.a,c),a.B&&a.B.U().na(e),c instanceof D&&a.f!==Jw?(a.C||(a.C=new Hk(new I(null))),e=c.Wh(0),b=a.C.U(),b.ba(e.ja,e.da())):a.T&&(b=a.C.U(),b.na(a.T)),Tw(a)):(b=b.coordinate.slice(),a.B?a.B.U().na(b):(a.B=new Hk(new C(b)),
+Tw(a)));return!0}function Rw(a,b){var c=!1;if(a.j){var d=!1,e=[a.s];a.f===Lw?d=a.a.length>a.Ea:a.f===Jw&&(d=a.a[0].length>a.Ea,e=[a.a[0][0],a.a[0][a.a[0].length-2]]);if(d){d=b.map;for(var f=0,g=e.length;f<g;f++){var h=e[f],l=d.Ia(h),m=b.pixel;c=m[0]-l[0];l=m[1]-l[1];if(c=Math.sqrt(c*c+l*l)<=(a.o?1:a.Xk)){a.s=h;break}}}}return c}
+function Pw(a,b){b=b.coordinate;a.s=b;a.f===Kw?a.a=b.slice():a.f===Jw?(a.a=[[b.slice(),b.slice()]],a.T=a.a[0]):(a.a=[b.slice(),b.slice()],a.f===Qw&&(a.T=a.a));a.T&&(a.C=new Hk(new I(a.T)));b=a.cb(a.a);a.j=new Hk;a.bb&&a.j.Lc(a.bb);a.j.Va(b);Tw(a);a.b(new Uw("drawstart",a.j))}
+function Nw(a,b){b=b.coordinate;var c=a.j.U(),d;if(a.f===Lw){a.s=b.slice();var e=a.a;e.length>=a.ua&&(a.o?e.pop():d=!0);e.push(b.slice());a.cb(e,c)}else a.f===Jw&&(e=a.a[0],e.length>=a.ua&&(a.o?e.pop():d=!0),e.push(b.slice()),d&&(a.s=e[0]),a.cb(a.a,c));Tw(a);d&&a.Pd()}
+k.nq=function(){if(this.j){var a=this.j.U();if(this.f===Lw){var b=this.a;b.splice(-2,1);this.cb(b,a);2<=b.length&&(this.s=b[b.length-2].slice())}else if(this.f===Jw){b=this.a[0];b.splice(-2,1);var c=this.C.U();c.na(b);this.cb(this.a,a)}0===b.length&&(this.s=null);Tw(this)}};
+k.Pd=function(){var a=Sw(this),b=this.a,c=a.U();this.f===Lw?(b.pop(),this.cb(b,c)):this.f===Jw&&(b[0].pop(),this.cb(b,c),b=c.W());"MultiPoint"===this.O?a.Va(new No([b])):"MultiLineString"===this.O?a.Va(new P([b])):"MultiPolygon"===this.O&&a.Va(new Q([b]));this.b(new Uw("drawend",a));this.La&&this.La.push(a);this.ob&&this.ob.Gb(a)};function Sw(a){a.s=null;var b=a.j;b&&(a.j=null,a.B=null,a.C=null,a.ra.ha().clear(!0));return b}
+k.Zn=function(a){var b=a.U();this.j=a;this.a=b.W();a=this.a[this.a.length-1];this.s=a.slice();this.a.push(a.slice());Tw(this);this.b(new Uw("drawstart",this.j))};k.jd=Se;function Tw(a){var b=[];a.j&&b.push(a.j);a.C&&b.push(a.C);a.B&&b.push(a.B);a=a.ra.ha();a.clear(!0);a.Qc(b)}k.Ki=function(){var a=this.v,b=this.c();a&&b||Sw(this);this.ra.setMap(b?a:null)};
+function Iw(a){var b;"Point"===a||"MultiPoint"===a?b=Kw:"LineString"===a||"MultiLineString"===a?b=Lw:"Polygon"===a||"MultiPolygon"===a?b=Jw:"Circle"===a&&(b=Qw);return b}var Kw="Point",Lw="LineString",Jw="Polygon",Qw="Circle";function Uw(a,b){Qc.call(this,a);this.feature=b}w(Uw,Qc);function Vw(a){var b=a||{};this.a=this.j=null;this.C=void 0!==b.pixelTolerance?b.pixelTolerance:10;this.B=!1;this.T=this.s=null;a||(a={});fh.call(this,{handleDownEvent:Ww,handleDragEvent:Xw,handleEvent:Yw,handleUpEvent:Zw});this.o=new T({source:new U({useSpatialIndex:!1,wrapX:!!a.wrapX}),style:a.boxStyle?a.boxStyle:$w(),updateWhileAnimating:!0,updateWhileInteracting:!0});this.O=new T({source:new U({useSpatialIndex:!1,wrapX:!!a.wrapX}),style:a.pointerStyle?a.pointerStyle:ax(),updateWhileAnimating:!0,
+updateWhileInteracting:!0});a.extent&&this.f(a.extent)}w(Vw,fh);function Yw(a){if(!(a instanceof Ad))return!0;if("pointermove"==a.type&&!this.D){var b=a.pixel,c=a.map,d=bx(this,b,c);d||(d=c.Ra(b));cx(this,d)}gh.call(this,a);return!1}
+function Ww(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=bx(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=dx(b(a)):null!==c?this.a=ex(b([c,e[1]]),b([c,e[3]])):null!==d&&(this.a=ex(b([e[0],d]),b([e[2],d])))):(a=d.Ra(c),this.f([a[0],a[1],a[0],a[1]]),this.a=dx(a));return!0}
+function Xw(a){this.a&&(a=a.coordinate,this.f(this.a(a)),cx(this,a));return!0}function Zw(){this.a=null;var a=this.G();a&&0!==ab(a)||this.f(null);return!1}function $w(){var a=Gk();return function(){return a.Polygon}}function ax(){var a=Gk();return function(){return a.Point}}function dx(a){return function(b){return Ca([a,b])}}function ex(a,b){return a[0]==b[0]?function(c){return Ca([a,[c[0],b[1]]])}:a[1]==b[1]?function(c){return Ca([a,[b[0],c[1]]])}:null}
+function bx(a,b,c){function d(a,b){return Je(e,a)-Je(e,b)}var e=c.Ra(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);f=f[0];var g=Be(e,f),h=c.Ia(g);if(Ie(b,h)<=a.C)return b=c.Ia(f[0]),c=c.Ia(f[1]),b=He(h,b),c=He(h,c),a.B=Math.sqrt(Math.min(b,c))<=a.C,a.B&&(g=b>c?f[1]:f[0]),g}return null}function cx(a,b){var c=a.T;c?c.U().na(b):(c=new Hk(new C(b)),a.T=c,a.O.ha().Gb(c))}
+Vw.prototype.setMap=function(a){this.o.setMap(a);this.O.setMap(a);fh.prototype.setMap.call(this,a)};Vw.prototype.G=function(){return this.j};Vw.prototype.f=function(a){this.j=a?a:null;var b=this.s;b?a?b.Va(Rf(a)):b.Va(void 0):(this.s=b=a?new Hk(Rf(a)):new Hk({}),this.o.ha().Gb(b));this.b(new fx(this.j))};function fx(a){Qc.call(this,"extentchanged");this.extent=a}w(fx,Qc);function gx(a){fh.call(this,{handleDownEvent:hx,handleDragEvent:ix,handleEvent:jx,handleUpEvent:kx});this.Md=a.condition?a.condition:eh;this.bb=function(a){return Wg(a)&&$g(a)};this.ob=a.deleteCondition?a.deleteCondition:this.bb;this.sc=a.insertVertexCondition?a.insertVertexCondition:Re;this.La=this.f=null;this.Ea=[0,0];this.C=this.T=!1;this.a=new um;this.ra=void 0!==a.pixelTolerance?a.pixelTolerance:10;this.s=this.ua=!1;this.j=[];this.B=new T({source:new U({useSpatialIndex:!1,wrapX:!!a.wrapX}),style:a.style?
+a.style:lx(),updateWhileAnimating:!0,updateWhileInteracting:!0});this.ca={Point:this.io,LineString:this.Mi,LinearRing:this.Mi,Polygon:this.jo,MultiPoint:this.fo,MultiLineString:this.eo,MultiPolygon:this.ho,Circle:this.bo,GeometryCollection:this.co};this.V=null;a.source?(this.V=a.source,a=new B(this.V.ee()),y(this.V,"addfeature",this.vm,this),y(this.V,"removefeature",this.xm,this)):a=a.features;if(!a)throw Error("The modify interaction requires features or a source");this.o=a;this.o.forEach(this.xg,
+this);y(this.o,"add",this.$n,this);y(this.o,"remove",this.ao,this);this.O=null}w(gx,fh);k=gx.prototype;k.xg=function(a){var b=a.U();b&&b.S()in this.ca&&this.ca[b.S()].call(this,a,b);(b=this.v)&&b.c&&this.c()&&mx(this,this.Ea,b);y(a,"change",this.Li,this)};function nx(a,b){a.C||(a.C=!0,a.b(new ox("modifystart",a.o,b)))}function px(a,b){qx(a,b);a.f&&0===a.o.kc()&&(a.B.ha().Lb(a.f),a.f=null);Mc(b,"change",a.Li,a)}
+function qx(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.f&&!a&&(this.B.ha().Lb(this.f),this.f=null);fh.prototype.Ha.call(this,a)};k.setMap=function(a){this.B.setMap(a);fh.prototype.setMap.call(this,a)};k.vm=function(a){a.feature&&this.o.push(a.feature)};k.xm=function(a){a.feature&&this.o.remove(a.feature)};k.$n=function(a){this.xg(a.element)};k.Li=function(a){this.s||(a=a.target,px(this,a),this.xg(a))};
+k.ao=function(a){px(this,a.element)};k.io=function(a,b){var c=b.W();a={feature:a,geometry:b,ma:[c,c]};this.a.Ca(b.G(),a)};k.fo=function(a,b){var c=b.W(),d;var e=0;for(d=c.length;e<d;++e){var f=c[e];f={feature:a,geometry:b,depth:[e],index:e,ma:[f,f]};this.a.Ca(b.G(),f)}};k.Mi=function(a,b){var c=b.W(),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,ma:f};this.a.Ca(Ca(f),g)}};
+k.eo=function(a,b){var c=b.W(),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,ma:l};this.a.Ca(Ca(l),m)}}};k.jo=function(a,b){var c=b.W(),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,ma:l};this.a.Ca(Ca(l),m)}}};
+k.ho=function(a,b){var c=b.W(),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,ma:p};this.a.Ca(Ca(p),q)}}}};k.bo=function(a,b){var c=b.xa(),d={feature:a,geometry:b,index:0,ma:[c,c]};a={feature:a,geometry:b,index:1,ma:[c,c]};d.Tf=a.Tf=[d,a];this.a.Ca(Pa(c),d);this.a.Ca(b.G(),a)};
+k.co=function(a,b){var c=b.a;for(b=0;b<c.length;++b)this.ca[c[b].S()].call(this,a,c[b])};function rx(a,b){var c=a.f;c?c.U().na(b):(c=new Hk(new C(b)),a.f=c,a.B.ha().Gb(c))}function sx(a,b){return a.index-b.index}
+function hx(a){if(!this.Md(a))return!1;mx(this,a.pixel,a.map);var b=a.map.Ra(a.pixel);this.j.length=0;this.C=!1;var c=this.f;if(c){var d=[];c=c.U().W();var e=Ca([c]);e=xm(this.a,e);var f={};e.sort(sx);for(var g=0,h=e.length;g<h;++g){var l=e[g],m=l.ma,n=x(l.feature),p=l.depth;p&&(n+="-"+p.join("-"));f[n]||(f[n]=Array(2));if("Circle"===l.geometry.S()&&1===l.index)m=tx(b,l),Ee(m,c)&&!f[n][0]&&(this.j.push([l,0]),f[n][0]=l);else if(Ee(m[0],c)&&!f[n][0])this.j.push([l,0]),f[n][0]=l;else if(Ee(m[1],c)&&
+!f[n][1]){if("LineString"!==l.geometry.S()&&"MultiLineString"!==l.geometry.S()||!f[n][0]||0!==f[n][0].index)this.j.push([l,1]),f[n][1]=l}else this.sc(a)&&x(m)in this.La&&!f[n][0]&&!f[n][1]&&d.push([l,c])}d.length&&nx(this,a);for(a=d.length-1;0<=a;--a)this.Em.apply(this,d[a])}return!!this.f}
+function ix(a){this.T=!1;nx(this,a);a=a.coordinate;for(var b=0,c=this.j.length;b<c;++b){var d=this.j[b],e=d[0],f=e.depth,g=e.geometry,h=e.ma;for(d=d[1];a.length<g.pa();)a.push(h[d][a.length]);switch(g.S()){case "Point":var l=a;h[0]=h[1]=a;break;case "MultiPoint":l=g.W();l[e.index]=a;h[0]=h[1]=a;break;case "LineString":l=g.W();l[e.index+d]=a;h[d]=a;break;case "MultiLineString":l=g.W();l[f[0]][e.index+d]=a;h[d]=a;break;case "Polygon":l=g.W();l[f[0]][e.index+d]=a;h[d]=a;break;case "MultiPolygon":l=g.W();
+l[f[1]][f[0]][e.index+d]=a;h[d]=a;break;case "Circle":h[0]=h[1]=a,0===e.index?(this.s=!0,g.ub(a)):(this.s=!0,g.fd(Ie(g.xa(),a))),this.s=!1}l&&(e=g,f=l,this.s=!0,e.na(f),this.s=!1)}rx(this,a)}function kx(a){for(var b,c,d=this.j.length-1;0<=d;--d)if(b=this.j[d][0],c=b.geometry,"Circle"===c.S()){var e=c.xa(),f=b.Tf[0];b=b.Tf[1];f.ma[0]=f.ma[1]=e;b.ma[0]=b.ma[1]=e;vm(this.a,Pa(e),f);vm(this.a,c.G(),b)}else vm(this.a,Ca(b.ma),b);this.C&&(this.b(new ox("modifyend",this.o,a)),this.C=!1);return!1}
+function jx(a){if(!(a instanceof Ad))return!0;this.O=a;var b;a.map.aa().Vh()||"pointermove"!=a.type||this.D||(this.Ea=a.pixel,mx(this,a.pixel,a.map));this.f&&this.ob(a)&&(b="singleclick"==a.type&&this.T?!0:this.Dj());"singleclick"==a.type&&(this.T=!1);return gh.call(this,a)&&!b}
+function mx(a,b,c){function d(a,b){return ux(e,a)-ux(e,b)}var e=c.Ra(b),f=Fa(Pa(e),c.aa().Pa()*a.ra);f=xm(a.a,f);if(0<f.length){f.sort(d);var g=f[0],h=g.ma,l=tx(e,g),m=c.Ia(l),n=Ie(b,m);if(n<=a.ra){b={};if("Circle"===g.geometry.S()&&1===g.index)a.ua=!0,rx(a,l);else for(n=c.Ia(h[0]),g=c.Ia(h[1]),c=He(m,n),m=He(m,g),n=Math.sqrt(Math.min(c,m)),a.ua=n<=a.ra,a.ua&&(l=c>m?h[1]:h[0]),rx(a,l),m=1,c=f.length;m<c;++m)if(l=f[m].ma,Ee(h[0],l[0])&&Ee(h[1],l[1])||Ee(h[0],l[1])&&Ee(h[1],l[0]))b[x(l)]=!0;else break;
+b[x(h)]=!0;a.La=b;return}}a.f&&(a.B.ha().Lb(a.f),a.f=null)}function ux(a,b){var c=b.geometry;return"Circle"===c.S()&&1===b.index?(a=He(c.xa(),a),c=Math.sqrt(a)-c.Bd(),c*c):Je(a,b.ma)}function tx(a,b){var c=b.geometry;return"Circle"===c.S()&&1===b.index?c.Ib(a):Be(a,b.ma)}
+k.Em=function(a,b){for(var c=a.ma,d=a.feature,e=a.geometry,f=a.depth,g=a.index,h;b.length<e.pa();)b.push(0);switch(e.S()){case "MultiLineString":h=e.W();h[f[0]].splice(g+1,0,b);break;case "Polygon":h=e.W();h[f[0]].splice(g+1,0,b);break;case "MultiPolygon":h=e.W();h[f[1]][f[0]].splice(g+1,0,b);break;case "LineString":h=e.W();h.splice(g+1,0,b);break;default:return}this.s=!0;e.na(h);this.s=!1;h=this.a;h.remove(a);vx(this,e,g,f,1);a={ma:[c[0],b],feature:d,geometry:e,depth:f,index:g};h.Ca(Ca(a.ma),a);
+this.j.push([a,1]);b={ma:[b,c[1]],feature:d,geometry:e,depth:f,index:g+1};h.Ca(Ca(b.ma),b);this.j.push([b,0]);this.T=!0};
+k.Dj=function(){if(this.O&&"pointerdrag"!=this.O.type){var a=this.O;nx(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=x(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.W();var q=!1;switch(f.S()){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.s=!0,q.na(d),this.s=!1,d=[],void 0!==m&&(this.a.remove(m),d.push(m.ma[0])),void 0!==l&&(this.a.remove(l),d.push(l.ma[1])),void 0!==m&&void 0!==l&&(m={depth:g.depth,feature:g.feature,geometry:g.geometry,index:n,ma:d},this.a.Ca(Ca(m.ma),
+m)),vx(this,f,e,g.depth,-1),this.f&&(this.B.ha().Lb(this.f),this.f=null),b.length=0)}this.b(new ox("modifyend",this.o,a));this.C=!1;return!0}return!1};function vx(a,b,c,d,e){zm(a.a,b.G(),function(a){a.geometry===b&&(void 0===d||void 0===a.depth||jc(a.depth,d))&&a.index>c&&(a.index+=e)})}function lx(){var a=Gk();return function(){return a.Point}}function ox(a,b,c){Qc.call(this,a);this.features=b;this.mapBrowserEvent=c}w(ox,Qc);function wx(a){Jg.call(this,{handleEvent:xx});a=a?a:{};this.C=a.condition?a.condition:$g;this.D=a.addCondition?a.addCondition:Se;this.B=a.removeCondition?a.removeCondition:Se;this.T=a.toggleCondition?a.toggleCondition:bh;this.s=a.multi?a.multi:!1;this.l=a.filter?a.filter:Re;this.j=a.hitTolerance?a.hitTolerance:0;this.f=new T({source:new U({useSpatialIndex:!1,features:a.features,wrapX:a.wrapX}),style:a.style?a.style:yx(),updateWhileAnimating:!0,updateWhileInteracting:!0});if(a.layers)if("function"===
+typeof a.layers)a=a.layers;else{var b=a.layers;a=function(a){return ec(b,a)}}else a=Re;this.o=a;this.a={};a=this.f.ha().i;y(a,"add",this.ko,this);y(a,"remove",this.oo,this)}w(wx,Jg);k=wx.prototype;k.lo=function(){return this.f.ha().i};k.mo=function(){return this.j};k.no=function(a){a=x(a);return this.a[a]};
+function xx(a){if(!this.C(a))return!0;var b=this.D(a),c=this.B(a),d=this.T(a),e=!b&&!c&&!d,f=a.map,g=this.f.ha().i,h=[],l=[];if(e){lb(this.a);f.Tc(a.pixel,function(a,b){if(this.l(a,b))return l.push(a),a=x(a),this.a[a]=b,!this.s}.bind(this),{layerFilter:this.o,hitTolerance:this.j});for(e=g.kc()-1;0<=e;--e){f=g.item(e);var m=l.indexOf(f);-1<m?l.splice(m,1):(g.remove(f),h.push(f))}0!==l.length&&g.qg(l)}else{f.Tc(a.pixel,function(a,e){if(this.l(a,e))return!b&&!d||ec(g.a,a)?(c||d)&&ec(g.a,a)&&(h.push(a),
+e=x(a),delete this.a[e]):(l.push(a),a=x(a),this.a[a]=e),!this.s}.bind(this),{layerFilter:this.o,hitTolerance:this.j});for(e=h.length-1;0<=e;--e)g.remove(h[e]);g.qg(l)}(0<l.length||0<h.length)&&this.b(new zx(Ax,l,h,a));return Zg(a)}k.po=function(a){this.j=a};k.setMap=function(a){var b=this.v,c=this.f.ha().i;b&&c.forEach(b.Zj,b);Jg.prototype.setMap.call(this,a);this.f.setMap(a);a&&c.forEach(a.Uj,a)};
+function yx(){var a=Gk();gc(a.Polygon,a.LineString);gc(a.GeometryCollection,a.LineString);return function(b){return b.U()?a[b.U().S()]:null}}k.ko=function(a){var b=this.v;b&&b.Uj(a.element)};k.oo=function(a){var b=this.v;b&&b.Zj(a.element)};function zx(a,b,c,d){Qc.call(this,a);this.selected=b;this.deselected=c;this.mapBrowserEvent=d}w(zx,Qc);var Ax="select";function Bx(a){fh.call(this,{handleEvent:Cx,handleDownEvent:Re,handleUpEvent:Dx});a=a?a:{};this.s=a.source?a.source:null;this.O=void 0!==a.vertex?a.vertex:!0;this.C=void 0!==a.edge?a.edge:!0;this.j=a.features?a.features:null;this.ra=[];this.B={};this.V={};this.o={};this.T=null;this.f=void 0!==a.pixelTolerance?a.pixelTolerance:10;this.ua=Ex.bind(this);this.a=new um;this.ca={Point:this.wo,LineString:this.Pi,LinearRing:this.Pi,Polygon:this.xo,MultiPoint:this.uo,MultiLineString:this.to,MultiPolygon:this.vo,
+GeometryCollection:this.so,Circle:this.ro}}w(Bx,fh);k=Bx.prototype;k.Gb=function(a,b){b=void 0!==b?b:!0;var c=x(a),d=a.U();if(d){var e=this.ca[d.S()];e&&(this.V[c]=d.G(Da()),e.call(this,a,d))}b&&(this.B[c]=y(a,"change",this.qo,this))};k.bl=function(a){this.Gb(a)};k.cl=function(a){this.Lb(a)};k.Ni=function(a){if(a instanceof Bw)var b=a.feature;else a instanceof cd&&(b=a.element);this.Gb(b)};k.Oi=function(a){if(a instanceof Bw)var b=a.feature;else a instanceof cd&&(b=a.element);this.Lb(b)};
+k.qo=function(a){a=a.target;if(this.D){var b=x(a);b in this.o||(this.o[b]=a)}else this.$j(a)};k.Lb=function(a,b){b=void 0!==b?b:!0;var c=x(a),d=this.V[c];if(d){var e=this.a,f=[];zm(e,d,function(b){a===b.feature&&f.push(b)});for(d=f.length-1;0<=d;--d)e.remove(f[d])}b&&(Gc(this.B[c]),delete this.B[c])};
+k.setMap=function(a){var b=this.v,c=this.ra,d;this.j?d=this.j:this.s&&(d=this.s.ee());b&&(c.forEach(Gc),c.length=0,d.forEach(this.cl,this));fh.prototype.setMap.call(this,a);a&&(this.j?c.push(y(this.j,"add",this.Ni,this),y(this.j,"remove",this.Oi,this)):this.s&&c.push(y(this.s,"addfeature",this.Ni,this),y(this.s,"removefeature",this.Oi,this)),d.forEach(this.bl,this))};k.jd=Se;
+function Fx(a,b,c,d){var e=d.Ra([b[0]-a.f,b[1]+a.f]),f=d.Ra([b[0]+a.f,b[1]-a.f]);e=Ca([e,f]);var g=xm(a.a,e);a.O&&!a.C&&(g=g.filter(function(a){return"Circle"!==a.feature.U().S()}));var h=!1;e=!1;var l=f=null;if(0<g.length){a.T=c;g.sort(a.ua);var m=g[0].ma;h="Circle"===g[0].feature.U().S();if(a.O&&!a.C){if(c=d.Ia(m[0]),h=d.Ia(m[1]),c=He(b,c),b=He(b,h),h=Math.sqrt(Math.min(c,b)),h=h<=a.f)e=!0,f=c>b?m[1]:m[0],l=d.Ia(f)}else a.C&&(f=h?Ae(c,g[0].feature.U()):Be(c,m),l=d.Ia(f),Ie(b,l)<=a.f&&(e=!0,a.O&&
+!h&&(c=d.Ia(m[0]),h=d.Ia(m[1]),c=He(l,c),b=He(l,h),h=Math.sqrt(Math.min(c,b)),h=h<=a.f)))&&(f=c>b?m[1]:m[0],l=d.Ia(f));e&&(l=[Math.round(l[0]),Math.round(l[1])])}return{Mq:e,vertex:f,Vq:l}}k.$j=function(a){this.Lb(a,!1);this.Gb(a,!1)};k.ro=function(a,b){b=Sf(b).W()[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,ma:e};this.a.Ca(Ca(e),f)}};k.so=function(a,b){var c=b.a;for(b=0;b<c.length;++b){var d=this.ca[c[b].S()];d&&d.call(this,a,c[b])}};
+k.Pi=function(a,b){b=b.W();var c;var d=0;for(c=b.length-1;d<c;++d){var e=b.slice(d,d+2);var f={feature:a,ma:e};this.a.Ca(Ca(e),f)}};k.to=function(a,b){b=b.W();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,ma:h};this.a.Ca(Ca(h),l)}}};k.uo=function(a,b){var c=b.W(),d;var e=0;for(d=c.length;e<d;++e){var f=c[e];f={feature:a,ma:[f,f]};this.a.Ca(b.G(),f)}};
+k.vo=function(a,b){b=b.W();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,ma:n};this.a.Ca(Ca(n),p)}}}};k.wo=function(a,b){var c=b.W();a={feature:a,ma:[c,c]};this.a.Ca(b.G(),a)};k.xo=function(a,b){b=b.W();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,ma:h};this.a.Ca(Ca(h),l)}}};
+function Cx(a){var b=Fx(this,a.pixel,a.coordinate,a.map);b.Mq&&(a.coordinate=b.vertex.slice(0,2),a.pixel=b.Vq);return gh.call(this,a)}function Dx(){var a=mb(this.o);a.length&&(a.forEach(this.$j,this),this.o={});return!1}function Ex(a,b){return Je(this.T,a.ma)-Je(this.T,b.ma)};function Gx(a){fh.call(this,{handleDownEvent:Hx,handleDragEvent:Ix,handleMoveEvent:Jx,handleUpEvent:Kx});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 ec(c,a)}}else b=Re;this.C=b;this.s=a.hitTolerance?a.hitTolerance:0;this.f=null;y(this,Xc("active"),this.o,this)}w(Gx,fh);
+function Hx(a){this.f=Lx(this,a.pixel,a.map);if(!this.a&&this.f){this.a=a.coordinate;Jx.call(this,a);var b=this.j||new B([this.f]);this.b(new Mx("translatestart",b,a.coordinate));return!0}return!1}function Kx(a){if(this.a){this.a=null;Jx.call(this,a);var b=this.j||new B([this.f]);this.b(new Mx("translateend",b,a.coordinate));return!0}return!1}
+function Ix(a){if(this.a){a=a.coordinate;var b=a[0]-this.a[0],c=a[1]-this.a[1],d=this.j||new B([this.f]);d.forEach(function(a){var d=a.U();d.translate(b,c);a.Va(d)});this.a=a;this.b(new Mx("translating",d,a))}}function Jx(a){var b=a.map.a;Lx(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 Lx(a,b,c){return c.Tc(b,function(a){if(!this.j||ec(this.j.a,a))return a}.bind(a),{layerFilter:a.C,hitTolerance:a.s})}Gx.prototype.B=function(){return this.s};Gx.prototype.T=function(a){this.s=a};Gx.prototype.setMap=function(a){var b=this.v;fh.prototype.setMap.call(this,a);Nx(this,b)};Gx.prototype.o=function(){Nx(this,null)};function Nx(a,b){var c=a.v;a=a.c();c&&a||(c=c||b)&&c.a.classList.remove("ol-grab","ol-grabbing")}
+function Mx(a,b,c){Qc.call(this,a);this.features=b;this.coordinate=c}w(Mx,Qc);function V(a){a=a?a:{};var b=kb({},a);delete b.gradient;delete b.radius;delete b.blur;delete b.shadow;delete b.weight;T.call(this,b);this.c=null;this.$=void 0!==a.shadow?a.shadow:250;this.O=void 0;this.T=null;y(this,Xc(Ox),this.cm,this);this.Lj(a.gradient?a.gradient:Px);this.Fj(void 0!==a.blur?a.blur:15);this.fd(void 0!==a.radius?a.radius:8);y(this,Xc(Qx),this.mg,this);y(this,Xc(Rx),this.mg,this);this.mg();var c=a.weight?a.weight:"weight",d;"string"===typeof c?d=function(a){return a.get(c)}:d=c;this.j(function(a){a=
+d(a);a=void 0!==a?pa(a,0,1):1;var b=255*a|0,c=this.T[b];c||(c=[new Bk({image:new dr({opacity:a,src:this.O})})],this.T[b]=c);return c}.bind(this));this.set(ik,null);y(this,"render",this.tm,this)}w(V,T);var Px=["#00f","#0ff","#0f0","#ff0","#f00"];k=V.prototype;k.Nh=function(){return this.get(Qx)};k.Uh=function(){return this.get(Ox)};k.Ri=function(){return this.get(Rx)};
+k.cm=function(){for(var a=this.Uh(),b=hg(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.c=b.getImageData(0,0,1,256).data};k.mg=function(){var a=this.Ri(),b=this.Nh(),c=a+b+1,d=2*c;d=hg(d,d);d.shadowOffsetX=d.shadowOffsetY=this.$;d.shadowBlur=b;d.shadowColor="#000";d.beginPath();b=c-this.$;d.arc(b,b,a,0,2*Math.PI,!0);d.fill();this.O=d.canvas.toDataURL();this.T=Array(256);this.u()};
+k.tm=function(a){a=a.context;var b=a.canvas;b=a.getImageData(0,0,b.width,b.height);var 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.c[e],c[f+1]=this.c[e+1],c[f+2]=this.c[e+2];a.putImageData(b,0,0)};k.Fj=function(a){this.set(Qx,a)};k.Lj=function(a){this.set(Ox,a)};k.fd=function(a){this.set(Rx,a)};var Qx="blur",Ox="gradient",Rx="radius";function Sx(a){xg.call(this,a?a:{});this.type="IMAGE"}w(Sx,xg);function Tx(a){a=a?a:{};var b=kb({},a);delete b.preload;delete b.useInterimTilesOnError;xg.call(this,b);this.j(void 0!==a.preload?a.preload:0);this.C(void 0!==a.useInterimTilesOnError?a.useInterimTilesOnError:!0);this.type="TILE"}w(Tx,xg);Tx.prototype.c=function(){return this.get("preload")};Tx.prototype.j=function(a){this.set("preload",a)};Tx.prototype.i=function(){return this.get("useInterimTilesOnError")};Tx.prototype.C=function(a){this.set("useInterimTilesOnError",a)};function W(a){a=a?a:{};var b=a.renderMode||"hybrid";oa(void 0==b||"image"==b||"hybrid"==b||"vector"==b,28);a.declutter&&"image"==b&&(b="hybrid");a.renderMode=b;b=kb({},a);delete b.preload;delete b.useInterimTilesOnError;T.call(this,b);this.T(a.preload?a.preload:0);this.O(a.useInterimTilesOnError?a.useInterimTilesOnError:!0);this.type="VECTOR_TILE"}w(W,T);W.prototype.c=function(){return this.get("preload")};W.prototype.i=function(){return this.get("useInterimTilesOnError")};
+W.prototype.T=function(a){this.set("preload",a)};W.prototype.O=function(a){this.set("useInterimTilesOnError",a)};function Ux(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;oa(a,55);return(a.ka-a.ea+1+g[2]).toString()})}}function Vx(a,b){for(var c=a.length,d=Array(c),e=0;e<c;++e)d[e]=Ux(a[e],b);return Wx(d)}function Wx(a){return 1===a.length?a[0]:function(b,c,d){if(b)return a[wa((b[1]<<b[0])+b[2],a.length)](b,c,d)}}
+function Xx(){}function Yx(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 Zx(a,b,c,d){function e(){delete window[g];f.parentNode.removeChild(f)}var f=document.createElement("script"),g="olc_"+x(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 $x(a){ci.call(this,a)}w($x,ci);$x.prototype.sd=function(a){for(var b,c;di(this);){b=this.g.Pc;c=b.ya[0].toString();var d;if(d=c in a)b=b.ya,d=ma(a[c],b[1],b[2]);if(d)break;else Pc(this.pop())}};function ay(a){if(0!==a.i){var b=a.c.jc.split("/").map(Number)[0];a.forEach(function(a){if(a.ya[0]!==b){var c=a.ya;this.remove(c[0]+"/"+c[1]+"/"+c[2]);Pc(a)}},a)}};function by(a,b,c,d){var e=ac(c,b,a);c=Nb(b,d,c);b=b.Bc();void 0!==b&&(c*=b);b=a.Bc();void 0!==b&&(c/=b);b=a.G();if(!b||Ja(b,e))a=Nb(a,c,e)/c,isFinite(a)&&0<a&&(c/=a);return c}function cy(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 dy(a,b,c,d,e,f,g,h,l,m,n){var p=hg(Math.round(c*a),Math.round(c*b));if(0===l.length)return p.canvas;p.scale(c,c);var q=Da();l.forEach(function(a){Ta(q,a.extent)});var r=hg(Math.round(c*cb(q)/d),Math.round(c*db(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,cb(a.extent)*u,db(a.extent)*u)});var v=$a(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]-
+v[0])/f;var n=-(e[0][1]-v[1])/f,u=(e[1][0]-v[0])/f,z=-(e[1][1]-v[1])/f,Va=(e[2][0]-v[0])/f,ic=-(e[2][1]-v[1])/f;e=b[0][0];b=b[0][1];g-=e;h-=b;l-=e;m-=b;a:{g=[[g,h,0,0,u-a],[l,m,0,0,Va-a],[0,0,g,h,z-n],[0,0,l,m,ic-n]];h=g.length;for(l=0;l<h;l++){m=l;for(var Xa=Math.abs(g[l][l]),Z=l+1;Z<h;Z++){var Zb=Math.abs(g[Z][l]);Zb>Xa&&(Xa=Zb,m=Z)}if(0===Xa){g=null;break a}Xa=g[m];g[m]=g[l];g[l]=Xa;for(m=l+1;m<h;m++)for(Xa=-g[m][l]/g[l][l],Z=l;Z<h+1;Z++)g[m][Z]=l==Z?0:g[m][Z]+Xa*g[l][Z]}l=Array(h);for(m=h-1;0<=
+m;m--)for(l[m]=g[m][h]/g[m][m],Xa=m-1;0<=Xa;Xa--)g[Xa][h]-=g[Xa][m]*l[m];g=l}g&&(p.save(),p.beginPath(),l=(a+u+Va)/3,m=(n+z+ic)/3,h=cy(l,m,a,n),u=cy(l,m,u,z),Va=cy(l,m,Va,ic),p.moveTo(u[0],u[1]),p.lineTo(h[0],h[1]),p.lineTo(Va[0],Va[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]-v[0])/f;var c=-(b[0][1]-v[1])/f,d=
+(b[1][0]-v[0])/f,e=-(b[1][1]-v[1])/f,g=(b[2][0]-v[0])/f;b=-(b[2][1]-v[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 ey(a,b,c,d,e){this.g=a;this.i=b;var f={},g=Yb(this.i,this.g);this.a=function(a){var b=a[0]+"/"+a[1];f[b]||(f[b]=g(a));return f[b]};this.f=d;this.v=e*e;this.c=[];this.l=!1;this.s=this.g.g&&!!d&&!!this.g.G()&&cb(d)==cb(this.g.G());this.b=this.g.G()?cb(this.g.G()):null;this.j=this.i.G()?cb(this.i.G()):null;a=$a(c);b=Za(c);d=Ya(c);c=Wa(c);e=this.a(a);var h=this.a(b),l=this.a(d),m=this.a(c);fy(this,a,b,d,c,e,h,l,m,10);if(this.l){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<
+function fy(a,b,c,d,e,f,g,h,l,m){var n=Ca([f,g,h,l]),p=a.b?cb(n)/a.b:null,q=a.b,r=a.g.g&&.5<p&&1>p,u=!1;if(0<m){if(a.i.c&&a.j){var v=Ca([b,c,d,e]);u|=.25<cb(v)/a.j}!r&&a.g.c&&p&&(u|=.25<p)}if(u||!a.f||hb(n,a.f)){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?(wa(f[0],q)+wa(h[0],q))/2-wa(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),fy(a,b,c,r,n,f,g,q,p,m-1),fy(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),fy(a,b,r,n,e,f,q,p,l,m-1),fy(a,r,c,d,n,q,g,h,p,m-1));return}if(r){if(!a.s)return;a.l=!0}a.c.push({source:[f,h,l],target:[b,d,e]});a.c.push({source:[f,g,h],target:[b,c,d]})}}
+function gy(a){var b=Da();a.c.forEach(function(a){a=a.source;Ea(b,a[0]);Ea(b,a[1]);Ea(b,a[2])});return b};function hy(a,b,c,d,e,f,g,h,l,m,n){cl.call(this,e,0);this.B=void 0!==n?n:!1;this.C=g;this.D=h;this.N=null;this.c=b;this.l=d;this.v=f?f:e;this.a=[];this.Id=null;this.f=0;f=d.Ma(this.v);h=this.l.G();e=this.c.G();f=h?gb(f,h):f;if(0===ab(f))this.state=4;else if((h=a.G())&&(e?e=gb(e,h):e=h),d=by(a,c,eb(f),d.Ta(this.v[0])),!isFinite(d)||0>=d)this.state=4;else if(this.o=new ey(a,c,f,e,d*(void 0!==m?m:.5)),0===this.o.c.length)this.state=4;else if(this.f=b.Dc(d),c=gy(this.o),e&&(a.g?(c[1]=pa(c[1],e[1],e[3]),
+c[3]=pa(c[3],e[1],e[3])):c=gb(c,e)),ab(c)){a=tc(b,c,this.f);for(b=a.fa;b<=a.la;b++)for(c=a.ea;c<=a.ka;c++)(m=l(this.f,b,c,g))&&this.a.push(m);0===this.a.length&&(this.state=4)}else this.state=4}w(hy,cl);hy.prototype.ia=function(){1==this.state&&(this.Id.forEach(Gc),this.Id=null);cl.prototype.ia.call(this)};hy.prototype.Y=function(){return this.N};
+hy.prototype.he=function(){var a=[];this.a.forEach(function(b){b&&2==b.getState()&&a.push({extent:this.c.Ma(b.ya),image:b.Y()})},this);this.a.length=0;if(0===a.length)this.state=3;else{var b=this.v[0],c=this.l.Za(b),d="number"===typeof c?c:c[0];c="number"===typeof c?c:c[1];b=this.l.Ta(b);var e=this.c.Ta(this.f),f=this.l.Ma(this.v);this.N=dy(d,c,this.C,e,this.c.G(),b,f,this.o,a,this.D,this.B);this.state=2}this.u()};
+hy.prototype.load=function(){if(0==this.state){this.state=1;this.u();var a=0;this.Id=[];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)Gc(d),a--,0===a&&(this.Id.forEach(Gc),this.Id=null,this.he())},this);this.Id.push(d)}},this);this.a.forEach(function(a){0==a.getState()&&a.load()});0===a&&setTimeout(this.he.bind(this),0)}};function iy(a){uw.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state,wrapX:a.wrapX});this.bb=void 0!==a.opaque?a.opaque:!1;this.sc=void 0!==a.tilePixelRatio?a.tilePixelRatio:1;this.tileGrid=void 0!==a.tileGrid?a.tileGrid:null;this.a=new $x(a.cacheSize);this.j=[0,0];this.jc="";this.Ea={transition:a.transition}}w(iy,uw);k=iy.prototype;k.cj=function(){return di(this.a)};k.sd=function(a,b){(a=this.Yd(a))&&a.sd(b)};
+function Li(a,b,c,d,e){a=a.Yd(b);if(!a)return!1;b=!0;for(var f,g,h=d.fa;h<=d.la;++h)for(var l=d.ea;l<=d.ka;++l)f=c+"/"+h+"/"+l,g=!1,a.a.hasOwnProperty(f)&&(f=a.get(f),(g=2===f.getState())&&(g=!1!==e(f))),g||(b=!1);return b}k.Zf=function(){return 0};function jy(a,b){a.jc!==b&&(a.jc=b,a.u())}k.eg=function(){return this.bb};k.jb=function(){return this.tileGrid};k.eb=function(a){return this.tileGrid?this.tileGrid:zc(a)};k.Yd=function(a){var b=this.c;return b&&!Xb(b,a)?null:this.a};k.Xc=function(){return this.sc};
+k.Zd=function(a,b,c){c=this.eb(c);b=this.Xc(b);a=Ba(c.Za(a),this.j);return 1==b?a:Aa(a,b,this.j)};function ky(a,b,c){var d=void 0!==c?c:a.c;c=a.eb(d);if(a.D&&d.c){var e=b;b=e[0];a=yc(c,e);d=Dc(d);Ja(d,a)?b=e:(e=cb(d),a[0]+=e*Math.ceil((d[0]-a[0])/e),b=c.jg(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?tc(c,f,e):c.a?c.a[e]:null)?ma(c,d,a):!0}return c?b:null}k.sa=function(){this.a.clear();this.u()};k.kh=ea;function ly(a,b){Qc.call(this,a);this.tile=b}w(ly,Qc);function my(a){iy.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,transition:a.transition});this.tileLoadFunction=a.tileLoadFunction;this.tileUrlFunction=this.dc?this.dc.bind(this):Xx;this.urls=null;a.urls?this.vb(a.urls):a.url&&this.rb(a.url);a.tileUrlFunction&&this.hb(a.tileUrlFunction);this.V={}}w(my,iy);k=my.prototype;k.yb=function(){return this.tileLoadFunction};
+k.zb=function(){return this.tileUrlFunction};k.Ab=function(){return this.urls};k.dj=function(a){a=a.target;var b=x(a),c=a.getState();if(1==c){this.V[b]=!0;var d="tileloadstart"}else b in this.V&&(delete this.V[b],d=3==c?"tileloaderror":2==c||5==c?"tileloadend":void 0);void 0!=d&&this.b(new ly(d,a))};k.Fb=function(a){this.a.clear();this.tileLoadFunction=a;this.u()};k.hb=function(a,b){this.tileUrlFunction=a;ay(this.a);"undefined"!==typeof b?jy(this,b):this.u()};
+k.rb=function(a){var b=this.urls=Yx(a);this.hb(this.dc?this.dc.bind(this):Vx(b,this.tileGrid),a)};k.vb=function(a){this.urls=a;var b=a.join("\n");this.hb(this.dc?this.dc.bind(this):Vx(a,this.tileGrid),b)};k.kh=function(a,b,c){a=a+"/"+b+"/"+c;this.a.a.hasOwnProperty(a)&&this.a.get(a)};function ny(a){my.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:oy,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:a.tileUrlFunction,url:a.url,urls:a.urls,wrapX:a.wrapX,transition:a.transition});this.crossOrigin=void 0!==a.crossOrigin?a.crossOrigin:null;this.tileClass=void 0!==a.tileClass?a.tileClass:el;this.f={};this.s={};this.ob=
+a.reprojectionErrorThreshold;this.O=!1}w(ny,my);k=ny.prototype;k.cj=function(){if(di(this.a))return!0;for(var a in this.f)if(di(this.f[a]))return!0;return!1};k.sd=function(a,b){a=this.Yd(a);this.a.sd(this.a==a?b:{});for(var c in this.f){var d=this.f[c];d.sd(d==a?b:{})}};k.Zf=function(a){return this.c&&a&&!Xb(this.c,a)?0:this.$f()};k.$f=function(){return 0};k.eg=function(a){return this.c&&a&&!Xb(this.c,a)?!1:my.prototype.eg.call(this,a)};
+k.eb=function(a){var b=this.c;return!this.tileGrid||b&&!Xb(b,a)?(b=x(a).toString(),b in this.s||(this.s[b]=zc(a)),this.s[b]):this.tileGrid};k.Yd=function(a){var b=this.c;if(!b||Xb(b,a))return this.a;a=x(a).toString();a in this.f||(this.f[a]=new $x(this.a.highWaterMark));return this.f[a]};
+function py(a,b,c,d,e,f,g){b=[b,c,d];e=(c=ky(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,a.Ea);e.key=g;y(e,"change",a.dj,a);return e}
+k.ad=function(a,b,c,d,e){var f=this.c;if(f&&e&&!Xb(f,e)){var g=this.Yd(e);c=[a,b,c];var h;a=c[0]+"/"+c[1]+"/"+c[2];g.a.hasOwnProperty(a)&&(h=g.get(a));b=this.jc;if(h&&h.key==b)return h;var l=this.eb(f),m=this.eb(e),n=ky(this,c,e);d=new hy(f,l,e,m,c,n,this.Xc(d),this.$f(),function(a,b,c,d){return qy(this,a,b,c,d,f)}.bind(this),this.ob,this.O);d.key=b;h?(d.g=h,dl(d),g.replace(a,d)):g.set(a,d);return d}return qy(this,a,b,c,d,f||e)};
+function qy(a,b,c,d,e,f){var g=b+"/"+c+"/"+d,h=a.jc;if(a.a.a.hasOwnProperty(g)){var l=a.a.get(g);if(l.key!=h){var m=l;l=py(a,b,c,d,e,f,h);0==m.getState()?l.g=m.g:l.g=m;dl(l);a.a.replace(g,l)}}else l=py(a,b,c,d,e,f,h),a.a.set(g,l);return l}k.Qb=function(a){if(this.O!=a){this.O=a;for(var b in this.f)this.f[b].clear();this.u()}};k.Rb=function(a,b){if(a=Ob(a))a=x(a).toString(),a in this.s||(this.s[a]=b)};function oy(a,b){a.Y().src=b};function ry(a){this.i=void 0!==a.hidpi?a.hidpi:!1;ny.call(this,{cacheSize:a.cacheSize,crossOrigin:"anonymous",opaque:!0,projection:Ob("EPSG:3857"),reprojectionErrorThreshold:a.reprojectionErrorThreshold,state:"loading",tileLoadFunction:a.tileLoadFunction,tilePixelRatio:this.i?2:1,wrapX:void 0!==a.wrapX?a.wrapX:!0,transition:a.transition});this.o=void 0!==a.culture?a.culture:"en-us";this.$=void 0!==a.maxZoom?a.maxZoom:-1;this.l=a.key;this.B=a.imagerySet;Zx("https://dev.virtualearth.net/REST/v1/Imagery/Metadata/"+
+this.B+"?uriScheme=https&include=ImageryProviders&key="+this.l+"&c="+this.o,this.La.bind(this),void 0,"jsonp")}w(ry,ny);ry.prototype.ca=function(){return this.l};ry.prototype.ua=function(){return this.B};
+ry.prototype.La=function(a){if(200!=a.statusCode||"OK"!=a.statusDescription||"ValidCredentials"!=a.authenticationResultCode||1!=a.resourceSets.length||1!=a.resourceSets[0].resources.length)ww(this,"error");else{var b=a.brandLogoUri;-1==b.indexOf("https")&&(b=b.replace("http","https"));var c=a.resourceSets[0].resources[0];a=-1==this.$?c.zoomMax:this.$;var d=Dc(this.c);this.tileGrid=Bc({extent:d,minZoom:c.zoomMin,maxZoom:a,tileSize:(c.imageWidth==c.imageHeight?c.imageWidth:[c.imageWidth,c.imageHeight])/
+(this.i?2:1)});var e=this.o,f=this.i;this.tileUrlFunction=Wx(c.imageUrlSubdomains.map(function(a){var b=[0,0,0],d=c.imageUrl.replace("{subdomain}",a).replace("{culture}",e);return function(a){if(a)return oc(a[0],a[1],-a[2]-1,b),a=d,f&&(a+="&dpi=d1&device=mobile"),a.replace("{quadkey}",pc(b))}}));if(c.imageryProviders){var g=Pb(Ob("EPSG:4326"),this.c);this.va(function(a){var b=[],d=a.viewState.zoom;c.imageryProviders.map(function(c){for(var e=!1,f=c.coverageAreas,h=0,l=f.length;h<l;++h){var m=f[h];
+if(d>=m.zoomMin&&d<=m.zoomMax&&(m=m.bbox,m=jb([m[1],m[0],m[3],m[2]],g),hb(m,a.extent))){e=!0;break}}e&&b.push(c.attribution)});b.push('<a class="ol-attribution-bing-tos" href="https://www.microsoft.com/maps/product/terms.html">Terms of Use</a>');return b})}this.T=b;ww(this,"ready")}};function sy(a){a=a||{};var b=void 0!==a.projection?a.projection:"EPSG:3857",c=void 0!==a.tileGrid?a.tileGrid:Bc({extent:Dc(b),maxZoom:a.maxZoom,minZoom:a.minZoom,tileSize:a.tileSize});ny.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,transition:a.transition})}w(sy,ny);function ty(a){this.o=a.account;this.B=a.map||"";this.i=a.config||{};this.l={};sy.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});uy(this)}w(ty,sy);k=ty.prototype;k.nl=function(){return this.i};k.Sq=function(a){kb(this.i,a);uy(this)};k.uq=function(a){this.i=a||{};uy(this)};
+function uy(a){var b=JSON.stringify(a.i);if(a.l[b])vy(a,a.l[b]);else{var c="https://"+a.o+".carto.com/api/v1/map";a.B&&(c+="/named/"+a.B);var d=new XMLHttpRequest;d.addEventListener("load",a.em.bind(a,b));d.addEventListener("error",a.dm.bind(a));d.open("POST",c);d.setRequestHeader("Content-type","application/json");d.send(JSON.stringify(a.i))}}
+k.em=function(a,b){b=b.target;if(!b.status||200<=b.status&&300>b.status){try{var c=JSON.parse(b.responseText)}catch(d){ww(this,"error");return}vy(this,c);this.l[a]=c;ww(this,"ready")}else ww(this,"error")};k.dm=function(){ww(this,"error")};function vy(a,b){a.rb("https://"+b.cdn_url.https+"/"+a.o+"/api/v1/map/"+b.layergroupid+"/{z}/{x}/{y}.png")};function X(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.U();oa(a instanceof C,10);return a};this.source=a.source;this.source.I("change",X.prototype.sa,this)}w(X,U);k=X.prototype;k.Eo=function(){return this.distance};k.Fo=function(){return this.source};
+k.ae=function(a,b,c){this.source.ae(a,b,c);b!==this.resolution&&(this.clear(),this.resolution=b,wy(this),this.Qc(this.features))};k.vq=function(a){this.distance=a;this.sa()};k.sa=function(){this.clear();wy(this);this.Qc(this.features);U.prototype.sa.call(this)};
+function wy(a){if(void 0!==a.resolution){a.features.length=0;for(var b=Da(),c=a.distance*a.resolution,d=a.source.ee(),e={},f=0,g=d.length;f<g;f++){var h=d[f];x(h).toString()in e||!(h=a.geometryFunction(h))||(h=h.W(),Pa(h,b),Fa(b,c,b),h=a.source.Yf(b),h=h.filter(function(a){a=x(a).toString();return a in e?!1:e[a]=!0}),a.features.push(xy(a,h)))}}}
+function xy(a,b){for(var c=[0,0],d=b.length-1;0<=d;--d){var e=a.geometryFunction(b[d]);e?ze(c,e.W()):b.splice(d,1)}Ge(c,1/b.length);a=new Hk(new C(c));a.set("features",b);return a};function yy(a,b,c,d,e,f){this.s=b;this.l=a.G();var g=b.G(),h=g?gb(c,g):c;g=by(a,b,eb(h),d);this.f=new ey(a,b,h,this.l,.5*g);this.c=d;this.g=c;a=gy(this.f);this.j=(this.Tb=f(a,g,e))?this.Tb.a:1;this.je=this.i=null;e=2;this.Tb&&(e=0);$h.call(this,c,d,this.j,e)}w(yy,$h);yy.prototype.ia=function(){1==this.state&&(Gc(this.je),this.je=null);$h.prototype.ia.call(this)};yy.prototype.Y=function(){return this.i};
+yy.prototype.he=function(){var a=this.Tb.getState();2==a&&(this.i=dy(cb(this.g)/this.c,db(this.g)/this.c,this.j,this.Tb.resolution,0,this.c,this.g,this.f,[{extent:this.Tb.G(),image:this.Tb.Y()}],0));this.state=a;this.u()};yy.prototype.load=function(){if(0==this.state){this.state=1;this.u();var a=this.Tb.getState();2==a||3==a?this.he():(this.je=y(this.Tb,"change",function(){var a=this.Tb.getState();if(2==a||3==a)Gc(this.je),this.je=null,this.he()},this),this.Tb.load())}};function zy(a){uw.call(this,{attributions:a.attributions,extent:a.extent,logo:a.logo,projection:a.projection,state:a.state});this.o=void 0!==a.resolutions?a.resolutions:null;this.i=null;this.ua=0}w(zy,uw);function Ay(a,b){a.o&&(b=a.o[fc(a.o,b,0)]);return b}
+zy.prototype.Y=function(a,b,c,d){var e=this.c;if(e&&d&&!Xb(e,d)){if(this.i){if(this.ua==this.g&&Xb(this.i.s,d)&&this.i.resolution==b&&Sa(this.i.G(),a))return this.i;Pc(this.i);this.i=null}this.i=new yy(e,d,a,b,c,function(a,b,c){return this.Wc(a,b,c,e)}.bind(this));this.ua=this.g;return this.i}e&&(d=e);return this.Wc(a,b,c,d)};zy.prototype.j=function(a){a=a.target;switch(a.getState()){case 1:this.b(new By(Cy,a));break;case 2:this.b(new By(Dy,a));break;case 3:this.b(new By(Ey,a))}};
+function Fy(a,b){a.Y().src=b}function By(a,b){Qc.call(this,a);this.image=b}w(By,Qc);var Cy="imageloadstart",Dy="imageloadend",Ey="imageloaderror";function Gy(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 Hy(a){a=a||{};zy.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions});this.V=void 0!==a.crossOrigin?a.crossOrigin:null;this.$=void 0!==a.hidpi?a.hidpi:!0;this.a=a.url;this.f=void 0!==a.imageLoadFunction?a.imageLoadFunction:Fy;this.s=a.params||{};this.M=null;this.l=[0,0];this.O=0;this.B=void 0!==a.ratio?a.ratio:1.5}w(Hy,zy);k=Hy.prototype;k.Ho=function(){return this.s};
+k.Wc=function(a,b,c,d){if(void 0===this.a)return null;b=Ay(this,b);c=this.$?c:1;var e=this.M;if(e&&this.O==this.g&&e.resolution==b&&e.a==c&&La(e.G(),a))return e;e={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};kb(e,this.s);a=a.slice();var f=(a[0]+a[2])/2,g=(a[1]+a[3])/2;if(1!=this.B){var h=this.B*cb(a)/2,l=this.B*db(a)/2;a[0]=f-h;a[1]=g-l;a[2]=f+h;a[3]=g+l}h=b/c;l=Math.ceil(cb(a)/h);var m=Math.ceil(db(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.wb.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.a;f=d.replace(/MapServer\/?$/,"MapServer/export").replace(/ImageServer\/?$/,"ImageServer/exportImage");f==d&&oa(!1,50);e=Gy(f,e);this.M=new bl(a,b,c,e,this.V,this.f);this.O=this.g;y(this.M,"change",this.j,this);return this.M};k.Go=function(){return this.f};k.Io=function(){return this.a};k.Jo=function(a){this.M=null;this.f=a;this.u()};
+k.Ko=function(a){a!=this.a&&(this.a=a,this.M=null,this.u())};k.Lo=function(a){kb(this.s,a);this.M=null;this.u()};function Iy(a){zy.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions,state:a.state});this.Ea=a.canvasFunction;this.V=null;this.$=0;this.La=void 0!==a.ratio?a.ratio:1.5}w(Iy,zy);Iy.prototype.Wc=function(a,b,c,d){b=Ay(this,b);var e=this.V;if(e&&this.$==this.g&&e.resolution==b&&e.a==c&&La(e.G(),a))return e;a=a.slice();ib(a,this.La);(d=this.Ea(a,b,c,[cb(a)/b*c,db(a)/b*c],d))&&(e=new ai(a,b,c,d));this.V=e;this.$=this.g;return e};function Jy(a){zy.call(this,{projection:a.projection,resolutions:a.resolutions});this.V=void 0!==a.crossOrigin?a.crossOrigin:null;this.l=void 0!==a.displayDpi?a.displayDpi:96;this.f=a.params||{};this.O=a.url;this.a=void 0!==a.imageLoadFunction?a.imageLoadFunction:Fy;this.$=void 0!==a.hidpi?a.hidpi:!0;this.ca=void 0!==a.metersPerUnit?a.metersPerUnit:1;this.s=void 0!==a.ratio?a.ratio:1;this.Ea=void 0!==a.useOverlay?a.useOverlay:!1;this.M=null;this.B=0}w(Jy,zy);k=Jy.prototype;k.No=function(){return this.f};
+k.Wc=function(a,b,c){b=Ay(this,b);c=this.$?c:1;var d=this.M;if(d&&this.B==this.g&&d.resolution==b&&d.a==c&&La(d.G(),a))return d;1!=this.s&&(a=a.slice(),ib(a,this.s));var e=[cb(a)/b*c,db(a)/b*c];if(void 0!==this.O){d=this.O;var f=eb(a),g=this.ca,h=cb(a),l=db(a),m=e[0],n=e[1],p=.0254/this.l;e={OPERATION:this.Ea?"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]};kb(e,this.f);d=Gy(d,e);d=new bl(a,b,c,d,this.V,this.a);y(d,"change",this.j,this)}else d=null;this.M=d;this.B=this.g;return d};k.Mo=function(){return this.a};k.Po=function(a){kb(this.f,a);this.u()};k.Oo=function(a){this.M=null;this.a=a;this.u()};function Ky(a){var b=a.imageExtent,c=void 0!==a.crossOrigin?a.crossOrigin:null,d=void 0!==a.imageLoadFunction?a.imageLoadFunction:Fy;zy.call(this,{attributions:a.attributions,logo:a.logo,projection:Ob(a.projection)});this.M=new bl(b,void 0,1,a.url,c,d);this.a=a.imageSize?a.imageSize:null;y(this.M,"change",this.j,this)}w(Ky,zy);Ky.prototype.Wc=function(a){return hb(a,this.M.G())?this.M:null};
+Ky.prototype.j=function(a){if(2==this.M.getState()){var b=this.M.G(),c=this.M.Y();if(this.a){var d=this.a[0];var e=this.a[1]}else d=c.width,e=c.height;b=Math.ceil(cb(b)/(db(b)/e));if(b!=d){b=hg(b,e);var f=b.canvas;b.drawImage(c,0,0,d,e,0,0,f.width,f.height);this.M.ih(f)}}zy.prototype.j.call(this,a)};function Ly(a){this.a=a.source;this.ob=We();this.f=hg();this.l=[0,0];this.ca=rj.Jc(9);this.bb=void 0==a.renderBuffer?100:a.renderBuffer;this.B=null;Iy.call(this,{attributions:a.attributions,canvasFunction:this.Mk.bind(this),logo:a.logo,projection:a.projection,ratio:a.ratio,resolutions:a.resolutions,state:this.a.getState()});this.O=null;this.s=void 0;this.aj(a.style);y(this.a,"change",this.To,this)}w(Ly,Iy);k=Ly.prototype;
+k.Mk=function(a,b,c,d,e){var f=new Vj(.5*b/c,a,b,c,this.a.$,this.ca,this.bb);this.a.ae(a,b,e);var g=!1;this.a.ec(a,function(a){var d;if(!(d=g)){var e;(d=a.ib())?e=d.call(a,b):this.s&&(e=this.s(a,b));if(e){var h,p=!1;Array.isArray(e)||(e=[e]);d=0;for(h=e.length;d<h;++d)p=ek(f,a,e[d],dk(b,c),this.So,this)||p;d=p}else d=!1}g=d},this);Zj(f);if(g)return null;this.l[0]!=d[0]||this.l[1]!=d[1]?(this.f.canvas.width=d[0],this.f.canvas.height=d[1],this.l[0]=d[0],this.l[1]=d[1]):this.f.clearRect(0,0,d[0],d[1]);
+this.ca.clear();a=My(this,eb(a),b,c,d);f.Na(this.f,a,0,{});this.B=f;return this.f.canvas};k.wa=function(a,b,c,d,e,f){if(this.B){var g={};return this.B.wa(a,b,0,d,e,function(a){var b=x(a).toString();if(!(b in g))return g[b]=!0,f(a)},null)}};k.Qo=function(){return this.a};k.Ro=function(){return this.O};k.ib=function(){return this.s};function My(a,b,c,d,e){c=d/c;return ef(a.ob,e[0]/2,e[1]/2,c,-c,0,-b[0],-b[1])}k.So=function(){this.u()};k.To=function(){ww(this,this.a.getState())};
+k.aj=function(a){this.O=void 0!==a?a:Fk;this.s=a?Dk(this.O):void 0;this.u()};function Ny(a){a=a||{};zy.call(this,{attributions:a.attributions,logo:a.logo,projection:a.projection,resolutions:a.resolutions});this.ca=void 0!==a.crossOrigin?a.crossOrigin:null;this.f=a.url;this.s=void 0!==a.imageLoadFunction?a.imageLoadFunction:Fy;this.a=a.params||{};this.l=!0;Oy(this);this.$=a.serverType;this.Ea=void 0!==a.hidpi?a.hidpi:!0;this.M=null;this.B=[0,0];this.V=0;this.O=void 0!==a.ratio?a.ratio:1.5}w(Ny,zy);var Py=[101,101];k=Ny.prototype;
+k.Uo=function(a,b,c,d){if(void 0!==this.f){c=Ob(c);var e=this.c;e&&e!==c&&(b=by(e,c,a,b),a=ac(a,c,e));var f=fb(a,b,0,Py),g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.a.LAYERS};kb(g,this.a,d);d=Math.floor((f[3]-a[1])/b);g[this.l?"I":"X"]=Math.floor((a[0]-f[0])/b);g[this.l?"J":"Y"]=d;return Qy(this,f,Py,1,e||c,g)}};k.Wo=function(){return this.a};
+k.Wc=function(a,b,c,d){if(void 0===this.f)return null;b=Ay(this,b);1==c||this.Ea&&void 0!==this.$||(c=1);var e=b/c,f=eb(a),g=fb(f,e,0,[Math.ceil(cb(a)/e),Math.ceil(db(a)/e)]);a=fb(f,e,0,[Math.ceil(this.O*cb(a)/e),Math.ceil(this.O*db(a)/e)]);if((f=this.M)&&this.V==this.g&&f.resolution==b&&f.a==c&&La(f.G(),g))return f;g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};kb(g,this.a);this.B[0]=Math.round(cb(a)/e);this.B[1]=Math.round(db(a)/e);d=Qy(this,a,this.B,c,d,g);
+this.M=new bl(a,b,c,d,this.ca,this.s);this.V=this.g;y(this.M,"change",this.j,this);return this.M};k.Vo=function(){return this.s};
+function Qy(a,b,c,d,e,f){oa(void 0!==a.f,9);f[a.l?"CRS":"SRS"]=e.wb;"STYLES"in a.a||(f.STYLES="");if(1!=d)switch(a.$){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:oa(!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 Gy(a.f,f)}k.Xo=function(){return this.f};
+k.Yo=function(a){this.M=null;this.s=a;this.u()};k.Zo=function(a){a!=this.f&&(this.f=a,this.M=null,this.u())};k.$o=function(a){kb(this.a,a);Oy(this);this.M=null;this.u()};function Oy(a){a.l=0<=ye(a.a.VERSION||"1.3.0")};function Ry(a){a=a||{};var b;void 0!==a.attributions?b=a.attributions:b=['&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'];sy.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})}w(Ry,sy);rj.nf={};rj.nf.Hf=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 S=f,Ia=g;n=b?new ImageData(n,S,Ia):{data:n,width:S,height:Ia};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;
+"  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.Jf=!!a.Cm;var b;0===a.threads?b=0:this.Jf?b=1:b=a.threads||1;var c=[];if(b)for(var f=0;f<b;++f)c[f]=d(a,this.xh.bind(this,f));else c[0]=e(a,this.xh.bind(this,
+0));this.we=c;this.Nd=[];this.Bk=a.Sp||Infinity;this.ve=0;this.od={};this.Kf=null}var g=!0;try{new ImageData(10,10)}catch(l){g=!1}var h=document.createElement("canvas").getContext("2d");f.prototype.Rp=function(a,b,c){this.zk({inputs:a,ii:b,callback:c});this.uh()};f.prototype.zk=function(a){for(this.Nd.push(a);this.Nd.length>this.Bk;)this.Nd.shift().callback(null,null)};f.prototype.uh=function(){if(0===this.ve&&0<this.Nd.length){var a=this.Kf=this.Nd.shift(),b=a.inputs[0].width,c=a.inputs[0].height,
+d=a.inputs.map(function(a){return a.data.buffer}),e=this.we.length;this.ve=e;if(1===e)this.we[0].postMessage({buffers:d,meta:a.ii,imageOps:this.Jf,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,z=[],A=0,E=d.length;A<E;++A)z.push(d[g].slice(h,h+f));this.we[g].postMessage({buffers:z,meta:a.ii,imageOps:this.Jf,width:b,height:c},z)}}};f.prototype.xh=function(a,b){this.hr||(this.od[a]=b.data,--this.ve,0===this.ve&&this.Ck())};f.prototype.Ck=function(){var a=
+this.Kf,c=this.we.length;if(1===c){var d=new Uint8ClampedArray(this.od[0].buffer);var e=this.od[0].meta}else{var f=a.inputs[0].data.length;d=new Uint8ClampedArray(f);e=Array(f);f=4*Math.ceil(f/4/c);for(var g=0;g<c;++g){var h=g*f;d.set(new Uint8ClampedArray(this.od[g].buffer),h);e[g]=this.od[g].meta}}this.Kf=null;this.od={};a.callback(null,b(d,a.inputs[0].width,a.inputs[0].height),e);this.uh()};a["default"]={Hf:f};a.Hf=f})(rj.nf=rj.nf||{});function Sy(a){this.B=null;this.Ea=void 0!==a.operationType?a.operationType:"pixel";this.La=void 0!==a.threads?a.threads:1;this.f=Ty(a.sources);for(var b=0,c=this.f.length;b<c;++b)y(this.f[b],"change",this.u,this);this.$=new le(function(){return 1},this.u.bind(this));b=Uy(this.f);c={};for(var d=0,e=b.length;d<e;++d)c[x(b[d].layer)]=b[d];this.a=null;this.O={animate:!1,coordinateToPixelTransform:We(),extent:null,focus:null,index:0,layerStates:c,layerStatesArray:b,logos:{},pixelRatio:1,pixelToCoordinateTransform:We(),
+postRenderFunctions:[],size:[0,0],skippedFeatureUids:{},tileQueue:this.$,time:Date.now(),usedTiles:{},viewState:{rotation:0},viewHints:[],wantedTiles:{}};zy.call(this,{});void 0!==a.operation&&this.s(a.operation,a.lib)}w(Sy,zy);Sy.prototype.s=function(a,b){this.B=new rj.nf.Hf({operation:a,Cm:"image"===this.Ea,Sp:1,lib:b,threads:this.La});this.u()};
+Sy.prototype.Y=function(a,b,c,d){c=!0;for(var e,f=0,g=this.f.length;f<g;++f)if(e=this.f[f].a.ha(),"ready"!==e.getState()){c=!1;break}if(!c)return null;c=kb({},this.O);c.viewState=kb({},c.viewState);e=eb(a);c.extent=a.slice();c.focus=e;c.size[0]=Math.round(cb(a)/b);c.size[1]=Math.round(db(a)/b);c.time=Date.now();c.animate=!1;f=c.viewState;f.center=e;f.projection=d;f.resolution=b;this.l=c;this.a&&(d=this.a.resolution,e=this.a.G(),b===d&&Sa(a,e)||(this.a=null));if(!this.a||this.g!==this.V)a:{a=this.l;
+d=this.f.length;b=Array(d);for(e=0;e<d;++e){f=this.f[e];g=a;var h=a.layerStatesArray[e];if(f.$c(g,h)){var l=g.size[0],m=g.size[1];if(Vy){var n=Vy.canvas;n.width!==l||n.height!==m?Vy=hg(l,m):Vy.clearRect(0,0,l,m)}else Vy=hg(l,m);f.df(g,h,Vy);f=Vy.getImageData(0,0,l,m)}else f=null;if(f)b[e]=f;else break a}d={};this.b(new Wy(Xy,a,d));this.B.Rp(b,d,this.ca.bind(this,a))}me(c.tileQueue,16,16);c.animate&&requestAnimationFrame(this.u.bind(this));return this.a};
+Sy.prototype.ca=function(a,b,c,d){if(!b&&c){b=a.extent;var e=a.viewState.resolution;if(e===this.l.viewState.resolution&&Sa(b,this.l.extent)){if(this.a)var f=this.a.Y().getContext("2d");else f=hg(Math.round(cb(b)/e),Math.round(db(b)/e)),this.a=new ai(b,e,1,f.canvas);f.putImageData(c,0,0);this.u();this.V=this.g;this.b(new Wy(Yy,a,d))}}};var Vy=null;function Uy(a){return a.map(function(a){return lg(a.a)})}
+function Ty(a){for(var b=a.length,c=Array(b),d=0;d<b;++d){var e=d,f=a[d],g=null;f instanceof iy?(f=new Tx({source:f}),g=new mj(f)):f instanceof zy&&(f=new Sx({source:f}),g=new bj(f));c[e]=g}return c}function Wy(a,b,c){Qc.call(this,a);this.extent=b.extent;this.resolution=b.viewState.resolution/b.pixelRatio;this.data=c}w(Wy,Qc);Sy.prototype.Wc=function(){return null};var Xy="beforeoperations",Yy="afteroperations";function Zy(a){var b=a.layer.indexOf("-");b=$y[-1==b?a.layer:a.layer.slice(0,b)];var c=az[a.layer];sy.call(this,{attributions:bz,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.Ob,wrapX:a.wrapX})}w(Zy,sy);
+var bz=['Map tiles by <a href="https://stamen.com/">Stamen Design</a>, under <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.','&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'],az={terrain:{Ob:"jpg",opaque:!0},"terrain-background":{Ob:"jpg",opaque:!0},"terrain-labels":{Ob:"png",opaque:!1},"terrain-lines":{Ob:"png",opaque:!1},"toner-background":{Ob:"png",opaque:!0},toner:{Ob:"png",opaque:!0},"toner-hybrid":{Ob:"png",opaque:!1},"toner-labels":{Ob:"png",
+opaque:!1},"toner-lines":{Ob:"png",opaque:!1},"toner-lite":{Ob:"png",opaque:!0},watercolor:{Ob:"jpg",opaque:!0}},$y={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:1,maxZoom:16}};function cz(a){a=a||{};ny.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,transition:a.transition});this.i=a.params||{};this.l=Da();jy(this,dz(this))}w(cz,ny);function dz(a){var b=0,c=[],d;for(d in a.i)c[b++]=d+"-"+a.i[d];return c.join("/")}cz.prototype.o=function(){return this.i};
+cz.prototype.Xc=function(a){return a};
+cz.prototype.dc=function(a,b,c){var d=this.tileGrid;d||(d=this.eb(c));if(!(d.b.length<=a[0])){var e=d.Ma(a,this.l),f=Ba(d.Za(a[0]),this.j);1!=b&&(f=Aa(f,b,this.j));d={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};kb(d,this.i);var g=this.urls;g?(c=c.wb.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[wa((a[1]<<a[0])+a[2],g.length)]).replace(/MapServer\/?$/,"MapServer/export").replace(/ImageServer\/?$/,"ImageServer/exportImage"),
+a=Gy(a,d)):a=void 0;return a}};cz.prototype.B=function(a){kb(this.i,a);jy(this,dz(this))};function ez(a){iy.call(this,{opaque:!1,projection:a.projection,tileGrid:a.tileGrid,wrapX:void 0!==a.wrapX?a.wrapX:!0})}w(ez,iy);ez.prototype.ad=function(a,b,c){var d=a+"/"+b+"/"+c;if(this.a.a.hasOwnProperty(d))return this.a.get(d);var e=Ba(this.tileGrid.Za(a));a=[a,b,c];b=(b=ky(this,a))?ky(this,b).toString():"";e=new fz(a,e,b);this.a.set(d,e);return e};function fz(a,b,c){cl.call(this,a,2);this.c=b;this.ta=c;this.a=null}w(fz,cl);
+fz.prototype.Y=function(){if(this.a)return this.a;var a=this.c,b=hg(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.ta,a[0]/2,a[1]/2);return this.a=b.canvas};fz.prototype.load=function(){};function gz(a){this.i=null;ny.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,projection:Ob("EPSG:3857"),reprojectionErrorThreshold:a.reprojectionErrorThreshold,state:"loading",tileLoadFunction:a.tileLoadFunction,wrapX:void 0!==a.wrapX?a.wrapX:!0,transition:a.transition});if(a.url)if(a.jsonp)Zx(a.url,this.Cg.bind(this),this.ef.bind(this));else{var b=new XMLHttpRequest;b.addEventListener("load",this.bp.bind(this));b.addEventListener("error",this.ap.bind(this));
+b.open("GET",a.url);b.send()}else a.tileJSON?this.Cg(a.tileJSON):oa(!1,51)}w(gz,ny);k=gz.prototype;k.bp=function(a){a=a.target;if(!a.status||200<=a.status&&300>a.status){try{var b=JSON.parse(a.responseText)}catch(c){this.ef();return}this.Cg(b)}else this.ef()};k.ap=function(){this.ef()};k.Sl=function(){return this.i};
+k.Cg=function(a){var b=Ob("EPSG:4326"),c=this.c;if(void 0!==a.bounds){var d=Pb(b,c);d=jb(a.bounds,d)}var e=a.minzoom||0,f=a.maxzoom||22;this.tileGrid=c=Bc({extent:Dc(c),maxZoom:f,minZoom:e});this.tileUrlFunction=Vx(a.tiles,c);if(void 0!==a.attribution&&!this.C){var g=void 0!==d?d:b.G();this.va(function(b){return hb(g,b.extent)?[a.attribution]:null})}this.i=a;ww(this,"ready")};k.ef=function(){ww(this,"error")};function hz(a){iy.call(this,{projection:Ob("EPSG:3857"),state:"loading"});this.s=void 0!==a.preemptive?a.preemptive:!0;this.l=Xx;this.f=void 0;this.i=a.jsonp||!1;if(a.url)if(this.i)Zx(a.url,this.Dg.bind(this),this.ff.bind(this));else{var b=new XMLHttpRequest;b.addEventListener("load",this.gp.bind(this));b.addEventListener("error",this.fp.bind(this));b.open("GET",a.url);b.send()}else a.tileJSON?this.Dg(a.tileJSON):oa(!1,51)}w(hz,iy);k=hz.prototype;
+k.gp=function(a){a=a.target;if(!a.status||200<=a.status&&300>a.status){try{var b=JSON.parse(a.responseText)}catch(c){this.ff();return}this.Dg(b)}else this.ff()};k.fp=function(){this.ff()};k.Pl=function(){return this.f};k.al=function(a,b,c,d,e){this.tileGrid?(b=this.tileGrid.Le(a,b),iz(this.ad(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.ff=function(){ww(this,"error")};
+k.Dg=function(a){var b=Ob("EPSG:4326"),c=this.c;if(void 0!==a.bounds){var d=Pb(b,c);d=jb(a.bounds,d)}var e=a.minzoom||0,f=a.maxzoom||22;this.tileGrid=c=Bc({extent:Dc(c),maxZoom:f,minZoom:e});this.f=a.template;if(e=a.grids){this.l=Vx(e,c);if(void 0!==a.attribution){var g=void 0!==d?d:b.G();this.va(function(b){return hb(g,b.extent)?[a.attribution]:null})}ww(this,"ready")}else ww(this,"error")};
+k.ad=function(a,b,c,d,e){var f=a+"/"+b+"/"+c;if(this.a.a.hasOwnProperty(f))return this.a.get(f);a=[a,b,c];b=ky(this,a,e);d=this.l(b,d,e);d=new jz(a,void 0!==d?0:4,void 0!==d?d:"",this.tileGrid.Ma(a),this.s,this.i);this.a.set(f,d);return d};k.kh=function(a,b,c){a=a+"/"+b+"/"+c;this.a.a.hasOwnProperty(a)&&this.a.get(a)};function jz(a,b,c,d,e,f){cl.call(this,a,b);this.v=c;this.a=d;this.N=e;this.c=this.l=this.f=null;this.o=f}w(jz,cl);k=jz.prototype;k.Y=function(){return null};
+k.getData=function(a){if(!this.f||!this.l)return null;var b=this.f[Math.floor((1-(a[1]-this.a[1])/(this.a[3]-this.a[1]))*this.f.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.l&&(b=this.l[b],this.c&&b in this.c?a=this.c[b]:a=b);return a};
+function iz(a,b,c,d,e){0==a.state&&!0===e?(Lc(a,"change",function(){c.call(d,this.getData(b))},a),kz(a)):!0===e?setTimeout(function(){c.call(d,this.getData(b))}.bind(a),0):c.call(d,a.getData(b))}k.lb=function(){return this.v};k.Ne=function(){this.state=3;this.u()};k.bj=function(a){this.f=a.grid;this.l=a.keys;this.c=a.data;this.state=4;this.u()};
+function kz(a){if(0==a.state)if(a.state=1,a.o)Zx(a.v,a.bj.bind(a),a.Ne.bind(a));else{var b=new XMLHttpRequest;b.addEventListener("load",a.ep.bind(a));b.addEventListener("error",a.cp.bind(a));b.open("GET",a.v);b.send()}}k.ep=function(a){a=a.target;if(!a.status||200<=a.status&&300>a.status){try{var b=JSON.parse(a.responseText)}catch(c){this.Ne();return}this.bj(b)}else this.Ne()};k.cp=function(){this.Ne()};k.load=function(){this.N&&kz(this)};function lz(a){a=a||{};var b=a.params||{};ny.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,tileClass:a.tileClass,tileGrid:a.tileGrid,tileLoadFunction:a.tileLoadFunction,url:a.url,urls:a.urls,wrapX:void 0!==a.wrapX?a.wrapX:!0,transition:a.transition});this.o=void 0!==a.gutter?a.gutter:0;this.i=b;this.l=!0;this.B=a.serverType;
+this.$=void 0!==a.hidpi?a.hidpi:!0;this.ca=Da();mz(this);jy(this,nz(this))}w(lz,ny);k=lz.prototype;
+k.hp=function(a,b,c,d){c=Ob(c);var e=this.c,f=this.tileGrid;f||(f=this.eb(c));b=f.Le(a,b);if(!(f.b.length<=b[0])){var g=f.Ta(b[0]),h=f.Ma(b,this.ca);f=Ba(f.Za(b[0]),this.j);var l=this.o;0!==l&&(f=za(f,l,this.j),h=Fa(h,g*l,h));e&&e!==c&&(g=by(e,c,a,g),h=bc(h,c,e),a=ac(a,c,e));l={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.i.LAYERS};kb(l,this.i,d);d=Math.floor((h[3]-a[1])/g);l[this.l?"I":"X"]=Math.floor((a[0]-h[0])/g);l[this.l?"J":"Y"]=
+d;return oz(this,b,f,h,1,e||c,l)}};k.$f=function(){return this.o};k.ip=function(){return this.i};
+function oz(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.wb;"STYLES"in a.i||(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:oa(!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 Gy(1==h.length?h[0]:h[wa((b[1]<<
+b[0])+b[2],h.length)],g)}}k.Xc=function(a){return this.$&&void 0!==this.B?a:1};function nz(a){var b=0,c=[],d;for(d in a.i)c[b++]=d+"-"+a.i[d];return c.join("/")}
+k.dc=function(a,b,c){var d=this.tileGrid;d||(d=this.eb(c));if(!(d.b.length<=a[0])){1==b||this.$&&void 0!==this.B||(b=1);var e=d.Ta(a[0]),f=d.Ma(a,this.ca);d=Ba(d.Za(a[0]),this.j);var g=this.o;0!==g&&(d=za(d,g,this.j),f=Fa(f,e*g,f));1!=b&&(d=Aa(d,b,this.j));e={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};kb(e,this.i);return oz(this,a,d,f,b,c,e)}};k.jp=function(a){kb(this.i,a);mz(this);jy(this,nz(this))};function mz(a){a.l=0<=ye(a.i.VERSION||"1.3.0")};function pz(a,b,c,d,e,f,g,h,l,m,n,p,q,r,u){cl.call(this,a,b,u);this.v={};this.o={};this.c=m;this.a=[];this.D=c;this.l=f;this.f=[];this.N=[];if(f){var v=l.Ma(f),z=l.Ta(a[0]);h.Vf(v,h.Dc(z),function(a){var b=gb(v,h.Ma(a)),c=h.G();c&&(b=gb(b,c));.5<=cb(b)/z&&.5<=db(b)/z&&(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.N.push(y(c,"change",r))),c.c++,this.a.push(b))}.bind(this))}}w(pz,cl);k=pz.prototype;
+k.ia=function(){for(var a=0,b=this.a.length;a<b;++a){var c=this.a[a],d=this.c[c];d.c--;0==d.c&&(delete this.c[c],Pc(d))}this.a.length=0;this.c=null;this.f.forEach(Gc);this.f.length=0;this.g&&Pc(this.g);this.state=5;this.u();this.N.forEach(Gc);this.N.length=0;cl.prototype.ia.call(this)};function nk(a,b){b=x(b).toString();b in a.v||(a.v[b]=hg());return a.v[b]}k.Y=function(a){return-1==mk(this,a).fh?null:nk(this,a).canvas};
+function mk(a,b){b=x(b).toString();b in a.o||(a.o[b]={Be:!1,eh:null,wf:-1,fh:-1});return a.o[b]}k.lb=function(){return this.a.join("/")+"-"+this.D};
+k.load=function(){var a=0,b={};0==this.state&&oj(this,1);1==this.state&&this.a.forEach(function(c){var d=this.c[c];0==d.state&&(d.ug(this.C),d.load());1==d.state&&(c=y(d,"change",function(){var c=d.getState();if(2==c||3==c){var f=x(d);3==c?b[f]=!0:(--a,delete b[f]);0==a-Object.keys(b).length&&this.Kh()}}.bind(this)),this.f.push(c),++a)}.bind(this));0==a-Object.keys(b).length&&setTimeout(this.Kh.bind(this),0)};
+k.Kh=function(){for(var a=this.a.length,b=0,c=a-1;0<=c;--c){var d=this.c[this.a[c]].getState();2!=d&&--a;4==d&&++b}a==this.a.length?(this.f.forEach(Gc),this.f.length=0,oj(this,2)):oj(this,b==this.a.length?4:3)};function qz(a,b){a.ug(Eo(b,a.v,a.Cp.bind(a),a.Bp.bind(a)))};function rz(a){var b=a.projection||"EPSG:3857",c=a.extent||Dc(b),d=a.tileGrid||Bc({extent:c,maxZoom:a.maxZoom||22,minZoom:a.minZoom,tileSize:a.tileSize||512});my.call(this,{attributions:a.attributions,cacheSize:void 0!==a.cacheSize?a.cacheSize:128,extent:c,logo:a.logo,opaque:!1,projection:b,state:a.state,tileGrid:d,tileLoadFunction:a.tileLoadFunction?a.tileLoadFunction:qz,tileUrlFunction:a.tileUrlFunction,url:a.url,urls:a.urls,wrapX:void 0===a.wrapX?!0:a.wrapX,transition:a.transition});this.l=a.format?
+a.format:null;this.i={};this.s=void 0==a.overlaps?!0:a.overlaps;this.tileClass=a.tileClass?a.tileClass:Kn;this.f={}}w(rz,my);k=rz.prototype;k.clear=function(){this.a.clear();this.i={}};k.ad=function(a,b,c,d,e){var f=a+"/"+b+"/"+c;if(this.a.a.hasOwnProperty(f))return this.a.get(f);a=[a,b,c];b=ky(this,a,e);d=new pz(a,null!==b?0:4,this.g,this.l,this.tileLoadFunction,b,this.tileUrlFunction,this.tileGrid,this.eb(e),this.i,d,e,this.tileClass,this.dj.bind(this),this.Ea);this.a.set(f,d);return d};
+k.eb=function(a){var b=a.wb,c=this.f[b];c||(c=this.tileGrid,c=this.f[b]=Ac(a,void 0,c?c.Za(c.minZoom):void 0));return c};k.Xc=function(a){return a};k.Zd=function(a,b,c){a=Ba(this.eb(c).Za(a));return[Math.round(a[0]*b),Math.round(a[1]*b)]};function sz(a){this.s=a.matrixIds;qc.call(this,{extent:a.extent,origin:a.origin,origins:a.origins,resolutions:a.resolutions,tileSize:a.tileSize,tileSizes:a.tileSizes,sizes:a.sizes})}w(sz,qc);sz.prototype.v=function(){return this.s};
+function tz(a,b,c){var d=[],e=[],f=[],g=[],h=[],l=void 0!==c?c:[];c=a.SupportedCRS;c=Ob(c.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"))||Ob(c);var m=c.Bc(),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=hc(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 sz({extent:b,origins:f,resolutions:d,matrixIds:e,tileSizes:g,sizes:h})};function Y(a){this.La=void 0!==a.version?a.version:"1.0.0";this.B=void 0!==a.format?a.format:"image/jpeg";this.i=void 0!==a.dimensions?a.dimensions:{};this.$=a.layer;this.o=a.matrixSet;this.ca=a.style;var b=a.urls;void 0===b&&void 0!==a.url&&(b=Yx(a.url));var c=this.ua=void 0!==a.requestEncoding?a.requestEncoding:"KVP",d=a.tileGrid,e={layer:this.$,style:this.ca,tilematrixset:this.o};"KVP"==c&&kb(e,{Service:"WMTS",Request:"GetTile",Version:this.La,Format:this.B});var f=this.i;this.l=function(a){a=
+"KVP"==c?Gy(a,e):a.replace(/\{(\w+?)\}/g,function(a,b){return b.toLowerCase()in e?e[b.toLowerCase()]:a});return function(b){if(b){var e={TileMatrix:d.s[b[0]],TileCol:b[1],TileRow:-b[2]-1};kb(e,f);b=a;return b="KVP"==c?Gy(b,e):b.replace(/\{(\w+?)\}/g,function(a,b){return e[b]})}}};var g=b&&0<b.length?Wx(b.map(this.l)):Xx;ny.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,reprojectionErrorThreshold:a.reprojectionErrorThreshold,
+tileClass:a.tileClass,tileGrid:d,tileLoadFunction:a.tileLoadFunction,tilePixelRatio:a.tilePixelRatio,tileUrlFunction:g,urls:b,wrapX:void 0!==a.wrapX?a.wrapX:!1,transition:a.transition});jy(this,uz(this))}w(Y,ny);k=Y.prototype;k.vb=function(a){this.urls=a;var b=a.join("\n");this.hb(this.dc?this.dc.bind(this):Wx(a.map(this.l.bind(this))),b)};k.ol=function(){return this.i};k.kp=function(){return this.B};k.lp=function(){return this.$};k.Al=function(){return this.o};k.Nl=function(){return this.ua};
+k.mp=function(){return this.ca};k.Ul=function(){return this.La};function uz(a){var b=0,c=[],d;for(d in a.i)c[b++]=d+"-"+a.i[d];return c.join("/")}k.Tq=function(a){kb(this.i,a);jy(this,uz(this))};function vz(a){a=a||{};var b=a.size,c=b[0],d=b[1];b=a.extent||[0,-b[1],b[0],0];var e=[],f=a.tileSize||256,g=f;switch(void 0!==a.tierSizeCalculation?a.tierSizeCalculation:wz){case wz:for(;c>g||d>g;)e.push([Math.ceil(c/g),Math.ceil(d/g)]),g+=g;break;case xz:for(;c>g||d>g;)e.push([Math.ceil(c/g),Math.ceil(d/g)]),c>>=1,d>>=1;break;default:oa(!1,53)}e.push([1,1]);e.reverse();d=[1];var h=[0];g=1;for(c=e.length;g<c;g++)d.push(1<<g),h.push(e[g-1][0]*e[g-1][1]+h[g-1]);d.reverse();var l=new qc({tileSize:f,
+extent:b,origin:$a(b),resolutions:d});(b=a.url)&&-1==b.indexOf("{TileGroup}")&&-1==b.indexOf("{tileIndex}")&&(b+="{TileGroup}/{z}-{x}-{y}.jpg");b=Yx(b);b=Wx(b.map(function(a){return function(b){if(b){var c=b[0],d=b[1];b=-b[2]-1;var f=d+b*e[c][0],g={z:c,x:d,y:b,tileIndex:f,TileGroup:"TileGroup"+((f+h[c])/l.Za(c)|0)};return a.replace(/\{(\w+?)\}/g,function(a,b){return g[b]})}}}));ny.call(this,{attributions:a.attributions,cacheSize:a.cacheSize,crossOrigin:a.crossOrigin,logo:a.logo,projection:a.projection,
+reprojectionErrorThreshold:a.reprojectionErrorThreshold,tileClass:yz.bind(null,l),tileGrid:l,tileUrlFunction:b,transition:a.transition})}w(vz,ny);function yz(a,b,c,d,e,f,g){el.call(this,b,c,d,e,f,g);this.a=null;this.o=Ba(a.Za(b[0]))}w(yz,el);yz.prototype.Y=function(){if(this.a)return this.a;var a=el.prototype.Y.call(this);if(2==this.state){var b=this.o;if(a.width==b[0]&&a.height==b[1])return this.a=a;b=hg(b[0],b[1]);b.drawImage(a,0,0);return this.a=b.canvas}return a};var wz="default",xz="truncated";ha.prototype.code=ha.prototype.code;t("ol.Attribution",Ec);Ec.prototype.getHTML=Ec.prototype.b;t("ol.CanvasMap",H);t("ol.Collection",B);B.prototype.clear=B.prototype.clear;B.prototype.extend=B.prototype.qg;B.prototype.forEach=B.prototype.forEach;B.prototype.getArray=B.prototype.Xm;B.prototype.item=B.prototype.item;B.prototype.getLength=B.prototype.kc;B.prototype.insertAt=B.prototype.Re;B.prototype.pop=B.prototype.pop;B.prototype.push=B.prototype.push;B.prototype.remove=B.prototype.remove;
+B.prototype.removeAt=B.prototype.Wg;B.prototype.setAt=B.prototype.rq;cd.prototype.element=cd.prototype.element;t("ol.color.asArray",vi);t("ol.color.asString",xi);t("ol.colorlike.asColorLike",zi);t("ol.control.defaults",Fg);t("ol.coordinate.add",ze);t("ol.coordinate.createStringXY",function(a){return function(b){return Ke(b,a)}});t("ol.coordinate.format",De);t("ol.coordinate.rotate",Fe);t("ol.coordinate.toStringHDMS",function(a,b){return a?Ce("NS",a[1],b)+" "+Ce("EW",a[0],b):""});
+t("ol.coordinate.toStringXY",Ke);t("ol.DeviceOrientation",pk);pk.prototype.getAlpha=pk.prototype.Ym;pk.prototype.getBeta=pk.prototype.ll;pk.prototype.getGamma=pk.prototype.ql;pk.prototype.getHeading=pk.prototype.Zm;pk.prototype.getTracking=pk.prototype.li;pk.prototype.setTracking=pk.prototype.rg;t("ol.easing.easeIn",Me);t("ol.easing.easeOut",Oe);t("ol.easing.inAndOut",Pe);t("ol.easing.linear",Qe);t("ol.easing.upAndDown",function(a){return.5>a?Pe(2*a):1-Pe(2*(a-.5))});
+t("ol.extent.boundingExtent",Ca);t("ol.extent.buffer",Fa);t("ol.extent.containsCoordinate",Ja);t("ol.extent.containsExtent",La);t("ol.extent.containsXY",Ka);t("ol.extent.createEmpty",Da);t("ol.extent.equals",Sa);t("ol.extent.extend",Ta);t("ol.extent.getArea",ab);t("ol.extent.getBottomLeft",Wa);t("ol.extent.getBottomRight",Ya);t("ol.extent.getCenter",eb);t("ol.extent.getHeight",db);t("ol.extent.getIntersection",gb);t("ol.extent.getSize",function(a){return[a[2]-a[0],a[3]-a[1]]});
+t("ol.extent.getTopLeft",$a);t("ol.extent.getTopRight",Za);t("ol.extent.getWidth",cb);t("ol.extent.intersects",hb);t("ol.extent.isEmpty",bb);t("ol.extent.applyTransform",jb);t("ol.Feature",Hk);Hk.prototype.clone=Hk.prototype.clone;Hk.prototype.getGeometry=Hk.prototype.U;Hk.prototype.getId=Hk.prototype.an;Hk.prototype.getGeometryName=Hk.prototype.sl;Hk.prototype.getStyle=Hk.prototype.bn;Hk.prototype.getStyleFunction=Hk.prototype.ib;Hk.prototype.setGeometry=Hk.prototype.Va;Hk.prototype.setStyle=Hk.prototype.sg;
+Hk.prototype.setId=Hk.prototype.qc;Hk.prototype.setGeometryName=Hk.prototype.Lc;t("ol.featureloader.xhr",Fo);t("ol.Geolocation",Jk);Jk.prototype.getAccuracy=Jk.prototype.el;Jk.prototype.getAccuracyGeometry=Jk.prototype.fl;Jk.prototype.getAltitude=Jk.prototype.gl;Jk.prototype.getAltitudeAccuracy=Jk.prototype.hl;Jk.prototype.getHeading=Jk.prototype.cn;Jk.prototype.getPosition=Jk.prototype.dn;Jk.prototype.getProjection=Jk.prototype.mi;Jk.prototype.getSpeed=Jk.prototype.Ol;Jk.prototype.getTracking=Jk.prototype.ni;
+Jk.prototype.getTrackingOptions=Jk.prototype.ai;Jk.prototype.setProjection=Jk.prototype.oi;Jk.prototype.setTracking=Jk.prototype.Ue;Jk.prototype.setTrackingOptions=Jk.prototype.Rj;t("ol.Graticule",Xk);Xk.prototype.getMap=Xk.prototype.gn;Xk.prototype.getMeridians=Xk.prototype.Cl;Xk.prototype.getParallels=Xk.prototype.Jl;Xk.prototype.setMap=Xk.prototype.setMap;t("ol.has.DEVICE_PIXEL_RATIO",nd);t("ol.has.CANVAS",pd);t("ol.has.DEVICE_ORIENTATION",qd);t("ol.has.GEOLOCATION",rd);t("ol.has.TOUCH",sd);
+t("ol.has.WEBGL",hd);bl.prototype.getImage=bl.prototype.Y;bl.prototype.load=bl.prototype.load;el.prototype.getImage=el.prototype.Y;t("ol.inherits",w);t("ol.interaction.defaults",Zh);t("ol.Kinetic",Gg);t("ol.loadingstrategy.all",tw);t("ol.loadingstrategy.bbox",function(a){return[a]});t("ol.loadingstrategy.tile",function(a){return function(b,c){c=a.Dc(c);b=tc(a,b,c);var d=[];c=[c,0,0];for(c[1]=b.fa;c[1]<=b.la;++c[1])for(c[2]=b.ea;c[2]<=b.ka;++c[2])d.push(a.Ma(c));return d}});t("ol.Map",K);
+ed.prototype.originalEvent=ed.prototype.originalEvent;ed.prototype.pixel=ed.prototype.pixel;ed.prototype.coordinate=ed.prototype.coordinate;ed.prototype.dragging=ed.prototype.dragging;dd.prototype.map=dd.prototype.map;dd.prototype.frameState=dd.prototype.frameState;t("ol.Object",Vc);Vc.prototype.get=Vc.prototype.get;Vc.prototype.getKeys=Vc.prototype.P;Vc.prototype.getProperties=Vc.prototype.L;Vc.prototype.set=Vc.prototype.set;Vc.prototype.setProperties=Vc.prototype.H;Vc.prototype.unset=Vc.prototype.R;
+Zc.prototype.key=Zc.prototype.key;Zc.prototype.oldValue=Zc.prototype.oldValue;t("ol.Observable",Uc);t("ol.Observable.unByKey",function(a){if(Array.isArray(a))for(var b=0,c=a.length;b<c;++b)Gc(a[b]);else Gc(a)});Uc.prototype.changed=Uc.prototype.u;Uc.prototype.dispatchEvent=Uc.prototype.b;Uc.prototype.getRevision=Uc.prototype.K;Uc.prototype.on=Uc.prototype.I;Uc.prototype.once=Uc.prototype.once;Uc.prototype.un=Uc.prototype.J;t("ol.Overlay",Bn);Bn.prototype.getElement=Bn.prototype.Rd;
+Bn.prototype.getId=Bn.prototype.nn;Bn.prototype.getMap=Bn.prototype.Ve;Bn.prototype.getOffset=Bn.prototype.Xh;Bn.prototype.getPosition=Bn.prototype.pi;Bn.prototype.getPositioning=Bn.prototype.Yh;Bn.prototype.setElement=Bn.prototype.Hj;Bn.prototype.setMap=Bn.prototype.setMap;Bn.prototype.setOffset=Bn.prototype.Mj;Bn.prototype.setPosition=Bn.prototype.We;Bn.prototype.setPositioning=Bn.prototype.Pj;t("ol.PluggableMap",G);G.prototype.addControl=G.prototype.Mf;G.prototype.addInteraction=G.prototype.Nf;
+G.prototype.addLayer=G.prototype.xe;G.prototype.addOverlay=G.prototype.ye;G.prototype.forEachFeatureAtPixel=G.prototype.Tc;G.prototype.getFeaturesAtPixel=G.prototype.Xf;G.prototype.forEachLayerAtPixel=G.prototype.tg;G.prototype.hasFeatureAtPixel=G.prototype.ng;G.prototype.getEventCoordinate=G.prototype.Sd;G.prototype.getEventPixel=G.prototype.ud;G.prototype.getTarget=G.prototype.Xd;G.prototype.getTargetElement=G.prototype.Cc;G.prototype.getCoordinateFromPixel=G.prototype.Ra;
+G.prototype.getControls=G.prototype.Wf;G.prototype.getOverlays=G.prototype.gg;G.prototype.getOverlayById=G.prototype.fg;G.prototype.getInteractions=G.prototype.bg;G.prototype.getLayerGroup=G.prototype.hc;G.prototype.getLayers=G.prototype.Xe;G.prototype.getPixelFromCoordinate=G.prototype.Ia;G.prototype.getSize=G.prototype.Cb;G.prototype.getView=G.prototype.aa;G.prototype.getViewport=G.prototype.kg;G.prototype.renderSync=G.prototype.dh;G.prototype.render=G.prototype.render;
+G.prototype.removeControl=G.prototype.Xg;G.prototype.removeInteraction=G.prototype.Zg;G.prototype.removeLayer=G.prototype.$g;G.prototype.removeOverlay=G.prototype.ah;G.prototype.setLayerGroup=G.prototype.zf;G.prototype.setSize=G.prototype.be;G.prototype.setTarget=G.prototype.Ad;G.prototype.setView=G.prototype.jh;G.prototype.updateSize=G.prototype.Oc;t("ol.proj.METERS_PER_UNIT",ub);t("ol.proj.setProj4",function(a){vb=a});t("ol.proj.getPointResolution",Nb);t("ol.proj.addEquivalentProjections",Qb);
+t("ol.proj.addProjection",Rb);t("ol.proj.addCoordinateTransforms",Vb);t("ol.proj.fromLonLat",function(a,b){return ac(a,"EPSG:4326",void 0!==b?b:"EPSG:3857")});t("ol.proj.toLonLat",function(a,b){a=ac(a,void 0!==b?b:"EPSG:3857","EPSG:4326");b=a[0];if(-180>b||180<b)a[0]=wa(b+180,360)-180;return a});t("ol.proj.get",Ob);t("ol.proj.equivalent",Xb);t("ol.proj.getTransform",Yb);t("ol.proj.transform",ac);t("ol.proj.transformExtent",bc);
+t("ol.render.toContext",function(a,b){var c=a.canvas,d=b?b:{};b=d.pixelRatio||nd;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=cf(We(),b,b);return new Bi(a,b,c,d,0)});t("ol.size.toSize",Ba);t("ol.Sphere",ob);ob.prototype.geodesicArea=ob.prototype.a;ob.prototype.haversineDistance=ob.prototype.b;t("ol.Sphere.getLength",rb);t("ol.Sphere.getArea",tb);t("ol.style.iconImageCache",ej);cl.prototype.getTileCoord=cl.prototype.i;
+cl.prototype.load=cl.prototype.load;t("ol.tilegrid.createXYZ",Bc);Kn.prototype.getExtent=Kn.prototype.G;Kn.prototype.getFormat=Kn.prototype.qn;Kn.prototype.getFeatures=Kn.prototype.pn;Kn.prototype.getProjection=Kn.prototype.rn;Kn.prototype.setExtent=Kn.prototype.ri;Kn.prototype.setFeatures=Kn.prototype.Ij;Kn.prototype.setProjection=Kn.prototype.vg;Kn.prototype.setLoader=Kn.prototype.ug;t("ol.View",F);F.prototype.animate=F.prototype.animate;F.prototype.getAnimating=F.prototype.Ac;
+F.prototype.getInteracting=F.prototype.Vh;F.prototype.cancelAnimations=F.prototype.rd;F.prototype.constrainCenter=F.prototype.Sc;F.prototype.constrainResolution=F.prototype.constrainResolution;F.prototype.constrainRotation=F.prototype.constrainRotation;F.prototype.getCenter=F.prototype.xa;F.prototype.calculateExtent=F.prototype.qd;F.prototype.getMaxResolution=F.prototype.sn;F.prototype.getMinResolution=F.prototype.vn;F.prototype.getMaxZoom=F.prototype.tn;F.prototype.setMaxZoom=F.prototype.Cq;
+F.prototype.getMinZoom=F.prototype.wn;F.prototype.setMinZoom=F.prototype.Dq;F.prototype.getProjection=F.prototype.xn;F.prototype.getResolution=F.prototype.Pa;F.prototype.getResolutions=F.prototype.yn;F.prototype.getResolutionForExtent=F.prototype.Je;F.prototype.getRotation=F.prototype.Sa;F.prototype.getZoom=F.prototype.lg;F.prototype.getZoomForResolution=F.prototype.Me;F.prototype.getResolutionForZoom=F.prototype.$h;F.prototype.fit=F.prototype.Uf;F.prototype.centerOn=F.prototype.Nk;
+F.prototype.rotate=F.prototype.rotate;F.prototype.setCenter=F.prototype.ub;F.prototype.setResolution=F.prototype.gd;F.prototype.setRotation=F.prototype.ce;F.prototype.setZoom=F.prototype.Tj;t("ol.xml.getAllTextContent",oo);t("ol.xml.parse",so);Hl.prototype.getGL=Hl.prototype.yp;Hl.prototype.useProgram=Hl.prototype.cd;t("ol.tilegrid.TileGrid",qc);qc.prototype.forEachTileCoord=qc.prototype.Vf;qc.prototype.getMaxZoom=qc.prototype.mj;qc.prototype.getMinZoom=qc.prototype.nj;qc.prototype.getOrigin=qc.prototype.Ic;
+qc.prototype.getResolution=qc.prototype.Ta;qc.prototype.getResolutions=qc.prototype.oj;qc.prototype.getTileCoordExtent=qc.prototype.Ma;qc.prototype.getTileCoordForCoordAndResolution=qc.prototype.Le;qc.prototype.getTileCoordForCoordAndZ=qc.prototype.jg;qc.prototype.getTileSize=qc.prototype.Za;qc.prototype.getZForResolution=qc.prototype.Dc;t("ol.tilegrid.WMTS",sz);sz.prototype.getMatrixIds=sz.prototype.v;t("ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet",tz);t("ol.style.AtlasManager",Tm);
+t("ol.style.Circle",yk);yk.prototype.setRadius=yk.prototype.fd;t("ol.style.Fill",zk);zk.prototype.clone=zk.prototype.clone;zk.prototype.getColor=zk.prototype.g;zk.prototype.setColor=zk.prototype.c;t("ol.style.Icon",dr);dr.prototype.clone=dr.prototype.clone;dr.prototype.getAnchor=dr.prototype.Vc;dr.prototype.getColor=dr.prototype.np;dr.prototype.getImage=dr.prototype.Y;dr.prototype.getOrigin=dr.prototype.bd;dr.prototype.getSrc=dr.prototype.op;dr.prototype.getSize=dr.prototype.oc;
+dr.prototype.load=dr.prototype.load;cj.prototype.setSize=cj.prototype.c;t("ol.style.Image",vk);vk.prototype.getOpacity=vk.prototype.hf;vk.prototype.getRotateWithView=vk.prototype.jf;vk.prototype.getRotation=vk.prototype.kf;vk.prototype.getScale=vk.prototype.lf;vk.prototype.getSnapToPixel=vk.prototype.Ke;vk.prototype.setOpacity=vk.prototype.Ed;vk.prototype.setRotation=vk.prototype.mf;vk.prototype.setScale=vk.prototype.Fd;t("ol.style.RegularShape",wk);wk.prototype.clone=wk.prototype.clone;
+wk.prototype.getAnchor=wk.prototype.Vc;wk.prototype.getAngle=wk.prototype.ij;wk.prototype.getFill=wk.prototype.Fa;wk.prototype.getImage=wk.prototype.Y;wk.prototype.getOrigin=wk.prototype.bd;wk.prototype.getPoints=wk.prototype.jj;wk.prototype.getRadius=wk.prototype.kj;wk.prototype.getRadius2=wk.prototype.Zh;wk.prototype.getSize=wk.prototype.oc;wk.prototype.getStroke=wk.prototype.Ga;t("ol.style.Stroke",Ak);Ak.prototype.clone=Ak.prototype.clone;Ak.prototype.getColor=Ak.prototype.pp;
+Ak.prototype.getLineCap=Ak.prototype.vl;Ak.prototype.getLineDash=Ak.prototype.qp;Ak.prototype.getLineDashOffset=Ak.prototype.wl;Ak.prototype.getLineJoin=Ak.prototype.xl;Ak.prototype.getMiterLimit=Ak.prototype.Dl;Ak.prototype.getWidth=Ak.prototype.rp;Ak.prototype.setColor=Ak.prototype.sp;Ak.prototype.setLineCap=Ak.prototype.yq;Ak.prototype.setLineDash=Ak.prototype.setLineDash;Ak.prototype.setLineDashOffset=Ak.prototype.zq;Ak.prototype.setLineJoin=Ak.prototype.Aq;Ak.prototype.setMiterLimit=Ak.prototype.Eq;
+Ak.prototype.setWidth=Ak.prototype.Kq;t("ol.style.Style",Bk);Bk.prototype.clone=Bk.prototype.clone;Bk.prototype.getRenderer=Bk.prototype.Ie;Bk.prototype.setRenderer=Bk.prototype.Iq;Bk.prototype.getGeometry=Bk.prototype.U;Bk.prototype.getGeometryFunction=Bk.prototype.rl;Bk.prototype.getFill=Bk.prototype.Fa;Bk.prototype.setFill=Bk.prototype.yf;Bk.prototype.getImage=Bk.prototype.Y;Bk.prototype.setImage=Bk.prototype.ih;Bk.prototype.getStroke=Bk.prototype.Ga;Bk.prototype.setStroke=Bk.prototype.Af;
+Bk.prototype.getText=Bk.prototype.Ka;Bk.prototype.setText=Bk.prototype.Hd;Bk.prototype.getZIndex=Bk.prototype.Ba;Bk.prototype.setGeometry=Bk.prototype.Va;Bk.prototype.setZIndex=Bk.prototype.$b;t("ol.style.Text",J);J.prototype.clone=J.prototype.clone;J.prototype.getOverflow=J.prototype.Gl;J.prototype.getFont=J.prototype.pl;J.prototype.getMaxAngle=J.prototype.Bl;J.prototype.getPlacement=J.prototype.Kl;J.prototype.getOffsetX=J.prototype.El;J.prototype.getOffsetY=J.prototype.Fl;J.prototype.getFill=J.prototype.Fa;
+J.prototype.getRotateWithView=J.prototype.tp;J.prototype.getRotation=J.prototype.up;J.prototype.getScale=J.prototype.vp;J.prototype.getStroke=J.prototype.Ga;J.prototype.getText=J.prototype.Ka;J.prototype.getTextAlign=J.prototype.Ql;J.prototype.getTextBaseline=J.prototype.Rl;J.prototype.getBackgroundFill=J.prototype.jl;J.prototype.getBackgroundStroke=J.prototype.kl;J.prototype.getPadding=J.prototype.Il;J.prototype.setOverflow=J.prototype.Fq;J.prototype.setFont=J.prototype.Jj;
+J.prototype.setMaxAngle=J.prototype.Bq;J.prototype.setOffsetX=J.prototype.Nj;J.prototype.setOffsetY=J.prototype.Oj;J.prototype.setPlacement=J.prototype.Hq;J.prototype.setFill=J.prototype.yf;J.prototype.setRotation=J.prototype.wp;J.prototype.setScale=J.prototype.lj;J.prototype.setStroke=J.prototype.Af;J.prototype.setText=J.prototype.Hd;J.prototype.setTextAlign=J.prototype.Qj;J.prototype.setTextBaseline=J.prototype.Jq;J.prototype.setBackgroundFill=J.prototype.sq;J.prototype.setBackgroundStroke=J.prototype.tq;
+J.prototype.setPadding=J.prototype.Gq;t("ol.source.BingMaps",ry);t("ol.source.BingMaps.TOS_ATTRIBUTION",'<a class="ol-attribution-bing-tos" href="https://www.microsoft.com/maps/product/terms.html">Terms of Use</a>');ry.prototype.getApiKey=ry.prototype.ca;ry.prototype.getImagerySet=ry.prototype.ua;t("ol.source.CartoDB",ty);ty.prototype.getConfig=ty.prototype.nl;ty.prototype.updateConfig=ty.prototype.Sq;ty.prototype.setConfig=ty.prototype.uq;t("ol.source.Cluster",X);X.prototype.getDistance=X.prototype.Eo;
+X.prototype.getSource=X.prototype.Fo;X.prototype.setDistance=X.prototype.vq;t("ol.source.Image",zy);By.prototype.image=By.prototype.image;t("ol.source.ImageArcGISRest",Hy);Hy.prototype.getParams=Hy.prototype.Ho;Hy.prototype.getImageLoadFunction=Hy.prototype.Go;Hy.prototype.getUrl=Hy.prototype.Io;Hy.prototype.setImageLoadFunction=Hy.prototype.Jo;Hy.prototype.setUrl=Hy.prototype.Ko;Hy.prototype.updateParams=Hy.prototype.Lo;t("ol.source.ImageCanvas",Iy);t("ol.source.ImageMapGuide",Jy);
+Jy.prototype.getParams=Jy.prototype.No;Jy.prototype.getImageLoadFunction=Jy.prototype.Mo;Jy.prototype.updateParams=Jy.prototype.Po;Jy.prototype.setImageLoadFunction=Jy.prototype.Oo;t("ol.source.ImageStatic",Ky);t("ol.source.ImageVector",Ly);Ly.prototype.getSource=Ly.prototype.Qo;Ly.prototype.getStyle=Ly.prototype.Ro;Ly.prototype.getStyleFunction=Ly.prototype.ib;Ly.prototype.setStyle=Ly.prototype.aj;t("ol.source.ImageWMS",Ny);Ny.prototype.getGetFeatureInfoUrl=Ny.prototype.Uo;
+Ny.prototype.getParams=Ny.prototype.Wo;Ny.prototype.getImageLoadFunction=Ny.prototype.Vo;Ny.prototype.getUrl=Ny.prototype.Xo;Ny.prototype.setImageLoadFunction=Ny.prototype.Yo;Ny.prototype.setUrl=Ny.prototype.Zo;Ny.prototype.updateParams=Ny.prototype.$o;t("ol.source.OSM",Ry);t("ol.source.OSM.ATTRIBUTION",'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.');t("ol.source.Raster",Sy);Sy.prototype.setOperation=Sy.prototype.s;Wy.prototype.extent=Wy.prototype.extent;
+Wy.prototype.resolution=Wy.prototype.resolution;Wy.prototype.data=Wy.prototype.data;t("ol.source.Source",uw);uw.prototype.getAttributions=uw.prototype.za;uw.prototype.getLogo=uw.prototype.Aa;uw.prototype.getProjection=uw.prototype.Da;uw.prototype.getState=uw.prototype.getState;uw.prototype.refresh=uw.prototype.sa;uw.prototype.setAttributions=uw.prototype.va;t("ol.source.Stamen",Zy);t("ol.source.Tile",iy);iy.prototype.getTileGrid=iy.prototype.jb;ly.prototype.tile=ly.prototype.tile;
+t("ol.source.TileArcGISRest",cz);cz.prototype.getParams=cz.prototype.o;cz.prototype.updateParams=cz.prototype.B;t("ol.source.TileDebug",ez);t("ol.source.TileImage",ny);ny.prototype.setRenderReprojectionEdges=ny.prototype.Qb;ny.prototype.setTileGridForProjection=ny.prototype.Rb;t("ol.source.TileJSON",gz);gz.prototype.getTileJSON=gz.prototype.Sl;t("ol.source.TileUTFGrid",hz);hz.prototype.getTemplate=hz.prototype.Pl;hz.prototype.forDataAtCoordinateAndResolution=hz.prototype.al;
+t("ol.source.TileWMS",lz);lz.prototype.getGetFeatureInfoUrl=lz.prototype.hp;lz.prototype.getParams=lz.prototype.ip;lz.prototype.updateParams=lz.prototype.jp;my.prototype.getTileLoadFunction=my.prototype.yb;my.prototype.getTileUrlFunction=my.prototype.zb;my.prototype.getUrls=my.prototype.Ab;my.prototype.setTileLoadFunction=my.prototype.Fb;my.prototype.setTileUrlFunction=my.prototype.hb;my.prototype.setUrl=my.prototype.rb;my.prototype.setUrls=my.prototype.vb;t("ol.source.Vector",U);
+U.prototype.addFeature=U.prototype.Gb;U.prototype.addFeatures=U.prototype.Qc;U.prototype.clear=U.prototype.clear;U.prototype.forEachFeature=U.prototype.Lh;U.prototype.forEachFeatureInExtent=U.prototype.ec;U.prototype.forEachFeatureIntersectingExtent=U.prototype.Mh;U.prototype.getFeaturesCollection=U.prototype.Th;U.prototype.getFeatures=U.prototype.ee;U.prototype.getFeaturesAtCoordinate=U.prototype.Sh;U.prototype.getFeaturesInExtent=U.prototype.Yf;U.prototype.getClosestFeatureToCoordinate=U.prototype.Oh;
+U.prototype.getExtent=U.prototype.G;U.prototype.getFeatureById=U.prototype.Rh;U.prototype.getFormat=U.prototype.ej;U.prototype.getUrl=U.prototype.fj;U.prototype.removeLoadedExtent=U.prototype.Cj;U.prototype.removeFeature=U.prototype.Lb;U.prototype.setLoader=U.prototype.hj;Bw.prototype.feature=Bw.prototype.feature;t("ol.source.VectorTile",rz);rz.prototype.clear=rz.prototype.clear;t("ol.source.WMTS",Y);Y.prototype.getDimensions=Y.prototype.ol;Y.prototype.getFormat=Y.prototype.kp;
+Y.prototype.getLayer=Y.prototype.lp;Y.prototype.getMatrixSet=Y.prototype.Al;Y.prototype.getRequestEncoding=Y.prototype.Nl;Y.prototype.getStyle=Y.prototype.mp;Y.prototype.getVersion=Y.prototype.Ul;Y.prototype.updateDimensions=Y.prototype.Tq;
+t("ol.source.WMTS.optionsFromCapabilities",function(a,b){var c=hc(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?mc(c.TileMatrixSetLink,function(a){var c=hc(d,function(b){return b.Identifier==a.TileMatrixSet}).SupportedCRS,e=Ob(c.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"))||Ob(c),f=Ob(b.projection);return e&&f?Xb(e,f):c==b.projection}):mc(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=mc(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=hc(a.Contents.TileMatrixSet,function(a){return a.Identifier==f}),n,p=m.SupportedCRS;p&&(n=Ob(p.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,
+"$1:$3"))||Ob(p));"projection"in b&&(p=Ob(b.projection),!p||n&&!Xb(p,n)||(n=p));p=c.WGS84BoundingBox;if(void 0!==p){var q=Ob("EPSG:4326").G();q=p[0]==q[0]&&p[2]==q[2];var r=bc(p,"EPSG:4326",n);(p=n.G())&&(La(p,r)||(r=void 0))}g=tz(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)if(a[r].Constraint){var v=hc(a[r].Constraint,function(a){return"GetEncoding"==a.name}).AllowedValues.Value;
+""===m&&(m=v[0]);if("KVP"===m)ec(v,"KVP")&&u.push(a[r].href);else break}else a[r].href&&(m="KVP",u.push(a[r].href));0===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",sy);t("ol.source.Zoomify",vz);t("ol.renderer.webgl.ImageLayer",pn);t("ol.renderer.webgl.Map",sn);
+t("ol.renderer.webgl.TileLayer",zn);t("ol.renderer.webgl.VectorLayer",An);t("ol.renderer.canvas.ImageLayer",bj);t("ol.renderer.canvas.Map",kj);t("ol.renderer.canvas.TileLayer",mj);t("ol.renderer.canvas.VectorLayer",hk);t("ol.renderer.canvas.VectorTileLayer",jk);bi.prototype.vectorContext=bi.prototype.vectorContext;bi.prototype.frameState=bi.prototype.frameState;bi.prototype.context=bi.prototype.context;bi.prototype.glContext=bi.prototype.glContext;it.prototype.get=it.prototype.get;
+it.prototype.getExtent=it.prototype.G;it.prototype.getId=it.prototype.Ao;it.prototype.getGeometry=it.prototype.U;it.prototype.getProperties=it.prototype.Bo;it.prototype.getType=it.prototype.S;t("ol.render.VectorContext",Ai);gn.prototype.setStyle=gn.prototype.Dd;gn.prototype.drawGeometry=gn.prototype.Hb;gn.prototype.drawFeature=gn.prototype.Ce;Bi.prototype.drawCircle=Bi.prototype.cc;Bi.prototype.setStyle=Bi.prototype.Dd;Bi.prototype.drawGeometry=Bi.prototype.Hb;Bi.prototype.drawFeature=Bi.prototype.Ce;
+t("ol.proj.common.add",cc);t("ol.proj.Projection",wb);wb.prototype.getCode=wb.prototype.ml;wb.prototype.getExtent=wb.prototype.G;wb.prototype.getUnits=wb.prototype.zo;wb.prototype.getMetersPerUnit=wb.prototype.Bc;wb.prototype.getWorldExtent=wb.prototype.Vl;wb.prototype.getAxisOrientation=wb.prototype.il;wb.prototype.isGlobal=wb.prototype.Gm;wb.prototype.setGlobal=wb.prototype.xq;wb.prototype.setExtent=wb.prototype.Si;wb.prototype.setWorldExtent=wb.prototype.Sj;wb.prototype.setGetPointResolution=wb.prototype.wq;
+t("ol.proj.Units.METERS_PER_UNIT",ub);t("ol.layer.Base",kg);kg.prototype.getExtent=kg.prototype.G;kg.prototype.getMaxResolution=kg.prototype.lc;kg.prototype.getMinResolution=kg.prototype.mc;kg.prototype.getOpacity=kg.prototype.nc;kg.prototype.getVisible=kg.prototype.Jb;kg.prototype.getZIndex=kg.prototype.Ba;kg.prototype.setExtent=kg.prototype.Fc;kg.prototype.setMaxResolution=kg.prototype.Mc;kg.prototype.setMinResolution=kg.prototype.Nc;kg.prototype.setOpacity=kg.prototype.Gc;
+kg.prototype.setVisible=kg.prototype.Hc;kg.prototype.setZIndex=kg.prototype.$b;t("ol.layer.Group",mg);mg.prototype.getLayers=mg.prototype.Cd;mg.prototype.setLayers=mg.prototype.Qi;t("ol.layer.Heatmap",V);V.prototype.getBlur=V.prototype.Nh;V.prototype.getGradient=V.prototype.Uh;V.prototype.getRadius=V.prototype.Ri;V.prototype.setBlur=V.prototype.Fj;V.prototype.setGradient=V.prototype.Lj;V.prototype.setRadius=V.prototype.fd;t("ol.layer.Image",Sx);Sx.prototype.getSource=Sx.prototype.ha;
+t("ol.layer.Layer",xg);xg.prototype.getSource=xg.prototype.ha;xg.prototype.setMap=xg.prototype.setMap;xg.prototype.setSource=xg.prototype.hd;t("ol.layer.Tile",Tx);Tx.prototype.getPreload=Tx.prototype.c;Tx.prototype.getSource=Tx.prototype.ha;Tx.prototype.setPreload=Tx.prototype.j;Tx.prototype.getUseInterimTilesOnError=Tx.prototype.i;Tx.prototype.setUseInterimTilesOnError=Tx.prototype.C;t("ol.layer.Vector",T);T.prototype.getSource=T.prototype.ha;T.prototype.getStyle=T.prototype.B;
+T.prototype.getStyleFunction=T.prototype.ib;T.prototype.setStyle=T.prototype.j;t("ol.layer.VectorTile",W);W.prototype.getPreload=W.prototype.c;W.prototype.getUseInterimTilesOnError=W.prototype.i;W.prototype.setPreload=W.prototype.T;W.prototype.setUseInterimTilesOnError=W.prototype.O;W.prototype.getSource=W.prototype.ha;t("ol.interaction.DoubleClickZoom",Ug);t("ol.interaction.DoubleClickZoom.handleEvent",Vg);t("ol.interaction.DragAndDrop",iw);t("ol.interaction.DragAndDrop.handleEvent",Re);
+lw.prototype.features=lw.prototype.features;lw.prototype.file=lw.prototype.file;lw.prototype.projection=lw.prototype.projection;t("ol.interaction.DragBox",th);th.prototype.getGeometry=th.prototype.U;yh.prototype.coordinate=yh.prototype.coordinate;yh.prototype.mapBrowserEvent=yh.prototype.mapBrowserEvent;t("ol.interaction.DragPan",ih);t("ol.interaction.DragRotate",mh);t("ol.interaction.DragRotateAndZoom",pw);t("ol.interaction.DragZoom",Ch);t("ol.interaction.Draw",Ew);
+t("ol.interaction.Draw.handleEvent",Gw);Ew.prototype.removeLastPoint=Ew.prototype.nq;Ew.prototype.finishDrawing=Ew.prototype.Pd;Ew.prototype.extend=Ew.prototype.Zn;t("ol.interaction.Draw.createRegularPolygon",function(a,b){return function(c,d){var e=c[0];c=c[1];var f=Math.sqrt(He(e,c));d=d?d:Sf(new gw(e),a);Tf(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=Ca(a);b=b||new D(null);b.na([[Wa(a),Ya(a),Za(a),$a(a),Wa(a)]]);return b}});Uw.prototype.feature=Uw.prototype.feature;t("ol.interaction.Extent",Vw);Vw.prototype.getExtent=Vw.prototype.G;Vw.prototype.setExtent=Vw.prototype.f;fx.prototype.extent=fx.prototype.extent;t("ol.interaction.Interaction",Jg);Jg.prototype.getActive=Jg.prototype.c;Jg.prototype.getMap=Jg.prototype.i;Jg.prototype.setActive=Jg.prototype.Ha;
+t("ol.interaction.KeyboardPan",Dh);t("ol.interaction.KeyboardPan.handleEvent",Eh);t("ol.interaction.KeyboardZoom",Fh);t("ol.interaction.KeyboardZoom.handleEvent",Gh);t("ol.interaction.Modify",gx);t("ol.interaction.Modify.handleEvent",jx);gx.prototype.removePoint=gx.prototype.Dj;ox.prototype.features=ox.prototype.features;ox.prototype.mapBrowserEvent=ox.prototype.mapBrowserEvent;t("ol.interaction.MouseWheelZoom",Hh);t("ol.interaction.MouseWheelZoom.handleEvent",Ih);Hh.prototype.setMouseAnchor=Hh.prototype.V;
+t("ol.interaction.PinchRotate",Rh);t("ol.interaction.PinchZoom",Vh);t("ol.interaction.Pointer",fh);t("ol.interaction.Pointer.handleEvent",gh);t("ol.interaction.Select",wx);wx.prototype.getFeatures=wx.prototype.lo;wx.prototype.getHitTolerance=wx.prototype.mo;wx.prototype.getLayer=wx.prototype.no;t("ol.interaction.Select.handleEvent",xx);wx.prototype.setHitTolerance=wx.prototype.po;wx.prototype.setMap=wx.prototype.setMap;zx.prototype.selected=zx.prototype.selected;zx.prototype.deselected=zx.prototype.deselected;
+zx.prototype.mapBrowserEvent=zx.prototype.mapBrowserEvent;t("ol.interaction.Snap",Bx);Bx.prototype.addFeature=Bx.prototype.Gb;Bx.prototype.removeFeature=Bx.prototype.Lb;t("ol.interaction.Translate",Gx);Gx.prototype.getHitTolerance=Gx.prototype.B;Gx.prototype.setHitTolerance=Gx.prototype.T;Mx.prototype.features=Mx.prototype.features;Mx.prototype.coordinate=Mx.prototype.coordinate;t("ol.geom.Circle",gw);gw.prototype.clone=gw.prototype.clone;gw.prototype.getCenter=gw.prototype.xa;
+gw.prototype.getRadius=gw.prototype.Bd;gw.prototype.getType=gw.prototype.S;gw.prototype.intersectsExtent=gw.prototype.$a;gw.prototype.setCenter=gw.prototype.ub;gw.prototype.setCenterAndRadius=gw.prototype.hh;gw.prototype.setRadius=gw.prototype.fd;gw.prototype.transform=gw.prototype.mb;t("ol.geom.Geometry",gf);gf.prototype.getClosestPoint=gf.prototype.Ib;gf.prototype.intersectsCoordinate=gf.prototype.Bb;gf.prototype.getExtent=gf.prototype.G;gf.prototype.rotate=gf.prototype.rotate;
+gf.prototype.scale=gf.prototype.scale;gf.prototype.simplify=gf.prototype.Sb;gf.prototype.transform=gf.prototype.mb;t("ol.geom.GeometryCollection",Mq);Mq.prototype.clone=Mq.prototype.clone;Mq.prototype.getGeometries=Mq.prototype.vd;Mq.prototype.getType=Mq.prototype.S;Mq.prototype.intersectsExtent=Mq.prototype.$a;Mq.prototype.setGeometries=Mq.prototype.Kj;Mq.prototype.applyTransform=Mq.prototype.Rc;Mq.prototype.translate=Mq.prototype.translate;t("ol.geom.LinearRing",Df);Df.prototype.clone=Df.prototype.clone;
+Df.prototype.getArea=Df.prototype.Vn;Df.prototype.getCoordinates=Df.prototype.W;Df.prototype.getType=Df.prototype.S;Df.prototype.setCoordinates=Df.prototype.na;t("ol.geom.LineString",I);I.prototype.appendCoordinate=I.prototype.Fk;I.prototype.clone=I.prototype.clone;I.prototype.forEachSegment=I.prototype.dl;I.prototype.getCoordinateAtM=I.prototype.Tn;I.prototype.getCoordinates=I.prototype.W;I.prototype.getCoordinateAt=I.prototype.Ph;I.prototype.getLength=I.prototype.Un;I.prototype.getType=I.prototype.S;
+I.prototype.intersectsExtent=I.prototype.$a;I.prototype.setCoordinates=I.prototype.na;t("ol.geom.MultiLineString",P);P.prototype.appendLineString=P.prototype.Gk;P.prototype.clone=P.prototype.clone;P.prototype.getCoordinateAtM=P.prototype.Wn;P.prototype.getCoordinates=P.prototype.W;P.prototype.getLineString=P.prototype.yl;P.prototype.getLineStrings=P.prototype.wd;P.prototype.getType=P.prototype.S;P.prototype.intersectsExtent=P.prototype.$a;P.prototype.setCoordinates=P.prototype.na;
+t("ol.geom.MultiPoint",No);No.prototype.appendPoint=No.prototype.Ik;No.prototype.clone=No.prototype.clone;No.prototype.getCoordinates=No.prototype.W;No.prototype.getPoint=No.prototype.Ll;No.prototype.getPoints=No.prototype.de;No.prototype.getType=No.prototype.S;No.prototype.intersectsExtent=No.prototype.$a;No.prototype.setCoordinates=No.prototype.na;t("ol.geom.MultiPolygon",Q);Q.prototype.appendPolygon=Q.prototype.Jk;Q.prototype.clone=Q.prototype.clone;Q.prototype.getArea=Q.prototype.Xn;
+Q.prototype.getCoordinates=Q.prototype.W;Q.prototype.getInteriorPoints=Q.prototype.ul;Q.prototype.getPolygon=Q.prototype.Ml;Q.prototype.getPolygons=Q.prototype.Vd;Q.prototype.getType=Q.prototype.S;Q.prototype.intersectsExtent=Q.prototype.$a;Q.prototype.setCoordinates=Q.prototype.na;t("ol.geom.Point",C);C.prototype.clone=C.prototype.clone;C.prototype.getCoordinates=C.prototype.W;C.prototype.getType=C.prototype.S;C.prototype.intersectsExtent=C.prototype.$a;C.prototype.setCoordinates=C.prototype.na;
+t("ol.geom.Polygon",D);D.prototype.appendLinearRing=D.prototype.Hk;D.prototype.clone=D.prototype.clone;D.prototype.getArea=D.prototype.Yn;D.prototype.getCoordinates=D.prototype.W;D.prototype.getInteriorPoint=D.prototype.tl;D.prototype.getLinearRingCount=D.prototype.zl;D.prototype.getLinearRing=D.prototype.Wh;D.prototype.getLinearRings=D.prototype.Ud;D.prototype.getType=D.prototype.S;D.prototype.intersectsExtent=D.prototype.$a;D.prototype.setCoordinates=D.prototype.na;
+t("ol.geom.Polygon.circular",Qf);t("ol.geom.Polygon.fromExtent",Rf);t("ol.geom.Polygon.fromCircle",Sf);t("ol.geom.SimpleGeometry",hf);hf.prototype.getFirstCoordinate=hf.prototype.fc;hf.prototype.getLastCoordinate=hf.prototype.gc;hf.prototype.getLayout=hf.prototype.ic;hf.prototype.applyTransform=hf.prototype.Rc;hf.prototype.translate=hf.prototype.translate;t("ol.format.EsriJSON",Po);Po.prototype.readFeature=Po.prototype.Yb;Po.prototype.readFeatures=Po.prototype.Qa;Po.prototype.readGeometry=Po.prototype.ed;
+Po.prototype.readProjection=Po.prototype.sb;Po.prototype.writeGeometry=Po.prototype.md;Po.prototype.writeGeometryObject=Po.prototype.se;Po.prototype.writeFeature=Po.prototype.Jd;Po.prototype.writeFeatureObject=Po.prototype.ld;Po.prototype.writeFeatures=Po.prototype.ac;Po.prototype.writeFeaturesObject=Po.prototype.qe;t("ol.format.Feature",Go);t("ol.format.filter.and",bu);
+t("ol.format.filter.or",function(a){var b=[null].concat(Array.prototype.slice.call(arguments));return new (Function.prototype.bind.apply($t,b))});t("ol.format.filter.not",function(a){return new Yt(a)});t("ol.format.filter.bbox",cu);t("ol.format.filter.contains",function(a,b,c){return new Lt(a,b,c)});t("ol.format.filter.intersects",function(a,b,c){return new St(a,b,c)});t("ol.format.filter.within",function(a,b,c){return new au(a,b,c)});
+t("ol.format.filter.equalTo",function(a,b,c){return new Pt(a,b,c)});t("ol.format.filter.notEqualTo",function(a,b,c){return new Zt(a,b,c)});t("ol.format.filter.lessThan",function(a,b){return new Wt(a,b)});t("ol.format.filter.lessThanOrEqualTo",function(a,b){return new Xt(a,b)});t("ol.format.filter.greaterThan",function(a,b){return new Qt(a,b)});t("ol.format.filter.greaterThanOrEqualTo",function(a,b){return new Rt(a,b)});t("ol.format.filter.isNull",function(a){return new Vt(a)});
+t("ol.format.filter.between",function(a,b,c){return new Tt(a,b,c)});t("ol.format.filter.like",function(a,b,c,d,e,f){return new Ut(a,b,c,d,e,f)});t("ol.format.filter.during",function(a,b,c){return new Nt(a,b,c)});t("ol.format.GeoJSON",Qq);Qq.prototype.readFeature=Qq.prototype.Yb;Qq.prototype.readFeatures=Qq.prototype.Qa;Qq.prototype.readGeometry=Qq.prototype.ed;Qq.prototype.readProjection=Qq.prototype.sb;Qq.prototype.writeFeature=Qq.prototype.Jd;Qq.prototype.writeFeatureObject=Qq.prototype.ld;
+Qq.prototype.writeFeatures=Qq.prototype.ac;Qq.prototype.writeFeaturesObject=Qq.prototype.qe;Qq.prototype.writeGeometry=Qq.prototype.md;Qq.prototype.writeGeometryObject=Qq.prototype.se;t("ol.format.GML",Kp);Kp.prototype.writeFeatures=Kp.prototype.ac;Kp.prototype.writeFeaturesNode=Kp.prototype.bc;t("ol.format.GML2",Tp);t("ol.format.GML3",Kp);Kp.prototype.writeGeometryNode=Kp.prototype.re;Kp.prototype.writeFeatures=Kp.prototype.ac;Kp.prototype.writeFeaturesNode=Kp.prototype.bc;
+Zo.prototype.readFeatures=Zo.prototype.Qa;t("ol.format.GPX",dq);dq.prototype.readFeature=dq.prototype.Yb;dq.prototype.readFeatures=dq.prototype.Qa;dq.prototype.readProjection=dq.prototype.sb;dq.prototype.writeFeatures=dq.prototype.ac;dq.prototype.writeFeaturesNode=dq.prototype.bc;t("ol.format.IGC",Xq);Xq.prototype.readFeature=Xq.prototype.Yb;Xq.prototype.readFeatures=Xq.prototype.Qa;Xq.prototype.readProjection=Xq.prototype.sb;t("ol.format.KML",er);er.prototype.readFeature=er.prototype.Yb;
+er.prototype.readFeatures=er.prototype.Qa;er.prototype.readName=er.prototype.cq;er.prototype.readNetworkLinks=er.prototype.eq;er.prototype.readRegion=er.prototype.hq;er.prototype.readRegionFromNode=er.prototype.vf;er.prototype.readProjection=er.prototype.sb;er.prototype.writeFeatures=er.prototype.ac;er.prototype.writeFeaturesNode=er.prototype.bc;t("ol.format.MVT",jt);jt.prototype.getLastExtent=jt.prototype.cg;jt.prototype.readFeatures=jt.prototype.Qa;jt.prototype.readProjection=jt.prototype.sb;
+jt.prototype.setLayers=jt.prototype.Sn;t("ol.format.OSMXML",ot);ot.prototype.readFeatures=ot.prototype.Qa;ot.prototype.readProjection=ot.prototype.sb;t("ol.format.Polyline",vt);t("ol.format.Polyline.encodeDeltas",wt);t("ol.format.Polyline.decodeDeltas",yt);t("ol.format.Polyline.encodeFloats",xt);t("ol.format.Polyline.decodeFloats",zt);vt.prototype.readFeature=vt.prototype.Yb;vt.prototype.readFeatures=vt.prototype.Qa;vt.prototype.readGeometry=vt.prototype.ed;vt.prototype.readProjection=vt.prototype.sb;
+vt.prototype.writeGeometry=vt.prototype.md;t("ol.format.TopoJSON",At);At.prototype.readFeatures=At.prototype.Qa;At.prototype.readProjection=At.prototype.sb;t("ol.format.WFS",du);du.prototype.readFeatures=du.prototype.Qa;du.prototype.readTransactionResponse=du.prototype.j;du.prototype.readFeatureCollectionMetadata=du.prototype.f;t("ol.format.WFS.writeFilter",function(a){var b=no("http://www.opengis.net/ogc","Filter");Do({node:b},su,yo(a.rc),[a],[]);return b});du.prototype.writeGetFeature=du.prototype.s;
+du.prototype.writeTransaction=du.prototype.v;du.prototype.readProjection=du.prototype.sb;t("ol.format.WKT",Ku);Ku.prototype.readFeature=Ku.prototype.Yb;Ku.prototype.readFeatures=Ku.prototype.Qa;Ku.prototype.readGeometry=Ku.prototype.ed;Ku.prototype.writeFeature=Ku.prototype.Jd;Ku.prototype.writeFeatures=Ku.prototype.ac;Ku.prototype.writeGeometry=Ku.prototype.md;t("ol.format.WMSCapabilities",ev);ev.prototype.read=ev.prototype.read;t("ol.format.WMSGetFeatureInfo",Bv);Bv.prototype.readFeatures=Bv.prototype.Qa;
+t("ol.format.WMTSCapabilities",Sv);Sv.prototype.read=Sv.prototype.read;t("ol.format.filter.And",It);t("ol.format.filter.Bbox",Jt);t("ol.format.filter.Comparison",Mt);t("ol.format.filter.ComparisonBinary",Ot);t("ol.format.filter.Contains",Lt);t("ol.format.filter.During",Nt);t("ol.format.filter.EqualTo",Pt);t("ol.format.filter.Filter",Gt);t("ol.format.filter.GreaterThan",Qt);t("ol.format.filter.GreaterThanOrEqualTo",Rt);t("ol.format.filter.Intersects",St);t("ol.format.filter.IsBetween",Tt);
+t("ol.format.filter.IsLike",Ut);t("ol.format.filter.IsNull",Vt);t("ol.format.filter.LessThan",Wt);t("ol.format.filter.LessThanOrEqualTo",Xt);t("ol.format.filter.Not",Yt);t("ol.format.filter.NotEqualTo",Zt);t("ol.format.filter.Or",$t);t("ol.format.filter.Spatial",Kt);t("ol.format.filter.Within",au);t("ol.events.condition.altKeyOnly",Wg);t("ol.events.condition.altShiftKeysOnly",Xg);t("ol.events.condition.always",Re);t("ol.events.condition.click",function(a){return"click"==a.type});
+t("ol.events.condition.never",Se);t("ol.events.condition.pointerMove",Zg);t("ol.events.condition.singleClick",$g);t("ol.events.condition.doubleClick",function(a){return"dblclick"==a.type});t("ol.events.condition.noModifierKeys",ah);t("ol.events.condition.platformModifierKeyOnly",function(a){a=a.originalEvent;return!a.altKey&&(md?a.metaKey:a.ctrlKey)&&!a.shiftKey});t("ol.events.condition.shiftKeyOnly",bh);t("ol.events.condition.targetNotEditable",ch);t("ol.events.condition.mouseOnly",dh);
+t("ol.events.condition.primaryAction",eh);Qc.prototype.type=Qc.prototype.type;Qc.prototype.target=Qc.prototype.target;Qc.prototype.preventDefault=Qc.prototype.preventDefault;Qc.prototype.stopPropagation=Qc.prototype.stopPropagation;t("ol.control.Attribution",zg);t("ol.control.Attribution.render",Ag);zg.prototype.getCollapsible=zg.prototype.An;zg.prototype.setCollapsible=zg.prototype.Dn;zg.prototype.setCollapsed=zg.prototype.Cn;zg.prototype.getCollapsed=zg.prototype.zn;t("ol.control.Control",vg);
+vg.prototype.getMap=vg.prototype.f;vg.prototype.setMap=vg.prototype.setMap;vg.prototype.setTarget=vg.prototype.i;t("ol.control.FullScreen",Mn);t("ol.control.MousePosition",Rn);t("ol.control.MousePosition.render",Sn);Rn.prototype.getCoordinateFormat=Rn.prototype.Qh;Rn.prototype.getProjection=Rn.prototype.si;Rn.prototype.setCoordinateFormat=Rn.prototype.Gj;Rn.prototype.setProjection=Rn.prototype.ti;t("ol.control.OverviewMap",Wn);t("ol.control.OverviewMap.render",Xn);Wn.prototype.getCollapsible=Wn.prototype.Gn;
+Wn.prototype.setCollapsible=Wn.prototype.Jn;Wn.prototype.setCollapsed=Wn.prototype.In;Wn.prototype.getCollapsed=Wn.prototype.Fn;Wn.prototype.getOverviewMap=Wn.prototype.Hl;t("ol.control.Rotate",Cg);t("ol.control.Rotate.render",Dg);t("ol.control.ScaleLine",ao);ao.prototype.getUnits=ao.prototype.C;t("ol.control.ScaleLine.render",bo);ao.prototype.setUnits=ao.prototype.O;t("ol.control.Zoom",Eg);t("ol.control.ZoomSlider",go);t("ol.control.ZoomSlider.render",io);t("ol.control.ZoomToExtent",lo);
+Vc.prototype.changed=Vc.prototype.u;Vc.prototype.dispatchEvent=Vc.prototype.b;Vc.prototype.getRevision=Vc.prototype.K;Vc.prototype.on=Vc.prototype.I;Vc.prototype.once=Vc.prototype.once;Vc.prototype.un=Vc.prototype.J;G.prototype.get=G.prototype.get;G.prototype.getKeys=G.prototype.P;G.prototype.getProperties=G.prototype.L;G.prototype.set=G.prototype.set;G.prototype.setProperties=G.prototype.H;G.prototype.unset=G.prototype.R;G.prototype.changed=G.prototype.u;G.prototype.dispatchEvent=G.prototype.b;
+G.prototype.getRevision=G.prototype.K;G.prototype.on=G.prototype.I;G.prototype.once=G.prototype.once;G.prototype.un=G.prototype.J;H.prototype.addControl=H.prototype.Mf;H.prototype.addInteraction=H.prototype.Nf;H.prototype.addLayer=H.prototype.xe;H.prototype.addOverlay=H.prototype.ye;H.prototype.forEachFeatureAtPixel=H.prototype.Tc;H.prototype.getFeaturesAtPixel=H.prototype.Xf;H.prototype.forEachLayerAtPixel=H.prototype.tg;H.prototype.hasFeatureAtPixel=H.prototype.ng;
+H.prototype.getEventCoordinate=H.prototype.Sd;H.prototype.getEventPixel=H.prototype.ud;H.prototype.getTarget=H.prototype.Xd;H.prototype.getTargetElement=H.prototype.Cc;H.prototype.getCoordinateFromPixel=H.prototype.Ra;H.prototype.getControls=H.prototype.Wf;H.prototype.getOverlays=H.prototype.gg;H.prototype.getOverlayById=H.prototype.fg;H.prototype.getInteractions=H.prototype.bg;H.prototype.getLayerGroup=H.prototype.hc;H.prototype.getLayers=H.prototype.Xe;H.prototype.getPixelFromCoordinate=H.prototype.Ia;
+H.prototype.getSize=H.prototype.Cb;H.prototype.getView=H.prototype.aa;H.prototype.getViewport=H.prototype.kg;H.prototype.renderSync=H.prototype.dh;H.prototype.render=H.prototype.render;H.prototype.removeControl=H.prototype.Xg;H.prototype.removeInteraction=H.prototype.Zg;H.prototype.removeLayer=H.prototype.$g;H.prototype.removeOverlay=H.prototype.ah;H.prototype.setLayerGroup=H.prototype.zf;H.prototype.setSize=H.prototype.be;H.prototype.setTarget=H.prototype.Ad;H.prototype.setView=H.prototype.jh;
+H.prototype.updateSize=H.prototype.Oc;H.prototype.get=H.prototype.get;H.prototype.getKeys=H.prototype.P;H.prototype.getProperties=H.prototype.L;H.prototype.set=H.prototype.set;H.prototype.setProperties=H.prototype.H;H.prototype.unset=H.prototype.R;H.prototype.changed=H.prototype.u;H.prototype.dispatchEvent=H.prototype.b;H.prototype.getRevision=H.prototype.K;H.prototype.on=H.prototype.I;H.prototype.once=H.prototype.once;H.prototype.un=H.prototype.J;B.prototype.get=B.prototype.get;
+B.prototype.getKeys=B.prototype.P;B.prototype.getProperties=B.prototype.L;B.prototype.set=B.prototype.set;B.prototype.setProperties=B.prototype.H;B.prototype.unset=B.prototype.R;B.prototype.changed=B.prototype.u;B.prototype.dispatchEvent=B.prototype.b;B.prototype.getRevision=B.prototype.K;B.prototype.on=B.prototype.I;B.prototype.once=B.prototype.once;B.prototype.un=B.prototype.J;cd.prototype.type=cd.prototype.type;cd.prototype.target=cd.prototype.target;cd.prototype.preventDefault=cd.prototype.preventDefault;
+cd.prototype.stopPropagation=cd.prototype.stopPropagation;pk.prototype.get=pk.prototype.get;pk.prototype.getKeys=pk.prototype.P;pk.prototype.getProperties=pk.prototype.L;pk.prototype.set=pk.prototype.set;pk.prototype.setProperties=pk.prototype.H;pk.prototype.unset=pk.prototype.R;pk.prototype.changed=pk.prototype.u;pk.prototype.dispatchEvent=pk.prototype.b;pk.prototype.getRevision=pk.prototype.K;pk.prototype.on=pk.prototype.I;pk.prototype.once=pk.prototype.once;pk.prototype.un=pk.prototype.J;
+Hk.prototype.get=Hk.prototype.get;Hk.prototype.getKeys=Hk.prototype.P;Hk.prototype.getProperties=Hk.prototype.L;Hk.prototype.set=Hk.prototype.set;Hk.prototype.setProperties=Hk.prototype.H;Hk.prototype.unset=Hk.prototype.R;Hk.prototype.changed=Hk.prototype.u;Hk.prototype.dispatchEvent=Hk.prototype.b;Hk.prototype.getRevision=Hk.prototype.K;Hk.prototype.on=Hk.prototype.I;Hk.prototype.once=Hk.prototype.once;Hk.prototype.un=Hk.prototype.J;Jk.prototype.get=Jk.prototype.get;Jk.prototype.getKeys=Jk.prototype.P;
+Jk.prototype.getProperties=Jk.prototype.L;Jk.prototype.set=Jk.prototype.set;Jk.prototype.setProperties=Jk.prototype.H;Jk.prototype.unset=Jk.prototype.R;Jk.prototype.changed=Jk.prototype.u;Jk.prototype.dispatchEvent=Jk.prototype.b;Jk.prototype.getRevision=Jk.prototype.K;Jk.prototype.on=Jk.prototype.I;Jk.prototype.once=Jk.prototype.once;Jk.prototype.un=Jk.prototype.J;el.prototype.getTileCoord=el.prototype.i;el.prototype.load=el.prototype.load;K.prototype.addControl=K.prototype.Mf;
+K.prototype.addInteraction=K.prototype.Nf;K.prototype.addLayer=K.prototype.xe;K.prototype.addOverlay=K.prototype.ye;K.prototype.forEachFeatureAtPixel=K.prototype.Tc;K.prototype.getFeaturesAtPixel=K.prototype.Xf;K.prototype.forEachLayerAtPixel=K.prototype.tg;K.prototype.hasFeatureAtPixel=K.prototype.ng;K.prototype.getEventCoordinate=K.prototype.Sd;K.prototype.getEventPixel=K.prototype.ud;K.prototype.getTarget=K.prototype.Xd;K.prototype.getTargetElement=K.prototype.Cc;
+K.prototype.getCoordinateFromPixel=K.prototype.Ra;K.prototype.getControls=K.prototype.Wf;K.prototype.getOverlays=K.prototype.gg;K.prototype.getOverlayById=K.prototype.fg;K.prototype.getInteractions=K.prototype.bg;K.prototype.getLayerGroup=K.prototype.hc;K.prototype.getLayers=K.prototype.Xe;K.prototype.getPixelFromCoordinate=K.prototype.Ia;K.prototype.getSize=K.prototype.Cb;K.prototype.getView=K.prototype.aa;K.prototype.getViewport=K.prototype.kg;K.prototype.renderSync=K.prototype.dh;
+K.prototype.render=K.prototype.render;K.prototype.removeControl=K.prototype.Xg;K.prototype.removeInteraction=K.prototype.Zg;K.prototype.removeLayer=K.prototype.$g;K.prototype.removeOverlay=K.prototype.ah;K.prototype.setLayerGroup=K.prototype.zf;K.prototype.setSize=K.prototype.be;K.prototype.setTarget=K.prototype.Ad;K.prototype.setView=K.prototype.jh;K.prototype.updateSize=K.prototype.Oc;K.prototype.get=K.prototype.get;K.prototype.getKeys=K.prototype.P;K.prototype.getProperties=K.prototype.L;
+K.prototype.set=K.prototype.set;K.prototype.setProperties=K.prototype.H;K.prototype.unset=K.prototype.R;K.prototype.changed=K.prototype.u;K.prototype.dispatchEvent=K.prototype.b;K.prototype.getRevision=K.prototype.K;K.prototype.on=K.prototype.I;K.prototype.once=K.prototype.once;K.prototype.un=K.prototype.J;dd.prototype.type=dd.prototype.type;dd.prototype.target=dd.prototype.target;dd.prototype.preventDefault=dd.prototype.preventDefault;dd.prototype.stopPropagation=dd.prototype.stopPropagation;
+ed.prototype.map=ed.prototype.map;ed.prototype.frameState=ed.prototype.frameState;ed.prototype.type=ed.prototype.type;ed.prototype.target=ed.prototype.target;ed.prototype.preventDefault=ed.prototype.preventDefault;ed.prototype.stopPropagation=ed.prototype.stopPropagation;Ad.prototype.originalEvent=Ad.prototype.originalEvent;Ad.prototype.pixel=Ad.prototype.pixel;Ad.prototype.coordinate=Ad.prototype.coordinate;Ad.prototype.dragging=Ad.prototype.dragging;Ad.prototype.preventDefault=Ad.prototype.preventDefault;
+Ad.prototype.stopPropagation=Ad.prototype.stopPropagation;Ad.prototype.map=Ad.prototype.map;Ad.prototype.frameState=Ad.prototype.frameState;Ad.prototype.type=Ad.prototype.type;Ad.prototype.target=Ad.prototype.target;Zc.prototype.type=Zc.prototype.type;Zc.prototype.target=Zc.prototype.target;Zc.prototype.preventDefault=Zc.prototype.preventDefault;Zc.prototype.stopPropagation=Zc.prototype.stopPropagation;Bn.prototype.get=Bn.prototype.get;Bn.prototype.getKeys=Bn.prototype.P;
+Bn.prototype.getProperties=Bn.prototype.L;Bn.prototype.set=Bn.prototype.set;Bn.prototype.setProperties=Bn.prototype.H;Bn.prototype.unset=Bn.prototype.R;Bn.prototype.changed=Bn.prototype.u;Bn.prototype.dispatchEvent=Bn.prototype.b;Bn.prototype.getRevision=Bn.prototype.K;Bn.prototype.on=Bn.prototype.I;Bn.prototype.once=Bn.prototype.once;Bn.prototype.un=Bn.prototype.J;pz.prototype.getTileCoord=pz.prototype.i;pz.prototype.load=pz.prototype.load;Kn.prototype.getTileCoord=Kn.prototype.i;
+Kn.prototype.load=Kn.prototype.load;F.prototype.get=F.prototype.get;F.prototype.getKeys=F.prototype.P;F.prototype.getProperties=F.prototype.L;F.prototype.set=F.prototype.set;F.prototype.setProperties=F.prototype.H;F.prototype.unset=F.prototype.R;F.prototype.changed=F.prototype.u;F.prototype.dispatchEvent=F.prototype.b;F.prototype.getRevision=F.prototype.K;F.prototype.on=F.prototype.I;F.prototype.once=F.prototype.once;F.prototype.un=F.prototype.J;sz.prototype.forEachTileCoord=sz.prototype.Vf;
+sz.prototype.getMaxZoom=sz.prototype.mj;sz.prototype.getMinZoom=sz.prototype.nj;sz.prototype.getOrigin=sz.prototype.Ic;sz.prototype.getResolution=sz.prototype.Ta;sz.prototype.getResolutions=sz.prototype.oj;sz.prototype.getTileCoordExtent=sz.prototype.Ma;sz.prototype.getTileCoordForCoordAndResolution=sz.prototype.Le;sz.prototype.getTileCoordForCoordAndZ=sz.prototype.jg;sz.prototype.getTileSize=sz.prototype.Za;sz.prototype.getZForResolution=sz.prototype.Dc;wk.prototype.getOpacity=wk.prototype.hf;
+wk.prototype.getRotateWithView=wk.prototype.jf;wk.prototype.getRotation=wk.prototype.kf;wk.prototype.getScale=wk.prototype.lf;wk.prototype.getSnapToPixel=wk.prototype.Ke;wk.prototype.setOpacity=wk.prototype.Ed;wk.prototype.setRotation=wk.prototype.mf;wk.prototype.setScale=wk.prototype.Fd;yk.prototype.clone=yk.prototype.clone;yk.prototype.getAngle=yk.prototype.ij;yk.prototype.getFill=yk.prototype.Fa;yk.prototype.getPoints=yk.prototype.jj;yk.prototype.getRadius=yk.prototype.kj;
+yk.prototype.getRadius2=yk.prototype.Zh;yk.prototype.getStroke=yk.prototype.Ga;yk.prototype.getOpacity=yk.prototype.hf;yk.prototype.getRotateWithView=yk.prototype.jf;yk.prototype.getRotation=yk.prototype.kf;yk.prototype.getScale=yk.prototype.lf;yk.prototype.getSnapToPixel=yk.prototype.Ke;yk.prototype.setOpacity=yk.prototype.Ed;yk.prototype.setRotation=yk.prototype.mf;yk.prototype.setScale=yk.prototype.Fd;dr.prototype.getOpacity=dr.prototype.hf;dr.prototype.getRotateWithView=dr.prototype.jf;
+dr.prototype.getRotation=dr.prototype.kf;dr.prototype.getScale=dr.prototype.lf;dr.prototype.getSnapToPixel=dr.prototype.Ke;dr.prototype.setOpacity=dr.prototype.Ed;dr.prototype.setRotation=dr.prototype.mf;dr.prototype.setScale=dr.prototype.Fd;uw.prototype.get=uw.prototype.get;uw.prototype.getKeys=uw.prototype.P;uw.prototype.getProperties=uw.prototype.L;uw.prototype.set=uw.prototype.set;uw.prototype.setProperties=uw.prototype.H;uw.prototype.unset=uw.prototype.R;uw.prototype.changed=uw.prototype.u;
+uw.prototype.dispatchEvent=uw.prototype.b;uw.prototype.getRevision=uw.prototype.K;uw.prototype.on=uw.prototype.I;uw.prototype.once=uw.prototype.once;uw.prototype.un=uw.prototype.J;iy.prototype.getAttributions=iy.prototype.za;iy.prototype.getLogo=iy.prototype.Aa;iy.prototype.getProjection=iy.prototype.Da;iy.prototype.getState=iy.prototype.getState;iy.prototype.refresh=iy.prototype.sa;iy.prototype.setAttributions=iy.prototype.va;iy.prototype.get=iy.prototype.get;iy.prototype.getKeys=iy.prototype.P;
+iy.prototype.getProperties=iy.prototype.L;iy.prototype.set=iy.prototype.set;iy.prototype.setProperties=iy.prototype.H;iy.prototype.unset=iy.prototype.R;iy.prototype.changed=iy.prototype.u;iy.prototype.dispatchEvent=iy.prototype.b;iy.prototype.getRevision=iy.prototype.K;iy.prototype.on=iy.prototype.I;iy.prototype.once=iy.prototype.once;iy.prototype.un=iy.prototype.J;my.prototype.getTileGrid=my.prototype.jb;my.prototype.refresh=my.prototype.sa;my.prototype.getAttributions=my.prototype.za;
+my.prototype.getLogo=my.prototype.Aa;my.prototype.getProjection=my.prototype.Da;my.prototype.getState=my.prototype.getState;my.prototype.setAttributions=my.prototype.va;my.prototype.get=my.prototype.get;my.prototype.getKeys=my.prototype.P;my.prototype.getProperties=my.prototype.L;my.prototype.set=my.prototype.set;my.prototype.setProperties=my.prototype.H;my.prototype.unset=my.prototype.R;my.prototype.changed=my.prototype.u;my.prototype.dispatchEvent=my.prototype.b;my.prototype.getRevision=my.prototype.K;
+my.prototype.on=my.prototype.I;my.prototype.once=my.prototype.once;my.prototype.un=my.prototype.J;ny.prototype.getTileLoadFunction=ny.prototype.yb;ny.prototype.getTileUrlFunction=ny.prototype.zb;ny.prototype.getUrls=ny.prototype.Ab;ny.prototype.setTileLoadFunction=ny.prototype.Fb;ny.prototype.setTileUrlFunction=ny.prototype.hb;ny.prototype.setUrl=ny.prototype.rb;ny.prototype.setUrls=ny.prototype.vb;ny.prototype.getTileGrid=ny.prototype.jb;ny.prototype.refresh=ny.prototype.sa;
+ny.prototype.getAttributions=ny.prototype.za;ny.prototype.getLogo=ny.prototype.Aa;ny.prototype.getProjection=ny.prototype.Da;ny.prototype.getState=ny.prototype.getState;ny.prototype.setAttributions=ny.prototype.va;ny.prototype.get=ny.prototype.get;ny.prototype.getKeys=ny.prototype.P;ny.prototype.getProperties=ny.prototype.L;ny.prototype.set=ny.prototype.set;ny.prototype.setProperties=ny.prototype.H;ny.prototype.unset=ny.prototype.R;ny.prototype.changed=ny.prototype.u;ny.prototype.dispatchEvent=ny.prototype.b;
+ny.prototype.getRevision=ny.prototype.K;ny.prototype.on=ny.prototype.I;ny.prototype.once=ny.prototype.once;ny.prototype.un=ny.prototype.J;ry.prototype.setRenderReprojectionEdges=ry.prototype.Qb;ry.prototype.setTileGridForProjection=ry.prototype.Rb;ry.prototype.getTileLoadFunction=ry.prototype.yb;ry.prototype.getTileUrlFunction=ry.prototype.zb;ry.prototype.getUrls=ry.prototype.Ab;ry.prototype.setTileLoadFunction=ry.prototype.Fb;ry.prototype.setTileUrlFunction=ry.prototype.hb;ry.prototype.setUrl=ry.prototype.rb;
+ry.prototype.setUrls=ry.prototype.vb;ry.prototype.getTileGrid=ry.prototype.jb;ry.prototype.refresh=ry.prototype.sa;ry.prototype.getAttributions=ry.prototype.za;ry.prototype.getLogo=ry.prototype.Aa;ry.prototype.getProjection=ry.prototype.Da;ry.prototype.getState=ry.prototype.getState;ry.prototype.setAttributions=ry.prototype.va;ry.prototype.get=ry.prototype.get;ry.prototype.getKeys=ry.prototype.P;ry.prototype.getProperties=ry.prototype.L;ry.prototype.set=ry.prototype.set;
+ry.prototype.setProperties=ry.prototype.H;ry.prototype.unset=ry.prototype.R;ry.prototype.changed=ry.prototype.u;ry.prototype.dispatchEvent=ry.prototype.b;ry.prototype.getRevision=ry.prototype.K;ry.prototype.on=ry.prototype.I;ry.prototype.once=ry.prototype.once;ry.prototype.un=ry.prototype.J;sy.prototype.setRenderReprojectionEdges=sy.prototype.Qb;sy.prototype.setTileGridForProjection=sy.prototype.Rb;sy.prototype.getTileLoadFunction=sy.prototype.yb;sy.prototype.getTileUrlFunction=sy.prototype.zb;
+sy.prototype.getUrls=sy.prototype.Ab;sy.prototype.setTileLoadFunction=sy.prototype.Fb;sy.prototype.setTileUrlFunction=sy.prototype.hb;sy.prototype.setUrl=sy.prototype.rb;sy.prototype.setUrls=sy.prototype.vb;sy.prototype.getTileGrid=sy.prototype.jb;sy.prototype.refresh=sy.prototype.sa;sy.prototype.getAttributions=sy.prototype.za;sy.prototype.getLogo=sy.prototype.Aa;sy.prototype.getProjection=sy.prototype.Da;sy.prototype.getState=sy.prototype.getState;sy.prototype.setAttributions=sy.prototype.va;
+sy.prototype.get=sy.prototype.get;sy.prototype.getKeys=sy.prototype.P;sy.prototype.getProperties=sy.prototype.L;sy.prototype.set=sy.prototype.set;sy.prototype.setProperties=sy.prototype.H;sy.prototype.unset=sy.prototype.R;sy.prototype.changed=sy.prototype.u;sy.prototype.dispatchEvent=sy.prototype.b;sy.prototype.getRevision=sy.prototype.K;sy.prototype.on=sy.prototype.I;sy.prototype.once=sy.prototype.once;sy.prototype.un=sy.prototype.J;ty.prototype.setRenderReprojectionEdges=ty.prototype.Qb;
+ty.prototype.setTileGridForProjection=ty.prototype.Rb;ty.prototype.getTileLoadFunction=ty.prototype.yb;ty.prototype.getTileUrlFunction=ty.prototype.zb;ty.prototype.getUrls=ty.prototype.Ab;ty.prototype.setTileLoadFunction=ty.prototype.Fb;ty.prototype.setTileUrlFunction=ty.prototype.hb;ty.prototype.setUrl=ty.prototype.rb;ty.prototype.setUrls=ty.prototype.vb;ty.prototype.getTileGrid=ty.prototype.jb;ty.prototype.refresh=ty.prototype.sa;ty.prototype.getAttributions=ty.prototype.za;
+ty.prototype.getLogo=ty.prototype.Aa;ty.prototype.getProjection=ty.prototype.Da;ty.prototype.getState=ty.prototype.getState;ty.prototype.setAttributions=ty.prototype.va;ty.prototype.get=ty.prototype.get;ty.prototype.getKeys=ty.prototype.P;ty.prototype.getProperties=ty.prototype.L;ty.prototype.set=ty.prototype.set;ty.prototype.setProperties=ty.prototype.H;ty.prototype.unset=ty.prototype.R;ty.prototype.changed=ty.prototype.u;ty.prototype.dispatchEvent=ty.prototype.b;ty.prototype.getRevision=ty.prototype.K;
+ty.prototype.on=ty.prototype.I;ty.prototype.once=ty.prototype.once;ty.prototype.un=ty.prototype.J;U.prototype.getAttributions=U.prototype.za;U.prototype.getLogo=U.prototype.Aa;U.prototype.getProjection=U.prototype.Da;U.prototype.getState=U.prototype.getState;U.prototype.refresh=U.prototype.sa;U.prototype.setAttributions=U.prototype.va;U.prototype.get=U.prototype.get;U.prototype.getKeys=U.prototype.P;U.prototype.getProperties=U.prototype.L;U.prototype.set=U.prototype.set;
+U.prototype.setProperties=U.prototype.H;U.prototype.unset=U.prototype.R;U.prototype.changed=U.prototype.u;U.prototype.dispatchEvent=U.prototype.b;U.prototype.getRevision=U.prototype.K;U.prototype.on=U.prototype.I;U.prototype.once=U.prototype.once;U.prototype.un=U.prototype.J;X.prototype.addFeature=X.prototype.Gb;X.prototype.addFeatures=X.prototype.Qc;X.prototype.clear=X.prototype.clear;X.prototype.forEachFeature=X.prototype.Lh;X.prototype.forEachFeatureInExtent=X.prototype.ec;
+X.prototype.forEachFeatureIntersectingExtent=X.prototype.Mh;X.prototype.getFeaturesCollection=X.prototype.Th;X.prototype.getFeatures=X.prototype.ee;X.prototype.getFeaturesAtCoordinate=X.prototype.Sh;X.prototype.getFeaturesInExtent=X.prototype.Yf;X.prototype.getClosestFeatureToCoordinate=X.prototype.Oh;X.prototype.getExtent=X.prototype.G;X.prototype.getFeatureById=X.prototype.Rh;X.prototype.getFormat=X.prototype.ej;X.prototype.getUrl=X.prototype.fj;X.prototype.removeLoadedExtent=X.prototype.Cj;
+X.prototype.removeFeature=X.prototype.Lb;X.prototype.setLoader=X.prototype.hj;X.prototype.getAttributions=X.prototype.za;X.prototype.getLogo=X.prototype.Aa;X.prototype.getProjection=X.prototype.Da;X.prototype.getState=X.prototype.getState;X.prototype.refresh=X.prototype.sa;X.prototype.setAttributions=X.prototype.va;X.prototype.get=X.prototype.get;X.prototype.getKeys=X.prototype.P;X.prototype.getProperties=X.prototype.L;X.prototype.set=X.prototype.set;X.prototype.setProperties=X.prototype.H;
+X.prototype.unset=X.prototype.R;X.prototype.changed=X.prototype.u;X.prototype.dispatchEvent=X.prototype.b;X.prototype.getRevision=X.prototype.K;X.prototype.on=X.prototype.I;X.prototype.once=X.prototype.once;X.prototype.un=X.prototype.J;zy.prototype.getAttributions=zy.prototype.za;zy.prototype.getLogo=zy.prototype.Aa;zy.prototype.getProjection=zy.prototype.Da;zy.prototype.getState=zy.prototype.getState;zy.prototype.refresh=zy.prototype.sa;zy.prototype.setAttributions=zy.prototype.va;
+zy.prototype.get=zy.prototype.get;zy.prototype.getKeys=zy.prototype.P;zy.prototype.getProperties=zy.prototype.L;zy.prototype.set=zy.prototype.set;zy.prototype.setProperties=zy.prototype.H;zy.prototype.unset=zy.prototype.R;zy.prototype.changed=zy.prototype.u;zy.prototype.dispatchEvent=zy.prototype.b;zy.prototype.getRevision=zy.prototype.K;zy.prototype.on=zy.prototype.I;zy.prototype.once=zy.prototype.once;zy.prototype.un=zy.prototype.J;By.prototype.type=By.prototype.type;By.prototype.target=By.prototype.target;
+By.prototype.preventDefault=By.prototype.preventDefault;By.prototype.stopPropagation=By.prototype.stopPropagation;Hy.prototype.getAttributions=Hy.prototype.za;Hy.prototype.getLogo=Hy.prototype.Aa;Hy.prototype.getProjection=Hy.prototype.Da;Hy.prototype.getState=Hy.prototype.getState;Hy.prototype.refresh=Hy.prototype.sa;Hy.prototype.setAttributions=Hy.prototype.va;Hy.prototype.get=Hy.prototype.get;Hy.prototype.getKeys=Hy.prototype.P;Hy.prototype.getProperties=Hy.prototype.L;Hy.prototype.set=Hy.prototype.set;
+Hy.prototype.setProperties=Hy.prototype.H;Hy.prototype.unset=Hy.prototype.R;Hy.prototype.changed=Hy.prototype.u;Hy.prototype.dispatchEvent=Hy.prototype.b;Hy.prototype.getRevision=Hy.prototype.K;Hy.prototype.on=Hy.prototype.I;Hy.prototype.once=Hy.prototype.once;Hy.prototype.un=Hy.prototype.J;Iy.prototype.getAttributions=Iy.prototype.za;Iy.prototype.getLogo=Iy.prototype.Aa;Iy.prototype.getProjection=Iy.prototype.Da;Iy.prototype.getState=Iy.prototype.getState;Iy.prototype.refresh=Iy.prototype.sa;
+Iy.prototype.setAttributions=Iy.prototype.va;Iy.prototype.get=Iy.prototype.get;Iy.prototype.getKeys=Iy.prototype.P;Iy.prototype.getProperties=Iy.prototype.L;Iy.prototype.set=Iy.prototype.set;Iy.prototype.setProperties=Iy.prototype.H;Iy.prototype.unset=Iy.prototype.R;Iy.prototype.changed=Iy.prototype.u;Iy.prototype.dispatchEvent=Iy.prototype.b;Iy.prototype.getRevision=Iy.prototype.K;Iy.prototype.on=Iy.prototype.I;Iy.prototype.once=Iy.prototype.once;Iy.prototype.un=Iy.prototype.J;
+Jy.prototype.getAttributions=Jy.prototype.za;Jy.prototype.getLogo=Jy.prototype.Aa;Jy.prototype.getProjection=Jy.prototype.Da;Jy.prototype.getState=Jy.prototype.getState;Jy.prototype.refresh=Jy.prototype.sa;Jy.prototype.setAttributions=Jy.prototype.va;Jy.prototype.get=Jy.prototype.get;Jy.prototype.getKeys=Jy.prototype.P;Jy.prototype.getProperties=Jy.prototype.L;Jy.prototype.set=Jy.prototype.set;Jy.prototype.setProperties=Jy.prototype.H;Jy.prototype.unset=Jy.prototype.R;Jy.prototype.changed=Jy.prototype.u;
+Jy.prototype.dispatchEvent=Jy.prototype.b;Jy.prototype.getRevision=Jy.prototype.K;Jy.prototype.on=Jy.prototype.I;Jy.prototype.once=Jy.prototype.once;Jy.prototype.un=Jy.prototype.J;Ky.prototype.getAttributions=Ky.prototype.za;Ky.prototype.getLogo=Ky.prototype.Aa;Ky.prototype.getProjection=Ky.prototype.Da;Ky.prototype.getState=Ky.prototype.getState;Ky.prototype.refresh=Ky.prototype.sa;Ky.prototype.setAttributions=Ky.prototype.va;Ky.prototype.get=Ky.prototype.get;Ky.prototype.getKeys=Ky.prototype.P;
+Ky.prototype.getProperties=Ky.prototype.L;Ky.prototype.set=Ky.prototype.set;Ky.prototype.setProperties=Ky.prototype.H;Ky.prototype.unset=Ky.prototype.R;Ky.prototype.changed=Ky.prototype.u;Ky.prototype.dispatchEvent=Ky.prototype.b;Ky.prototype.getRevision=Ky.prototype.K;Ky.prototype.on=Ky.prototype.I;Ky.prototype.once=Ky.prototype.once;Ky.prototype.un=Ky.prototype.J;Ly.prototype.getAttributions=Ly.prototype.za;Ly.prototype.getLogo=Ly.prototype.Aa;Ly.prototype.getProjection=Ly.prototype.Da;
+Ly.prototype.getState=Ly.prototype.getState;Ly.prototype.refresh=Ly.prototype.sa;Ly.prototype.setAttributions=Ly.prototype.va;Ly.prototype.get=Ly.prototype.get;Ly.prototype.getKeys=Ly.prototype.P;Ly.prototype.getProperties=Ly.prototype.L;Ly.prototype.set=Ly.prototype.set;Ly.prototype.setProperties=Ly.prototype.H;Ly.prototype.unset=Ly.prototype.R;Ly.prototype.changed=Ly.prototype.u;Ly.prototype.dispatchEvent=Ly.prototype.b;Ly.prototype.getRevision=Ly.prototype.K;Ly.prototype.on=Ly.prototype.I;
+Ly.prototype.once=Ly.prototype.once;Ly.prototype.un=Ly.prototype.J;Ny.prototype.getAttributions=Ny.prototype.za;Ny.prototype.getLogo=Ny.prototype.Aa;Ny.prototype.getProjection=Ny.prototype.Da;Ny.prototype.getState=Ny.prototype.getState;Ny.prototype.refresh=Ny.prototype.sa;Ny.prototype.setAttributions=Ny.prototype.va;Ny.prototype.get=Ny.prototype.get;Ny.prototype.getKeys=Ny.prototype.P;Ny.prototype.getProperties=Ny.prototype.L;Ny.prototype.set=Ny.prototype.set;Ny.prototype.setProperties=Ny.prototype.H;
+Ny.prototype.unset=Ny.prototype.R;Ny.prototype.changed=Ny.prototype.u;Ny.prototype.dispatchEvent=Ny.prototype.b;Ny.prototype.getRevision=Ny.prototype.K;Ny.prototype.on=Ny.prototype.I;Ny.prototype.once=Ny.prototype.once;Ny.prototype.un=Ny.prototype.J;Ry.prototype.setRenderReprojectionEdges=Ry.prototype.Qb;Ry.prototype.setTileGridForProjection=Ry.prototype.Rb;Ry.prototype.getTileLoadFunction=Ry.prototype.yb;Ry.prototype.getTileUrlFunction=Ry.prototype.zb;Ry.prototype.getUrls=Ry.prototype.Ab;
+Ry.prototype.setTileLoadFunction=Ry.prototype.Fb;Ry.prototype.setTileUrlFunction=Ry.prototype.hb;Ry.prototype.setUrl=Ry.prototype.rb;Ry.prototype.setUrls=Ry.prototype.vb;Ry.prototype.getTileGrid=Ry.prototype.jb;Ry.prototype.refresh=Ry.prototype.sa;Ry.prototype.getAttributions=Ry.prototype.za;Ry.prototype.getLogo=Ry.prototype.Aa;Ry.prototype.getProjection=Ry.prototype.Da;Ry.prototype.getState=Ry.prototype.getState;Ry.prototype.setAttributions=Ry.prototype.va;Ry.prototype.get=Ry.prototype.get;
+Ry.prototype.getKeys=Ry.prototype.P;Ry.prototype.getProperties=Ry.prototype.L;Ry.prototype.set=Ry.prototype.set;Ry.prototype.setProperties=Ry.prototype.H;Ry.prototype.unset=Ry.prototype.R;Ry.prototype.changed=Ry.prototype.u;Ry.prototype.dispatchEvent=Ry.prototype.b;Ry.prototype.getRevision=Ry.prototype.K;Ry.prototype.on=Ry.prototype.I;Ry.prototype.once=Ry.prototype.once;Ry.prototype.un=Ry.prototype.J;Sy.prototype.getAttributions=Sy.prototype.za;Sy.prototype.getLogo=Sy.prototype.Aa;
+Sy.prototype.getProjection=Sy.prototype.Da;Sy.prototype.getState=Sy.prototype.getState;Sy.prototype.refresh=Sy.prototype.sa;Sy.prototype.setAttributions=Sy.prototype.va;Sy.prototype.get=Sy.prototype.get;Sy.prototype.getKeys=Sy.prototype.P;Sy.prototype.getProperties=Sy.prototype.L;Sy.prototype.set=Sy.prototype.set;Sy.prototype.setProperties=Sy.prototype.H;Sy.prototype.unset=Sy.prototype.R;Sy.prototype.changed=Sy.prototype.u;Sy.prototype.dispatchEvent=Sy.prototype.b;Sy.prototype.getRevision=Sy.prototype.K;
+Sy.prototype.on=Sy.prototype.I;Sy.prototype.once=Sy.prototype.once;Sy.prototype.un=Sy.prototype.J;Wy.prototype.type=Wy.prototype.type;Wy.prototype.target=Wy.prototype.target;Wy.prototype.preventDefault=Wy.prototype.preventDefault;Wy.prototype.stopPropagation=Wy.prototype.stopPropagation;Zy.prototype.setRenderReprojectionEdges=Zy.prototype.Qb;Zy.prototype.setTileGridForProjection=Zy.prototype.Rb;Zy.prototype.getTileLoadFunction=Zy.prototype.yb;Zy.prototype.getTileUrlFunction=Zy.prototype.zb;
+Zy.prototype.getUrls=Zy.prototype.Ab;Zy.prototype.setTileLoadFunction=Zy.prototype.Fb;Zy.prototype.setTileUrlFunction=Zy.prototype.hb;Zy.prototype.setUrl=Zy.prototype.rb;Zy.prototype.setUrls=Zy.prototype.vb;Zy.prototype.getTileGrid=Zy.prototype.jb;Zy.prototype.refresh=Zy.prototype.sa;Zy.prototype.getAttributions=Zy.prototype.za;Zy.prototype.getLogo=Zy.prototype.Aa;Zy.prototype.getProjection=Zy.prototype.Da;Zy.prototype.getState=Zy.prototype.getState;Zy.prototype.setAttributions=Zy.prototype.va;
+Zy.prototype.get=Zy.prototype.get;Zy.prototype.getKeys=Zy.prototype.P;Zy.prototype.getProperties=Zy.prototype.L;Zy.prototype.set=Zy.prototype.set;Zy.prototype.setProperties=Zy.prototype.H;Zy.prototype.unset=Zy.prototype.R;Zy.prototype.changed=Zy.prototype.u;Zy.prototype.dispatchEvent=Zy.prototype.b;Zy.prototype.getRevision=Zy.prototype.K;Zy.prototype.on=Zy.prototype.I;Zy.prototype.once=Zy.prototype.once;Zy.prototype.un=Zy.prototype.J;ly.prototype.type=ly.prototype.type;ly.prototype.target=ly.prototype.target;
+ly.prototype.preventDefault=ly.prototype.preventDefault;ly.prototype.stopPropagation=ly.prototype.stopPropagation;cz.prototype.setRenderReprojectionEdges=cz.prototype.Qb;cz.prototype.setTileGridForProjection=cz.prototype.Rb;cz.prototype.getTileLoadFunction=cz.prototype.yb;cz.prototype.getTileUrlFunction=cz.prototype.zb;cz.prototype.getUrls=cz.prototype.Ab;cz.prototype.setTileLoadFunction=cz.prototype.Fb;cz.prototype.setTileUrlFunction=cz.prototype.hb;cz.prototype.setUrl=cz.prototype.rb;
+cz.prototype.setUrls=cz.prototype.vb;cz.prototype.getTileGrid=cz.prototype.jb;cz.prototype.refresh=cz.prototype.sa;cz.prototype.getAttributions=cz.prototype.za;cz.prototype.getLogo=cz.prototype.Aa;cz.prototype.getProjection=cz.prototype.Da;cz.prototype.getState=cz.prototype.getState;cz.prototype.setAttributions=cz.prototype.va;cz.prototype.get=cz.prototype.get;cz.prototype.getKeys=cz.prototype.P;cz.prototype.getProperties=cz.prototype.L;cz.prototype.set=cz.prototype.set;
+cz.prototype.setProperties=cz.prototype.H;cz.prototype.unset=cz.prototype.R;cz.prototype.changed=cz.prototype.u;cz.prototype.dispatchEvent=cz.prototype.b;cz.prototype.getRevision=cz.prototype.K;cz.prototype.on=cz.prototype.I;cz.prototype.once=cz.prototype.once;cz.prototype.un=cz.prototype.J;ez.prototype.getTileGrid=ez.prototype.jb;ez.prototype.refresh=ez.prototype.sa;ez.prototype.getAttributions=ez.prototype.za;ez.prototype.getLogo=ez.prototype.Aa;ez.prototype.getProjection=ez.prototype.Da;
+ez.prototype.getState=ez.prototype.getState;ez.prototype.setAttributions=ez.prototype.va;ez.prototype.get=ez.prototype.get;ez.prototype.getKeys=ez.prototype.P;ez.prototype.getProperties=ez.prototype.L;ez.prototype.set=ez.prototype.set;ez.prototype.setProperties=ez.prototype.H;ez.prototype.unset=ez.prototype.R;ez.prototype.changed=ez.prototype.u;ez.prototype.dispatchEvent=ez.prototype.b;ez.prototype.getRevision=ez.prototype.K;ez.prototype.on=ez.prototype.I;ez.prototype.once=ez.prototype.once;
+ez.prototype.un=ez.prototype.J;gz.prototype.setRenderReprojectionEdges=gz.prototype.Qb;gz.prototype.setTileGridForProjection=gz.prototype.Rb;gz.prototype.getTileLoadFunction=gz.prototype.yb;gz.prototype.getTileUrlFunction=gz.prototype.zb;gz.prototype.getUrls=gz.prototype.Ab;gz.prototype.setTileLoadFunction=gz.prototype.Fb;gz.prototype.setTileUrlFunction=gz.prototype.hb;gz.prototype.setUrl=gz.prototype.rb;gz.prototype.setUrls=gz.prototype.vb;gz.prototype.getTileGrid=gz.prototype.jb;
+gz.prototype.refresh=gz.prototype.sa;gz.prototype.getAttributions=gz.prototype.za;gz.prototype.getLogo=gz.prototype.Aa;gz.prototype.getProjection=gz.prototype.Da;gz.prototype.getState=gz.prototype.getState;gz.prototype.setAttributions=gz.prototype.va;gz.prototype.get=gz.prototype.get;gz.prototype.getKeys=gz.prototype.P;gz.prototype.getProperties=gz.prototype.L;gz.prototype.set=gz.prototype.set;gz.prototype.setProperties=gz.prototype.H;gz.prototype.unset=gz.prototype.R;gz.prototype.changed=gz.prototype.u;
+gz.prototype.dispatchEvent=gz.prototype.b;gz.prototype.getRevision=gz.prototype.K;gz.prototype.on=gz.prototype.I;gz.prototype.once=gz.prototype.once;gz.prototype.un=gz.prototype.J;hz.prototype.getTileGrid=hz.prototype.jb;hz.prototype.refresh=hz.prototype.sa;hz.prototype.getAttributions=hz.prototype.za;hz.prototype.getLogo=hz.prototype.Aa;hz.prototype.getProjection=hz.prototype.Da;hz.prototype.getState=hz.prototype.getState;hz.prototype.setAttributions=hz.prototype.va;hz.prototype.get=hz.prototype.get;
+hz.prototype.getKeys=hz.prototype.P;hz.prototype.getProperties=hz.prototype.L;hz.prototype.set=hz.prototype.set;hz.prototype.setProperties=hz.prototype.H;hz.prototype.unset=hz.prototype.R;hz.prototype.changed=hz.prototype.u;hz.prototype.dispatchEvent=hz.prototype.b;hz.prototype.getRevision=hz.prototype.K;hz.prototype.on=hz.prototype.I;hz.prototype.once=hz.prototype.once;hz.prototype.un=hz.prototype.J;lz.prototype.setRenderReprojectionEdges=lz.prototype.Qb;lz.prototype.setTileGridForProjection=lz.prototype.Rb;
+lz.prototype.getTileLoadFunction=lz.prototype.yb;lz.prototype.getTileUrlFunction=lz.prototype.zb;lz.prototype.getUrls=lz.prototype.Ab;lz.prototype.setTileLoadFunction=lz.prototype.Fb;lz.prototype.setTileUrlFunction=lz.prototype.hb;lz.prototype.setUrl=lz.prototype.rb;lz.prototype.setUrls=lz.prototype.vb;lz.prototype.getTileGrid=lz.prototype.jb;lz.prototype.refresh=lz.prototype.sa;lz.prototype.getAttributions=lz.prototype.za;lz.prototype.getLogo=lz.prototype.Aa;lz.prototype.getProjection=lz.prototype.Da;
+lz.prototype.getState=lz.prototype.getState;lz.prototype.setAttributions=lz.prototype.va;lz.prototype.get=lz.prototype.get;lz.prototype.getKeys=lz.prototype.P;lz.prototype.getProperties=lz.prototype.L;lz.prototype.set=lz.prototype.set;lz.prototype.setProperties=lz.prototype.H;lz.prototype.unset=lz.prototype.R;lz.prototype.changed=lz.prototype.u;lz.prototype.dispatchEvent=lz.prototype.b;lz.prototype.getRevision=lz.prototype.K;lz.prototype.on=lz.prototype.I;lz.prototype.once=lz.prototype.once;
+lz.prototype.un=lz.prototype.J;Bw.prototype.type=Bw.prototype.type;Bw.prototype.target=Bw.prototype.target;Bw.prototype.preventDefault=Bw.prototype.preventDefault;Bw.prototype.stopPropagation=Bw.prototype.stopPropagation;rz.prototype.getTileLoadFunction=rz.prototype.yb;rz.prototype.getTileUrlFunction=rz.prototype.zb;rz.prototype.getUrls=rz.prototype.Ab;rz.prototype.setTileLoadFunction=rz.prototype.Fb;rz.prototype.setTileUrlFunction=rz.prototype.hb;rz.prototype.setUrl=rz.prototype.rb;
+rz.prototype.setUrls=rz.prototype.vb;rz.prototype.getTileGrid=rz.prototype.jb;rz.prototype.refresh=rz.prototype.sa;rz.prototype.getAttributions=rz.prototype.za;rz.prototype.getLogo=rz.prototype.Aa;rz.prototype.getProjection=rz.prototype.Da;rz.prototype.getState=rz.prototype.getState;rz.prototype.setAttributions=rz.prototype.va;rz.prototype.get=rz.prototype.get;rz.prototype.getKeys=rz.prototype.P;rz.prototype.getProperties=rz.prototype.L;rz.prototype.set=rz.prototype.set;
+rz.prototype.setProperties=rz.prototype.H;rz.prototype.unset=rz.prototype.R;rz.prototype.changed=rz.prototype.u;rz.prototype.dispatchEvent=rz.prototype.b;rz.prototype.getRevision=rz.prototype.K;rz.prototype.on=rz.prototype.I;rz.prototype.once=rz.prototype.once;rz.prototype.un=rz.prototype.J;Y.prototype.setRenderReprojectionEdges=Y.prototype.Qb;Y.prototype.setTileGridForProjection=Y.prototype.Rb;Y.prototype.getTileLoadFunction=Y.prototype.yb;Y.prototype.getTileUrlFunction=Y.prototype.zb;
+Y.prototype.getUrls=Y.prototype.Ab;Y.prototype.setTileLoadFunction=Y.prototype.Fb;Y.prototype.setTileUrlFunction=Y.prototype.hb;Y.prototype.setUrl=Y.prototype.rb;Y.prototype.setUrls=Y.prototype.vb;Y.prototype.getTileGrid=Y.prototype.jb;Y.prototype.refresh=Y.prototype.sa;Y.prototype.getAttributions=Y.prototype.za;Y.prototype.getLogo=Y.prototype.Aa;Y.prototype.getProjection=Y.prototype.Da;Y.prototype.getState=Y.prototype.getState;Y.prototype.setAttributions=Y.prototype.va;Y.prototype.get=Y.prototype.get;
+Y.prototype.getKeys=Y.prototype.P;Y.prototype.getProperties=Y.prototype.L;Y.prototype.set=Y.prototype.set;Y.prototype.setProperties=Y.prototype.H;Y.prototype.unset=Y.prototype.R;Y.prototype.changed=Y.prototype.u;Y.prototype.dispatchEvent=Y.prototype.b;Y.prototype.getRevision=Y.prototype.K;Y.prototype.on=Y.prototype.I;Y.prototype.once=Y.prototype.once;Y.prototype.un=Y.prototype.J;vz.prototype.setRenderReprojectionEdges=vz.prototype.Qb;vz.prototype.setTileGridForProjection=vz.prototype.Rb;
+vz.prototype.getTileLoadFunction=vz.prototype.yb;vz.prototype.getTileUrlFunction=vz.prototype.zb;vz.prototype.getUrls=vz.prototype.Ab;vz.prototype.setTileLoadFunction=vz.prototype.Fb;vz.prototype.setTileUrlFunction=vz.prototype.hb;vz.prototype.setUrl=vz.prototype.rb;vz.prototype.setUrls=vz.prototype.vb;vz.prototype.getTileGrid=vz.prototype.jb;vz.prototype.refresh=vz.prototype.sa;vz.prototype.getAttributions=vz.prototype.za;vz.prototype.getLogo=vz.prototype.Aa;vz.prototype.getProjection=vz.prototype.Da;
+vz.prototype.getState=vz.prototype.getState;vz.prototype.setAttributions=vz.prototype.va;vz.prototype.get=vz.prototype.get;vz.prototype.getKeys=vz.prototype.P;vz.prototype.getProperties=vz.prototype.L;vz.prototype.set=vz.prototype.set;vz.prototype.setProperties=vz.prototype.H;vz.prototype.unset=vz.prototype.R;vz.prototype.changed=vz.prototype.u;vz.prototype.dispatchEvent=vz.prototype.b;vz.prototype.getRevision=vz.prototype.K;vz.prototype.on=vz.prototype.I;vz.prototype.once=vz.prototype.once;
+vz.prototype.un=vz.prototype.J;hy.prototype.getTileCoord=hy.prototype.i;hy.prototype.load=hy.prototype.load;Ki.prototype.changed=Ki.prototype.u;Ki.prototype.dispatchEvent=Ki.prototype.b;Ki.prototype.getRevision=Ki.prototype.K;Ki.prototype.on=Ki.prototype.I;Ki.prototype.once=Ki.prototype.once;Ki.prototype.un=Ki.prototype.J;mn.prototype.changed=mn.prototype.u;mn.prototype.dispatchEvent=mn.prototype.b;mn.prototype.getRevision=mn.prototype.K;mn.prototype.on=mn.prototype.I;mn.prototype.once=mn.prototype.once;
+mn.prototype.un=mn.prototype.J;pn.prototype.changed=pn.prototype.u;pn.prototype.dispatchEvent=pn.prototype.b;pn.prototype.getRevision=pn.prototype.K;pn.prototype.on=pn.prototype.I;pn.prototype.once=pn.prototype.once;pn.prototype.un=pn.prototype.J;zn.prototype.changed=zn.prototype.u;zn.prototype.dispatchEvent=zn.prototype.b;zn.prototype.getRevision=zn.prototype.K;zn.prototype.on=zn.prototype.I;zn.prototype.once=zn.prototype.once;zn.prototype.un=zn.prototype.J;An.prototype.changed=An.prototype.u;
+An.prototype.dispatchEvent=An.prototype.b;An.prototype.getRevision=An.prototype.K;An.prototype.on=An.prototype.I;An.prototype.once=An.prototype.once;An.prototype.un=An.prototype.J;Xi.prototype.changed=Xi.prototype.u;Xi.prototype.dispatchEvent=Xi.prototype.b;Xi.prototype.getRevision=Xi.prototype.K;Xi.prototype.on=Xi.prototype.I;Xi.prototype.once=Xi.prototype.once;Xi.prototype.un=Xi.prototype.J;aj.prototype.changed=aj.prototype.u;aj.prototype.dispatchEvent=aj.prototype.b;aj.prototype.getRevision=aj.prototype.K;
+aj.prototype.on=aj.prototype.I;aj.prototype.once=aj.prototype.once;aj.prototype.un=aj.prototype.J;bj.prototype.changed=bj.prototype.u;bj.prototype.dispatchEvent=bj.prototype.b;bj.prototype.getRevision=bj.prototype.K;bj.prototype.on=bj.prototype.I;bj.prototype.once=bj.prototype.once;bj.prototype.un=bj.prototype.J;mj.prototype.changed=mj.prototype.u;mj.prototype.dispatchEvent=mj.prototype.b;mj.prototype.getRevision=mj.prototype.K;mj.prototype.on=mj.prototype.I;mj.prototype.once=mj.prototype.once;
+mj.prototype.un=mj.prototype.J;hk.prototype.changed=hk.prototype.u;hk.prototype.dispatchEvent=hk.prototype.b;hk.prototype.getRevision=hk.prototype.K;hk.prototype.on=hk.prototype.I;hk.prototype.once=hk.prototype.once;hk.prototype.un=hk.prototype.J;jk.prototype.changed=jk.prototype.u;jk.prototype.dispatchEvent=jk.prototype.b;jk.prototype.getRevision=jk.prototype.K;jk.prototype.on=jk.prototype.I;jk.prototype.once=jk.prototype.once;jk.prototype.un=jk.prototype.J;bi.prototype.type=bi.prototype.type;
+bi.prototype.target=bi.prototype.target;bi.prototype.preventDefault=bi.prototype.preventDefault;bi.prototype.stopPropagation=bi.prototype.stopPropagation;Md.prototype.type=Md.prototype.type;Md.prototype.target=Md.prototype.target;Md.prototype.preventDefault=Md.prototype.preventDefault;Md.prototype.stopPropagation=Md.prototype.stopPropagation;kg.prototype.get=kg.prototype.get;kg.prototype.getKeys=kg.prototype.P;kg.prototype.getProperties=kg.prototype.L;kg.prototype.set=kg.prototype.set;
+kg.prototype.setProperties=kg.prototype.H;kg.prototype.unset=kg.prototype.R;kg.prototype.changed=kg.prototype.u;kg.prototype.dispatchEvent=kg.prototype.b;kg.prototype.getRevision=kg.prototype.K;kg.prototype.on=kg.prototype.I;kg.prototype.once=kg.prototype.once;kg.prototype.un=kg.prototype.J;mg.prototype.getExtent=mg.prototype.G;mg.prototype.getMaxResolution=mg.prototype.lc;mg.prototype.getMinResolution=mg.prototype.mc;mg.prototype.getOpacity=mg.prototype.nc;mg.prototype.getVisible=mg.prototype.Jb;
+mg.prototype.getZIndex=mg.prototype.Ba;mg.prototype.setExtent=mg.prototype.Fc;mg.prototype.setMaxResolution=mg.prototype.Mc;mg.prototype.setMinResolution=mg.prototype.Nc;mg.prototype.setOpacity=mg.prototype.Gc;mg.prototype.setVisible=mg.prototype.Hc;mg.prototype.setZIndex=mg.prototype.$b;mg.prototype.get=mg.prototype.get;mg.prototype.getKeys=mg.prototype.P;mg.prototype.getProperties=mg.prototype.L;mg.prototype.set=mg.prototype.set;mg.prototype.setProperties=mg.prototype.H;mg.prototype.unset=mg.prototype.R;
+mg.prototype.changed=mg.prototype.u;mg.prototype.dispatchEvent=mg.prototype.b;mg.prototype.getRevision=mg.prototype.K;mg.prototype.on=mg.prototype.I;mg.prototype.once=mg.prototype.once;mg.prototype.un=mg.prototype.J;xg.prototype.getExtent=xg.prototype.G;xg.prototype.getMaxResolution=xg.prototype.lc;xg.prototype.getMinResolution=xg.prototype.mc;xg.prototype.getOpacity=xg.prototype.nc;xg.prototype.getVisible=xg.prototype.Jb;xg.prototype.getZIndex=xg.prototype.Ba;xg.prototype.setExtent=xg.prototype.Fc;
+xg.prototype.setMaxResolution=xg.prototype.Mc;xg.prototype.setMinResolution=xg.prototype.Nc;xg.prototype.setOpacity=xg.prototype.Gc;xg.prototype.setVisible=xg.prototype.Hc;xg.prototype.setZIndex=xg.prototype.$b;xg.prototype.get=xg.prototype.get;xg.prototype.getKeys=xg.prototype.P;xg.prototype.getProperties=xg.prototype.L;xg.prototype.set=xg.prototype.set;xg.prototype.setProperties=xg.prototype.H;xg.prototype.unset=xg.prototype.R;xg.prototype.changed=xg.prototype.u;xg.prototype.dispatchEvent=xg.prototype.b;
+xg.prototype.getRevision=xg.prototype.K;xg.prototype.on=xg.prototype.I;xg.prototype.once=xg.prototype.once;xg.prototype.un=xg.prototype.J;T.prototype.setMap=T.prototype.setMap;T.prototype.setSource=T.prototype.hd;T.prototype.getExtent=T.prototype.G;T.prototype.getMaxResolution=T.prototype.lc;T.prototype.getMinResolution=T.prototype.mc;T.prototype.getOpacity=T.prototype.nc;T.prototype.getVisible=T.prototype.Jb;T.prototype.getZIndex=T.prototype.Ba;T.prototype.setExtent=T.prototype.Fc;
+T.prototype.setMaxResolution=T.prototype.Mc;T.prototype.setMinResolution=T.prototype.Nc;T.prototype.setOpacity=T.prototype.Gc;T.prototype.setVisible=T.prototype.Hc;T.prototype.setZIndex=T.prototype.$b;T.prototype.get=T.prototype.get;T.prototype.getKeys=T.prototype.P;T.prototype.getProperties=T.prototype.L;T.prototype.set=T.prototype.set;T.prototype.setProperties=T.prototype.H;T.prototype.unset=T.prototype.R;T.prototype.changed=T.prototype.u;T.prototype.dispatchEvent=T.prototype.b;
+T.prototype.getRevision=T.prototype.K;T.prototype.on=T.prototype.I;T.prototype.once=T.prototype.once;T.prototype.un=T.prototype.J;V.prototype.getSource=V.prototype.ha;V.prototype.getStyle=V.prototype.B;V.prototype.getStyleFunction=V.prototype.ib;V.prototype.setStyle=V.prototype.j;V.prototype.setMap=V.prototype.setMap;V.prototype.setSource=V.prototype.hd;V.prototype.getExtent=V.prototype.G;V.prototype.getMaxResolution=V.prototype.lc;V.prototype.getMinResolution=V.prototype.mc;
+V.prototype.getOpacity=V.prototype.nc;V.prototype.getVisible=V.prototype.Jb;V.prototype.getZIndex=V.prototype.Ba;V.prototype.setExtent=V.prototype.Fc;V.prototype.setMaxResolution=V.prototype.Mc;V.prototype.setMinResolution=V.prototype.Nc;V.prototype.setOpacity=V.prototype.Gc;V.prototype.setVisible=V.prototype.Hc;V.prototype.setZIndex=V.prototype.$b;V.prototype.get=V.prototype.get;V.prototype.getKeys=V.prototype.P;V.prototype.getProperties=V.prototype.L;V.prototype.set=V.prototype.set;
+V.prototype.setProperties=V.prototype.H;V.prototype.unset=V.prototype.R;V.prototype.changed=V.prototype.u;V.prototype.dispatchEvent=V.prototype.b;V.prototype.getRevision=V.prototype.K;V.prototype.on=V.prototype.I;V.prototype.once=V.prototype.once;V.prototype.un=V.prototype.J;Sx.prototype.setMap=Sx.prototype.setMap;Sx.prototype.setSource=Sx.prototype.hd;Sx.prototype.getExtent=Sx.prototype.G;Sx.prototype.getMaxResolution=Sx.prototype.lc;Sx.prototype.getMinResolution=Sx.prototype.mc;
+Sx.prototype.getOpacity=Sx.prototype.nc;Sx.prototype.getVisible=Sx.prototype.Jb;Sx.prototype.getZIndex=Sx.prototype.Ba;Sx.prototype.setExtent=Sx.prototype.Fc;Sx.prototype.setMaxResolution=Sx.prototype.Mc;Sx.prototype.setMinResolution=Sx.prototype.Nc;Sx.prototype.setOpacity=Sx.prototype.Gc;Sx.prototype.setVisible=Sx.prototype.Hc;Sx.prototype.setZIndex=Sx.prototype.$b;Sx.prototype.get=Sx.prototype.get;Sx.prototype.getKeys=Sx.prototype.P;Sx.prototype.getProperties=Sx.prototype.L;Sx.prototype.set=Sx.prototype.set;
+Sx.prototype.setProperties=Sx.prototype.H;Sx.prototype.unset=Sx.prototype.R;Sx.prototype.changed=Sx.prototype.u;Sx.prototype.dispatchEvent=Sx.prototype.b;Sx.prototype.getRevision=Sx.prototype.K;Sx.prototype.on=Sx.prototype.I;Sx.prototype.once=Sx.prototype.once;Sx.prototype.un=Sx.prototype.J;Tx.prototype.setMap=Tx.prototype.setMap;Tx.prototype.setSource=Tx.prototype.hd;Tx.prototype.getExtent=Tx.prototype.G;Tx.prototype.getMaxResolution=Tx.prototype.lc;Tx.prototype.getMinResolution=Tx.prototype.mc;
+Tx.prototype.getOpacity=Tx.prototype.nc;Tx.prototype.getVisible=Tx.prototype.Jb;Tx.prototype.getZIndex=Tx.prototype.Ba;Tx.prototype.setExtent=Tx.prototype.Fc;Tx.prototype.setMaxResolution=Tx.prototype.Mc;Tx.prototype.setMinResolution=Tx.prototype.Nc;Tx.prototype.setOpacity=Tx.prototype.Gc;Tx.prototype.setVisible=Tx.prototype.Hc;Tx.prototype.setZIndex=Tx.prototype.$b;Tx.prototype.get=Tx.prototype.get;Tx.prototype.getKeys=Tx.prototype.P;Tx.prototype.getProperties=Tx.prototype.L;Tx.prototype.set=Tx.prototype.set;
+Tx.prototype.setProperties=Tx.prototype.H;Tx.prototype.unset=Tx.prototype.R;Tx.prototype.changed=Tx.prototype.u;Tx.prototype.dispatchEvent=Tx.prototype.b;Tx.prototype.getRevision=Tx.prototype.K;Tx.prototype.on=Tx.prototype.I;Tx.prototype.once=Tx.prototype.once;Tx.prototype.un=Tx.prototype.J;W.prototype.getStyle=W.prototype.B;W.prototype.getStyleFunction=W.prototype.ib;W.prototype.setStyle=W.prototype.j;W.prototype.setMap=W.prototype.setMap;W.prototype.setSource=W.prototype.hd;
+W.prototype.getExtent=W.prototype.G;W.prototype.getMaxResolution=W.prototype.lc;W.prototype.getMinResolution=W.prototype.mc;W.prototype.getOpacity=W.prototype.nc;W.prototype.getVisible=W.prototype.Jb;W.prototype.getZIndex=W.prototype.Ba;W.prototype.setExtent=W.prototype.Fc;W.prototype.setMaxResolution=W.prototype.Mc;W.prototype.setMinResolution=W.prototype.Nc;W.prototype.setOpacity=W.prototype.Gc;W.prototype.setVisible=W.prototype.Hc;W.prototype.setZIndex=W.prototype.$b;W.prototype.get=W.prototype.get;
+W.prototype.getKeys=W.prototype.P;W.prototype.getProperties=W.prototype.L;W.prototype.set=W.prototype.set;W.prototype.setProperties=W.prototype.H;W.prototype.unset=W.prototype.R;W.prototype.changed=W.prototype.u;W.prototype.dispatchEvent=W.prototype.b;W.prototype.getRevision=W.prototype.K;W.prototype.on=W.prototype.I;W.prototype.once=W.prototype.once;W.prototype.un=W.prototype.J;Jg.prototype.get=Jg.prototype.get;Jg.prototype.getKeys=Jg.prototype.P;Jg.prototype.getProperties=Jg.prototype.L;
+Jg.prototype.set=Jg.prototype.set;Jg.prototype.setProperties=Jg.prototype.H;Jg.prototype.unset=Jg.prototype.R;Jg.prototype.changed=Jg.prototype.u;Jg.prototype.dispatchEvent=Jg.prototype.b;Jg.prototype.getRevision=Jg.prototype.K;Jg.prototype.on=Jg.prototype.I;Jg.prototype.once=Jg.prototype.once;Jg.prototype.un=Jg.prototype.J;Ug.prototype.getActive=Ug.prototype.c;Ug.prototype.getMap=Ug.prototype.i;Ug.prototype.setActive=Ug.prototype.Ha;Ug.prototype.get=Ug.prototype.get;Ug.prototype.getKeys=Ug.prototype.P;
+Ug.prototype.getProperties=Ug.prototype.L;Ug.prototype.set=Ug.prototype.set;Ug.prototype.setProperties=Ug.prototype.H;Ug.prototype.unset=Ug.prototype.R;Ug.prototype.changed=Ug.prototype.u;Ug.prototype.dispatchEvent=Ug.prototype.b;Ug.prototype.getRevision=Ug.prototype.K;Ug.prototype.on=Ug.prototype.I;Ug.prototype.once=Ug.prototype.once;Ug.prototype.un=Ug.prototype.J;iw.prototype.getActive=iw.prototype.c;iw.prototype.getMap=iw.prototype.i;iw.prototype.setActive=iw.prototype.Ha;iw.prototype.get=iw.prototype.get;
+iw.prototype.getKeys=iw.prototype.P;iw.prototype.getProperties=iw.prototype.L;iw.prototype.set=iw.prototype.set;iw.prototype.setProperties=iw.prototype.H;iw.prototype.unset=iw.prototype.R;iw.prototype.changed=iw.prototype.u;iw.prototype.dispatchEvent=iw.prototype.b;iw.prototype.getRevision=iw.prototype.K;iw.prototype.on=iw.prototype.I;iw.prototype.once=iw.prototype.once;iw.prototype.un=iw.prototype.J;lw.prototype.type=lw.prototype.type;lw.prototype.target=lw.prototype.target;
+lw.prototype.preventDefault=lw.prototype.preventDefault;lw.prototype.stopPropagation=lw.prototype.stopPropagation;fh.prototype.getActive=fh.prototype.c;fh.prototype.getMap=fh.prototype.i;fh.prototype.setActive=fh.prototype.Ha;fh.prototype.get=fh.prototype.get;fh.prototype.getKeys=fh.prototype.P;fh.prototype.getProperties=fh.prototype.L;fh.prototype.set=fh.prototype.set;fh.prototype.setProperties=fh.prototype.H;fh.prototype.unset=fh.prototype.R;fh.prototype.changed=fh.prototype.u;
+fh.prototype.dispatchEvent=fh.prototype.b;fh.prototype.getRevision=fh.prototype.K;fh.prototype.on=fh.prototype.I;fh.prototype.once=fh.prototype.once;fh.prototype.un=fh.prototype.J;th.prototype.getActive=th.prototype.c;th.prototype.getMap=th.prototype.i;th.prototype.setActive=th.prototype.Ha;th.prototype.get=th.prototype.get;th.prototype.getKeys=th.prototype.P;th.prototype.getProperties=th.prototype.L;th.prototype.set=th.prototype.set;th.prototype.setProperties=th.prototype.H;th.prototype.unset=th.prototype.R;
+th.prototype.changed=th.prototype.u;th.prototype.dispatchEvent=th.prototype.b;th.prototype.getRevision=th.prototype.K;th.prototype.on=th.prototype.I;th.prototype.once=th.prototype.once;th.prototype.un=th.prototype.J;yh.prototype.type=yh.prototype.type;yh.prototype.target=yh.prototype.target;yh.prototype.preventDefault=yh.prototype.preventDefault;yh.prototype.stopPropagation=yh.prototype.stopPropagation;ih.prototype.getActive=ih.prototype.c;ih.prototype.getMap=ih.prototype.i;
+ih.prototype.setActive=ih.prototype.Ha;ih.prototype.get=ih.prototype.get;ih.prototype.getKeys=ih.prototype.P;ih.prototype.getProperties=ih.prototype.L;ih.prototype.set=ih.prototype.set;ih.prototype.setProperties=ih.prototype.H;ih.prototype.unset=ih.prototype.R;ih.prototype.changed=ih.prototype.u;ih.prototype.dispatchEvent=ih.prototype.b;ih.prototype.getRevision=ih.prototype.K;ih.prototype.on=ih.prototype.I;ih.prototype.once=ih.prototype.once;ih.prototype.un=ih.prototype.J;mh.prototype.getActive=mh.prototype.c;
+mh.prototype.getMap=mh.prototype.i;mh.prototype.setActive=mh.prototype.Ha;mh.prototype.get=mh.prototype.get;mh.prototype.getKeys=mh.prototype.P;mh.prototype.getProperties=mh.prototype.L;mh.prototype.set=mh.prototype.set;mh.prototype.setProperties=mh.prototype.H;mh.prototype.unset=mh.prototype.R;mh.prototype.changed=mh.prototype.u;mh.prototype.dispatchEvent=mh.prototype.b;mh.prototype.getRevision=mh.prototype.K;mh.prototype.on=mh.prototype.I;mh.prototype.once=mh.prototype.once;mh.prototype.un=mh.prototype.J;
+pw.prototype.getActive=pw.prototype.c;pw.prototype.getMap=pw.prototype.i;pw.prototype.setActive=pw.prototype.Ha;pw.prototype.get=pw.prototype.get;pw.prototype.getKeys=pw.prototype.P;pw.prototype.getProperties=pw.prototype.L;pw.prototype.set=pw.prototype.set;pw.prototype.setProperties=pw.prototype.H;pw.prototype.unset=pw.prototype.R;pw.prototype.changed=pw.prototype.u;pw.prototype.dispatchEvent=pw.prototype.b;pw.prototype.getRevision=pw.prototype.K;pw.prototype.on=pw.prototype.I;
+pw.prototype.once=pw.prototype.once;pw.prototype.un=pw.prototype.J;Ch.prototype.getGeometry=Ch.prototype.U;Ch.prototype.getActive=Ch.prototype.c;Ch.prototype.getMap=Ch.prototype.i;Ch.prototype.setActive=Ch.prototype.Ha;Ch.prototype.get=Ch.prototype.get;Ch.prototype.getKeys=Ch.prototype.P;Ch.prototype.getProperties=Ch.prototype.L;Ch.prototype.set=Ch.prototype.set;Ch.prototype.setProperties=Ch.prototype.H;Ch.prototype.unset=Ch.prototype.R;Ch.prototype.changed=Ch.prototype.u;
+Ch.prototype.dispatchEvent=Ch.prototype.b;Ch.prototype.getRevision=Ch.prototype.K;Ch.prototype.on=Ch.prototype.I;Ch.prototype.once=Ch.prototype.once;Ch.prototype.un=Ch.prototype.J;Ew.prototype.getActive=Ew.prototype.c;Ew.prototype.getMap=Ew.prototype.i;Ew.prototype.setActive=Ew.prototype.Ha;Ew.prototype.get=Ew.prototype.get;Ew.prototype.getKeys=Ew.prototype.P;Ew.prototype.getProperties=Ew.prototype.L;Ew.prototype.set=Ew.prototype.set;Ew.prototype.setProperties=Ew.prototype.H;Ew.prototype.unset=Ew.prototype.R;
+Ew.prototype.changed=Ew.prototype.u;Ew.prototype.dispatchEvent=Ew.prototype.b;Ew.prototype.getRevision=Ew.prototype.K;Ew.prototype.on=Ew.prototype.I;Ew.prototype.once=Ew.prototype.once;Ew.prototype.un=Ew.prototype.J;Uw.prototype.type=Uw.prototype.type;Uw.prototype.target=Uw.prototype.target;Uw.prototype.preventDefault=Uw.prototype.preventDefault;Uw.prototype.stopPropagation=Uw.prototype.stopPropagation;Vw.prototype.getActive=Vw.prototype.c;Vw.prototype.getMap=Vw.prototype.i;
+Vw.prototype.setActive=Vw.prototype.Ha;Vw.prototype.get=Vw.prototype.get;Vw.prototype.getKeys=Vw.prototype.P;Vw.prototype.getProperties=Vw.prototype.L;Vw.prototype.set=Vw.prototype.set;Vw.prototype.setProperties=Vw.prototype.H;Vw.prototype.unset=Vw.prototype.R;Vw.prototype.changed=Vw.prototype.u;Vw.prototype.dispatchEvent=Vw.prototype.b;Vw.prototype.getRevision=Vw.prototype.K;Vw.prototype.on=Vw.prototype.I;Vw.prototype.once=Vw.prototype.once;Vw.prototype.un=Vw.prototype.J;fx.prototype.type=fx.prototype.type;
+fx.prototype.target=fx.prototype.target;fx.prototype.preventDefault=fx.prototype.preventDefault;fx.prototype.stopPropagation=fx.prototype.stopPropagation;Dh.prototype.getActive=Dh.prototype.c;Dh.prototype.getMap=Dh.prototype.i;Dh.prototype.setActive=Dh.prototype.Ha;Dh.prototype.get=Dh.prototype.get;Dh.prototype.getKeys=Dh.prototype.P;Dh.prototype.getProperties=Dh.prototype.L;Dh.prototype.set=Dh.prototype.set;Dh.prototype.setProperties=Dh.prototype.H;Dh.prototype.unset=Dh.prototype.R;
+Dh.prototype.changed=Dh.prototype.u;Dh.prototype.dispatchEvent=Dh.prototype.b;Dh.prototype.getRevision=Dh.prototype.K;Dh.prototype.on=Dh.prototype.I;Dh.prototype.once=Dh.prototype.once;Dh.prototype.un=Dh.prototype.J;Fh.prototype.getActive=Fh.prototype.c;Fh.prototype.getMap=Fh.prototype.i;Fh.prototype.setActive=Fh.prototype.Ha;Fh.prototype.get=Fh.prototype.get;Fh.prototype.getKeys=Fh.prototype.P;Fh.prototype.getProperties=Fh.prototype.L;Fh.prototype.set=Fh.prototype.set;
+Fh.prototype.setProperties=Fh.prototype.H;Fh.prototype.unset=Fh.prototype.R;Fh.prototype.changed=Fh.prototype.u;Fh.prototype.dispatchEvent=Fh.prototype.b;Fh.prototype.getRevision=Fh.prototype.K;Fh.prototype.on=Fh.prototype.I;Fh.prototype.once=Fh.prototype.once;Fh.prototype.un=Fh.prototype.J;gx.prototype.getActive=gx.prototype.c;gx.prototype.getMap=gx.prototype.i;gx.prototype.setActive=gx.prototype.Ha;gx.prototype.get=gx.prototype.get;gx.prototype.getKeys=gx.prototype.P;
+gx.prototype.getProperties=gx.prototype.L;gx.prototype.set=gx.prototype.set;gx.prototype.setProperties=gx.prototype.H;gx.prototype.unset=gx.prototype.R;gx.prototype.changed=gx.prototype.u;gx.prototype.dispatchEvent=gx.prototype.b;gx.prototype.getRevision=gx.prototype.K;gx.prototype.on=gx.prototype.I;gx.prototype.once=gx.prototype.once;gx.prototype.un=gx.prototype.J;ox.prototype.type=ox.prototype.type;ox.prototype.target=ox.prototype.target;ox.prototype.preventDefault=ox.prototype.preventDefault;
+ox.prototype.stopPropagation=ox.prototype.stopPropagation;Hh.prototype.getActive=Hh.prototype.c;Hh.prototype.getMap=Hh.prototype.i;Hh.prototype.setActive=Hh.prototype.Ha;Hh.prototype.get=Hh.prototype.get;Hh.prototype.getKeys=Hh.prototype.P;Hh.prototype.getProperties=Hh.prototype.L;Hh.prototype.set=Hh.prototype.set;Hh.prototype.setProperties=Hh.prototype.H;Hh.prototype.unset=Hh.prototype.R;Hh.prototype.changed=Hh.prototype.u;Hh.prototype.dispatchEvent=Hh.prototype.b;Hh.prototype.getRevision=Hh.prototype.K;
+Hh.prototype.on=Hh.prototype.I;Hh.prototype.once=Hh.prototype.once;Hh.prototype.un=Hh.prototype.J;Rh.prototype.getActive=Rh.prototype.c;Rh.prototype.getMap=Rh.prototype.i;Rh.prototype.setActive=Rh.prototype.Ha;Rh.prototype.get=Rh.prototype.get;Rh.prototype.getKeys=Rh.prototype.P;Rh.prototype.getProperties=Rh.prototype.L;Rh.prototype.set=Rh.prototype.set;Rh.prototype.setProperties=Rh.prototype.H;Rh.prototype.unset=Rh.prototype.R;Rh.prototype.changed=Rh.prototype.u;Rh.prototype.dispatchEvent=Rh.prototype.b;
+Rh.prototype.getRevision=Rh.prototype.K;Rh.prototype.on=Rh.prototype.I;Rh.prototype.once=Rh.prototype.once;Rh.prototype.un=Rh.prototype.J;Vh.prototype.getActive=Vh.prototype.c;Vh.prototype.getMap=Vh.prototype.i;Vh.prototype.setActive=Vh.prototype.Ha;Vh.prototype.get=Vh.prototype.get;Vh.prototype.getKeys=Vh.prototype.P;Vh.prototype.getProperties=Vh.prototype.L;Vh.prototype.set=Vh.prototype.set;Vh.prototype.setProperties=Vh.prototype.H;Vh.prototype.unset=Vh.prototype.R;Vh.prototype.changed=Vh.prototype.u;
+Vh.prototype.dispatchEvent=Vh.prototype.b;Vh.prototype.getRevision=Vh.prototype.K;Vh.prototype.on=Vh.prototype.I;Vh.prototype.once=Vh.prototype.once;Vh.prototype.un=Vh.prototype.J;wx.prototype.getActive=wx.prototype.c;wx.prototype.getMap=wx.prototype.i;wx.prototype.setActive=wx.prototype.Ha;wx.prototype.get=wx.prototype.get;wx.prototype.getKeys=wx.prototype.P;wx.prototype.getProperties=wx.prototype.L;wx.prototype.set=wx.prototype.set;wx.prototype.setProperties=wx.prototype.H;wx.prototype.unset=wx.prototype.R;
+wx.prototype.changed=wx.prototype.u;wx.prototype.dispatchEvent=wx.prototype.b;wx.prototype.getRevision=wx.prototype.K;wx.prototype.on=wx.prototype.I;wx.prototype.once=wx.prototype.once;wx.prototype.un=wx.prototype.J;zx.prototype.type=zx.prototype.type;zx.prototype.target=zx.prototype.target;zx.prototype.preventDefault=zx.prototype.preventDefault;zx.prototype.stopPropagation=zx.prototype.stopPropagation;Bx.prototype.getActive=Bx.prototype.c;Bx.prototype.getMap=Bx.prototype.i;
+Bx.prototype.setActive=Bx.prototype.Ha;Bx.prototype.get=Bx.prototype.get;Bx.prototype.getKeys=Bx.prototype.P;Bx.prototype.getProperties=Bx.prototype.L;Bx.prototype.set=Bx.prototype.set;Bx.prototype.setProperties=Bx.prototype.H;Bx.prototype.unset=Bx.prototype.R;Bx.prototype.changed=Bx.prototype.u;Bx.prototype.dispatchEvent=Bx.prototype.b;Bx.prototype.getRevision=Bx.prototype.K;Bx.prototype.on=Bx.prototype.I;Bx.prototype.once=Bx.prototype.once;Bx.prototype.un=Bx.prototype.J;Gx.prototype.getActive=Gx.prototype.c;
+Gx.prototype.getMap=Gx.prototype.i;Gx.prototype.setActive=Gx.prototype.Ha;Gx.prototype.get=Gx.prototype.get;Gx.prototype.getKeys=Gx.prototype.P;Gx.prototype.getProperties=Gx.prototype.L;Gx.prototype.set=Gx.prototype.set;Gx.prototype.setProperties=Gx.prototype.H;Gx.prototype.unset=Gx.prototype.R;Gx.prototype.changed=Gx.prototype.u;Gx.prototype.dispatchEvent=Gx.prototype.b;Gx.prototype.getRevision=Gx.prototype.K;Gx.prototype.on=Gx.prototype.I;Gx.prototype.once=Gx.prototype.once;Gx.prototype.un=Gx.prototype.J;
+Mx.prototype.type=Mx.prototype.type;Mx.prototype.target=Mx.prototype.target;Mx.prototype.preventDefault=Mx.prototype.preventDefault;Mx.prototype.stopPropagation=Mx.prototype.stopPropagation;gf.prototype.get=gf.prototype.get;gf.prototype.getKeys=gf.prototype.P;gf.prototype.getProperties=gf.prototype.L;gf.prototype.set=gf.prototype.set;gf.prototype.setProperties=gf.prototype.H;gf.prototype.unset=gf.prototype.R;gf.prototype.changed=gf.prototype.u;gf.prototype.dispatchEvent=gf.prototype.b;
+gf.prototype.getRevision=gf.prototype.K;gf.prototype.on=gf.prototype.I;gf.prototype.once=gf.prototype.once;gf.prototype.un=gf.prototype.J;hf.prototype.getClosestPoint=hf.prototype.Ib;hf.prototype.intersectsCoordinate=hf.prototype.Bb;hf.prototype.getExtent=hf.prototype.G;hf.prototype.rotate=hf.prototype.rotate;hf.prototype.scale=hf.prototype.scale;hf.prototype.simplify=hf.prototype.Sb;hf.prototype.transform=hf.prototype.mb;hf.prototype.get=hf.prototype.get;hf.prototype.getKeys=hf.prototype.P;
+hf.prototype.getProperties=hf.prototype.L;hf.prototype.set=hf.prototype.set;hf.prototype.setProperties=hf.prototype.H;hf.prototype.unset=hf.prototype.R;hf.prototype.changed=hf.prototype.u;hf.prototype.dispatchEvent=hf.prototype.b;hf.prototype.getRevision=hf.prototype.K;hf.prototype.on=hf.prototype.I;hf.prototype.once=hf.prototype.once;hf.prototype.un=hf.prototype.J;gw.prototype.getFirstCoordinate=gw.prototype.fc;gw.prototype.getLastCoordinate=gw.prototype.gc;gw.prototype.getLayout=gw.prototype.ic;
+gw.prototype.rotate=gw.prototype.rotate;gw.prototype.scale=gw.prototype.scale;gw.prototype.getClosestPoint=gw.prototype.Ib;gw.prototype.intersectsCoordinate=gw.prototype.Bb;gw.prototype.getExtent=gw.prototype.G;gw.prototype.simplify=gw.prototype.Sb;gw.prototype.get=gw.prototype.get;gw.prototype.getKeys=gw.prototype.P;gw.prototype.getProperties=gw.prototype.L;gw.prototype.set=gw.prototype.set;gw.prototype.setProperties=gw.prototype.H;gw.prototype.unset=gw.prototype.R;gw.prototype.changed=gw.prototype.u;
+gw.prototype.dispatchEvent=gw.prototype.b;gw.prototype.getRevision=gw.prototype.K;gw.prototype.on=gw.prototype.I;gw.prototype.once=gw.prototype.once;gw.prototype.un=gw.prototype.J;Mq.prototype.getClosestPoint=Mq.prototype.Ib;Mq.prototype.intersectsCoordinate=Mq.prototype.Bb;Mq.prototype.getExtent=Mq.prototype.G;Mq.prototype.rotate=Mq.prototype.rotate;Mq.prototype.scale=Mq.prototype.scale;Mq.prototype.simplify=Mq.prototype.Sb;Mq.prototype.transform=Mq.prototype.mb;Mq.prototype.get=Mq.prototype.get;
+Mq.prototype.getKeys=Mq.prototype.P;Mq.prototype.getProperties=Mq.prototype.L;Mq.prototype.set=Mq.prototype.set;Mq.prototype.setProperties=Mq.prototype.H;Mq.prototype.unset=Mq.prototype.R;Mq.prototype.changed=Mq.prototype.u;Mq.prototype.dispatchEvent=Mq.prototype.b;Mq.prototype.getRevision=Mq.prototype.K;Mq.prototype.on=Mq.prototype.I;Mq.prototype.once=Mq.prototype.once;Mq.prototype.un=Mq.prototype.J;Df.prototype.getFirstCoordinate=Df.prototype.fc;Df.prototype.getLastCoordinate=Df.prototype.gc;
+Df.prototype.getLayout=Df.prototype.ic;Df.prototype.rotate=Df.prototype.rotate;Df.prototype.scale=Df.prototype.scale;Df.prototype.getClosestPoint=Df.prototype.Ib;Df.prototype.intersectsCoordinate=Df.prototype.Bb;Df.prototype.getExtent=Df.prototype.G;Df.prototype.simplify=Df.prototype.Sb;Df.prototype.transform=Df.prototype.mb;Df.prototype.get=Df.prototype.get;Df.prototype.getKeys=Df.prototype.P;Df.prototype.getProperties=Df.prototype.L;Df.prototype.set=Df.prototype.set;Df.prototype.setProperties=Df.prototype.H;
+Df.prototype.unset=Df.prototype.R;Df.prototype.changed=Df.prototype.u;Df.prototype.dispatchEvent=Df.prototype.b;Df.prototype.getRevision=Df.prototype.K;Df.prototype.on=Df.prototype.I;Df.prototype.once=Df.prototype.once;Df.prototype.un=Df.prototype.J;I.prototype.getFirstCoordinate=I.prototype.fc;I.prototype.getLastCoordinate=I.prototype.gc;I.prototype.getLayout=I.prototype.ic;I.prototype.rotate=I.prototype.rotate;I.prototype.scale=I.prototype.scale;I.prototype.getClosestPoint=I.prototype.Ib;
+I.prototype.intersectsCoordinate=I.prototype.Bb;I.prototype.getExtent=I.prototype.G;I.prototype.simplify=I.prototype.Sb;I.prototype.transform=I.prototype.mb;I.prototype.get=I.prototype.get;I.prototype.getKeys=I.prototype.P;I.prototype.getProperties=I.prototype.L;I.prototype.set=I.prototype.set;I.prototype.setProperties=I.prototype.H;I.prototype.unset=I.prototype.R;I.prototype.changed=I.prototype.u;I.prototype.dispatchEvent=I.prototype.b;I.prototype.getRevision=I.prototype.K;I.prototype.on=I.prototype.I;
+I.prototype.once=I.prototype.once;I.prototype.un=I.prototype.J;P.prototype.getFirstCoordinate=P.prototype.fc;P.prototype.getLastCoordinate=P.prototype.gc;P.prototype.getLayout=P.prototype.ic;P.prototype.rotate=P.prototype.rotate;P.prototype.scale=P.prototype.scale;P.prototype.getClosestPoint=P.prototype.Ib;P.prototype.intersectsCoordinate=P.prototype.Bb;P.prototype.getExtent=P.prototype.G;P.prototype.simplify=P.prototype.Sb;P.prototype.transform=P.prototype.mb;P.prototype.get=P.prototype.get;
+P.prototype.getKeys=P.prototype.P;P.prototype.getProperties=P.prototype.L;P.prototype.set=P.prototype.set;P.prototype.setProperties=P.prototype.H;P.prototype.unset=P.prototype.R;P.prototype.changed=P.prototype.u;P.prototype.dispatchEvent=P.prototype.b;P.prototype.getRevision=P.prototype.K;P.prototype.on=P.prototype.I;P.prototype.once=P.prototype.once;P.prototype.un=P.prototype.J;No.prototype.getFirstCoordinate=No.prototype.fc;No.prototype.getLastCoordinate=No.prototype.gc;No.prototype.getLayout=No.prototype.ic;
+No.prototype.rotate=No.prototype.rotate;No.prototype.scale=No.prototype.scale;No.prototype.getClosestPoint=No.prototype.Ib;No.prototype.intersectsCoordinate=No.prototype.Bb;No.prototype.getExtent=No.prototype.G;No.prototype.simplify=No.prototype.Sb;No.prototype.transform=No.prototype.mb;No.prototype.get=No.prototype.get;No.prototype.getKeys=No.prototype.P;No.prototype.getProperties=No.prototype.L;No.prototype.set=No.prototype.set;No.prototype.setProperties=No.prototype.H;No.prototype.unset=No.prototype.R;
+No.prototype.changed=No.prototype.u;No.prototype.dispatchEvent=No.prototype.b;No.prototype.getRevision=No.prototype.K;No.prototype.on=No.prototype.I;No.prototype.once=No.prototype.once;No.prototype.un=No.prototype.J;Q.prototype.getFirstCoordinate=Q.prototype.fc;Q.prototype.getLastCoordinate=Q.prototype.gc;Q.prototype.getLayout=Q.prototype.ic;Q.prototype.rotate=Q.prototype.rotate;Q.prototype.scale=Q.prototype.scale;Q.prototype.getClosestPoint=Q.prototype.Ib;Q.prototype.intersectsCoordinate=Q.prototype.Bb;
+Q.prototype.getExtent=Q.prototype.G;Q.prototype.simplify=Q.prototype.Sb;Q.prototype.transform=Q.prototype.mb;Q.prototype.get=Q.prototype.get;Q.prototype.getKeys=Q.prototype.P;Q.prototype.getProperties=Q.prototype.L;Q.prototype.set=Q.prototype.set;Q.prototype.setProperties=Q.prototype.H;Q.prototype.unset=Q.prototype.R;Q.prototype.changed=Q.prototype.u;Q.prototype.dispatchEvent=Q.prototype.b;Q.prototype.getRevision=Q.prototype.K;Q.prototype.on=Q.prototype.I;Q.prototype.once=Q.prototype.once;
+Q.prototype.un=Q.prototype.J;C.prototype.getFirstCoordinate=C.prototype.fc;C.prototype.getLastCoordinate=C.prototype.gc;C.prototype.getLayout=C.prototype.ic;C.prototype.rotate=C.prototype.rotate;C.prototype.scale=C.prototype.scale;C.prototype.getClosestPoint=C.prototype.Ib;C.prototype.intersectsCoordinate=C.prototype.Bb;C.prototype.getExtent=C.prototype.G;C.prototype.simplify=C.prototype.Sb;C.prototype.transform=C.prototype.mb;C.prototype.get=C.prototype.get;C.prototype.getKeys=C.prototype.P;
+C.prototype.getProperties=C.prototype.L;C.prototype.set=C.prototype.set;C.prototype.setProperties=C.prototype.H;C.prototype.unset=C.prototype.R;C.prototype.changed=C.prototype.u;C.prototype.dispatchEvent=C.prototype.b;C.prototype.getRevision=C.prototype.K;C.prototype.on=C.prototype.I;C.prototype.once=C.prototype.once;C.prototype.un=C.prototype.J;D.prototype.getFirstCoordinate=D.prototype.fc;D.prototype.getLastCoordinate=D.prototype.gc;D.prototype.getLayout=D.prototype.ic;D.prototype.rotate=D.prototype.rotate;
+D.prototype.scale=D.prototype.scale;D.prototype.getClosestPoint=D.prototype.Ib;D.prototype.intersectsCoordinate=D.prototype.Bb;D.prototype.getExtent=D.prototype.G;D.prototype.simplify=D.prototype.Sb;D.prototype.transform=D.prototype.mb;D.prototype.get=D.prototype.get;D.prototype.getKeys=D.prototype.P;D.prototype.getProperties=D.prototype.L;D.prototype.set=D.prototype.set;D.prototype.setProperties=D.prototype.H;D.prototype.unset=D.prototype.R;D.prototype.changed=D.prototype.u;
+D.prototype.dispatchEvent=D.prototype.b;D.prototype.getRevision=D.prototype.K;D.prototype.on=D.prototype.I;D.prototype.once=D.prototype.once;D.prototype.un=D.prototype.J;Kp.prototype.readFeatures=Kp.prototype.Qa;Tp.prototype.readFeatures=Tp.prototype.Qa;Kp.prototype.readFeatures=Kp.prototype.Qa;vg.prototype.get=vg.prototype.get;vg.prototype.getKeys=vg.prototype.P;vg.prototype.getProperties=vg.prototype.L;vg.prototype.set=vg.prototype.set;vg.prototype.setProperties=vg.prototype.H;
+vg.prototype.unset=vg.prototype.R;vg.prototype.changed=vg.prototype.u;vg.prototype.dispatchEvent=vg.prototype.b;vg.prototype.getRevision=vg.prototype.K;vg.prototype.on=vg.prototype.I;vg.prototype.once=vg.prototype.once;vg.prototype.un=vg.prototype.J;zg.prototype.getMap=zg.prototype.f;zg.prototype.setMap=zg.prototype.setMap;zg.prototype.setTarget=zg.prototype.i;zg.prototype.get=zg.prototype.get;zg.prototype.getKeys=zg.prototype.P;zg.prototype.getProperties=zg.prototype.L;zg.prototype.set=zg.prototype.set;
+zg.prototype.setProperties=zg.prototype.H;zg.prototype.unset=zg.prototype.R;zg.prototype.changed=zg.prototype.u;zg.prototype.dispatchEvent=zg.prototype.b;zg.prototype.getRevision=zg.prototype.K;zg.prototype.on=zg.prototype.I;zg.prototype.once=zg.prototype.once;zg.prototype.un=zg.prototype.J;Mn.prototype.getMap=Mn.prototype.f;Mn.prototype.setMap=Mn.prototype.setMap;Mn.prototype.setTarget=Mn.prototype.i;Mn.prototype.get=Mn.prototype.get;Mn.prototype.getKeys=Mn.prototype.P;
+Mn.prototype.getProperties=Mn.prototype.L;Mn.prototype.set=Mn.prototype.set;Mn.prototype.setProperties=Mn.prototype.H;Mn.prototype.unset=Mn.prototype.R;Mn.prototype.changed=Mn.prototype.u;Mn.prototype.dispatchEvent=Mn.prototype.b;Mn.prototype.getRevision=Mn.prototype.K;Mn.prototype.on=Mn.prototype.I;Mn.prototype.once=Mn.prototype.once;Mn.prototype.un=Mn.prototype.J;Rn.prototype.getMap=Rn.prototype.f;Rn.prototype.setMap=Rn.prototype.setMap;Rn.prototype.setTarget=Rn.prototype.i;Rn.prototype.get=Rn.prototype.get;
+Rn.prototype.getKeys=Rn.prototype.P;Rn.prototype.getProperties=Rn.prototype.L;Rn.prototype.set=Rn.prototype.set;Rn.prototype.setProperties=Rn.prototype.H;Rn.prototype.unset=Rn.prototype.R;Rn.prototype.changed=Rn.prototype.u;Rn.prototype.dispatchEvent=Rn.prototype.b;Rn.prototype.getRevision=Rn.prototype.K;Rn.prototype.on=Rn.prototype.I;Rn.prototype.once=Rn.prototype.once;Rn.prototype.un=Rn.prototype.J;Wn.prototype.getMap=Wn.prototype.f;Wn.prototype.setMap=Wn.prototype.setMap;
+Wn.prototype.setTarget=Wn.prototype.i;Wn.prototype.get=Wn.prototype.get;Wn.prototype.getKeys=Wn.prototype.P;Wn.prototype.getProperties=Wn.prototype.L;Wn.prototype.set=Wn.prototype.set;Wn.prototype.setProperties=Wn.prototype.H;Wn.prototype.unset=Wn.prototype.R;Wn.prototype.changed=Wn.prototype.u;Wn.prototype.dispatchEvent=Wn.prototype.b;Wn.prototype.getRevision=Wn.prototype.K;Wn.prototype.on=Wn.prototype.I;Wn.prototype.once=Wn.prototype.once;Wn.prototype.un=Wn.prototype.J;Cg.prototype.getMap=Cg.prototype.f;
+Cg.prototype.setMap=Cg.prototype.setMap;Cg.prototype.setTarget=Cg.prototype.i;Cg.prototype.get=Cg.prototype.get;Cg.prototype.getKeys=Cg.prototype.P;Cg.prototype.getProperties=Cg.prototype.L;Cg.prototype.set=Cg.prototype.set;Cg.prototype.setProperties=Cg.prototype.H;Cg.prototype.unset=Cg.prototype.R;Cg.prototype.changed=Cg.prototype.u;Cg.prototype.dispatchEvent=Cg.prototype.b;Cg.prototype.getRevision=Cg.prototype.K;Cg.prototype.on=Cg.prototype.I;Cg.prototype.once=Cg.prototype.once;
+Cg.prototype.un=Cg.prototype.J;ao.prototype.getMap=ao.prototype.f;ao.prototype.setMap=ao.prototype.setMap;ao.prototype.setTarget=ao.prototype.i;ao.prototype.get=ao.prototype.get;ao.prototype.getKeys=ao.prototype.P;ao.prototype.getProperties=ao.prototype.L;ao.prototype.set=ao.prototype.set;ao.prototype.setProperties=ao.prototype.H;ao.prototype.unset=ao.prototype.R;ao.prototype.changed=ao.prototype.u;ao.prototype.dispatchEvent=ao.prototype.b;ao.prototype.getRevision=ao.prototype.K;ao.prototype.on=ao.prototype.I;
+ao.prototype.once=ao.prototype.once;ao.prototype.un=ao.prototype.J;Eg.prototype.getMap=Eg.prototype.f;Eg.prototype.setMap=Eg.prototype.setMap;Eg.prototype.setTarget=Eg.prototype.i;Eg.prototype.get=Eg.prototype.get;Eg.prototype.getKeys=Eg.prototype.P;Eg.prototype.getProperties=Eg.prototype.L;Eg.prototype.set=Eg.prototype.set;Eg.prototype.setProperties=Eg.prototype.H;Eg.prototype.unset=Eg.prototype.R;Eg.prototype.changed=Eg.prototype.u;Eg.prototype.dispatchEvent=Eg.prototype.b;
+Eg.prototype.getRevision=Eg.prototype.K;Eg.prototype.on=Eg.prototype.I;Eg.prototype.once=Eg.prototype.once;Eg.prototype.un=Eg.prototype.J;go.prototype.getMap=go.prototype.f;go.prototype.setMap=go.prototype.setMap;go.prototype.setTarget=go.prototype.i;go.prototype.get=go.prototype.get;go.prototype.getKeys=go.prototype.P;go.prototype.getProperties=go.prototype.L;go.prototype.set=go.prototype.set;go.prototype.setProperties=go.prototype.H;go.prototype.unset=go.prototype.R;go.prototype.changed=go.prototype.u;
+go.prototype.dispatchEvent=go.prototype.b;go.prototype.getRevision=go.prototype.K;go.prototype.on=go.prototype.I;go.prototype.once=go.prototype.once;go.prototype.un=go.prototype.J;lo.prototype.getMap=lo.prototype.f;lo.prototype.setMap=lo.prototype.setMap;lo.prototype.setTarget=lo.prototype.i;lo.prototype.get=lo.prototype.get;lo.prototype.getKeys=lo.prototype.P;lo.prototype.getProperties=lo.prototype.L;lo.prototype.set=lo.prototype.set;lo.prototype.setProperties=lo.prototype.H;lo.prototype.unset=lo.prototype.R;
+lo.prototype.changed=lo.prototype.u;lo.prototype.dispatchEvent=lo.prototype.b;lo.prototype.getRevision=lo.prototype.K;lo.prototype.on=lo.prototype.I;lo.prototype.once=lo.prototype.once;lo.prototype.un=lo.prototype.J;
   return OPENLAYERS.ol;
 }));
 
diff --git a/spatial/static/spatial/js/gridmap.js b/spatial/static/spatial/js/gridmap.js
index 8763e8273d4e71bc064dee3e7c21bcbb15a32a4b..7ac2250fb78782a3a641d39c54635cbf978217b6 100644
--- a/spatial/static/spatial/js/gridmap.js
+++ b/spatial/static/spatial/js/gridmap.js
@@ -169,7 +169,8 @@ function getLayersForCurrentTimestamp()
  */
 async function initGridMap(inputModelId, wmsURL, mapAttribution) {
     modelId = inputModelId;
-    var backgroundLayer = new ol.layer.Tile({
+    console.info("Init grid map " + inputModelId)
+    let backgroundLayer = new ol.layer.Tile({
 		source: new ol.source.OSM({
 			attributions: [
 				new ol.Attribution({
@@ -557,4 +558,4 @@ function moveDateSlider(step)
     newValue = newValue > max ? max : newValue < min ? min : newValue;
     slider.value=newValue;
     slider.onchange();
-}
\ No newline at end of file
+}