[openlayers3] 01/08: Imported Upstream version 3.2.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sun Jan 24 20:22:11 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository openlayers3.

commit c885503f88179c2f9c38116222683af27ddc2fda
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Feb 26 19:36:17 2015 +0100

    Imported Upstream version 3.2.0
---
 .jshintrc                                          |    5 +-
 AUTHORS.md                                         |   58 -
 CONTRIBUTING.md                                    |   12 +-
 LICENSE.md                                         |    3 +-
 README.md                                          |    2 +-
 build.py                                           |   16 +-
 changelog/v3.2.0.md                                |   86 +
 closure-util.json                                  |    2 +-
 config/jsdoc/api/conf.json                         |    2 +-
 config/ol.json                                     |    4 +-
 css/ol.css                                         |   14 +-
 examples/animation.js                              |    3 +
 examples/bing-maps.js                              |    3 +
 examples/custom-controls.html                      |   29 +-
 examples/custom-controls.js                        |   15 +-
 examples/data/WMTSCapabilities.xml                 | 2725 +++++++++++++++++---
 examples/drag-and-drop-image-vector.js             |    5 +-
 examples/drag-and-drop.js                          |    5 +-
 examples/getfeatureinfo-image.html                 |    4 +-
 examples/getfeatureinfo-image.js                   |   15 +-
 examples/getfeatureinfo-tile.html                  |    4 +-
 examples/getfeatureinfo-tile.js                    |   15 +-
 examples/gpx.js                                    |    5 +-
 examples/icon-sprite-webgl.html                    |    7 +-
 examples/icon-sprite-webgl.js                      |   31 +
 examples/icon.js                                   |   20 +-
 examples/igc.js                                    |    5 +-
 examples/image-vector-layer.js                     |    5 +-
 examples/kml-earthquakes.js                        |    6 +-
 examples/kml-timezones.js                          |    6 +-
 examples/kml.js                                    |    5 +-
 examples/measure.html                              |   36 +-
 examples/measure.js                                |  143 +-
 ...tfeatureinfo-image.html => polygon-styles.html} |   24 +-
 examples/polygon-styles.js                         |  101 +
 examples/popup.html                                |    2 -
 examples/popup.js                                  |    6 +-
 examples/synthetic-points.js                       |   18 +-
 examples/tileutfgrid.js                            |    5 +-
 examples/vector-layer.js                           |    5 +-
 ...on-sprite-webgl.html => wmts-capabilities.html} |   26 +-
 examples/wmts-capabilities.js                      |    8 +
 externs/geojson.js                                 |   26 +-
 externs/oli.js                                     |    6 +
 externs/olx.js                                     |  208 +-
 package.json                                       |    4 +-
 resources/example-behaviour.js                     |    8 +-
 src/ol/collection.js                               |    2 +-
 src/ol/control/attributioncontrol.js               |   34 +-
 src/ol/control/control.js                          |   21 +-
 src/ol/control/fullscreencontrol.js                |   29 +-
 src/ol/control/overviewmapcontrol.js               |   35 +-
 src/ol/control/rotatecontrol.js                    |   17 +-
 src/ol/control/scalelinecontrol.js                 |    2 +-
 src/ol/control/zoomtoextentcontrol.js              |    3 +-
 src/ol/featureoverlay.js                           |   11 +-
 src/ol/format/featureformat.js                     |    9 +-
 src/ol/format/geojsonformat.js                     |   13 +-
 src/ol/format/gml/gml2format.js                    |    8 +-
 src/ol/format/gml/gml3format.js                    |   53 +-
 src/ol/format/gml/gmlbaseformat.js                 |   50 +-
 src/ol/format/gpxformat.js                         |   69 +-
 src/ol/format/igcformat.js                         |    8 -
 src/ol/format/kmlformat.js                         |  220 +-
 src/ol/format/osmxmlformat.js                      |   22 +-
 src/ol/format/owsformat.js                         |  121 +-
 src/ol/format/polylineformat.js                    |    8 -
 src/ol/format/textfeatureformat.js                 |    4 +-
 src/ol/format/topojsonformat.js                    |    2 +-
 src/ol/format/wfsformat.js                         |   42 +-
 src/ol/format/wktformat.js                         |    8 -
 src/ol/format/wmscapabilitiesformat.js             |   58 +-
 src/ol/format/wmsgetfeatureinfoformat.js           |    7 +-
 src/ol/format/wmtscapabilitiesformat.js            |  400 +++
 src/ol/format/xmlfeatureformat.js                  |    8 +-
 src/ol/geom/circle.js                              |   20 +-
 src/ol/geom/flat/geodesicflatgeom.js               |    6 +-
 src/ol/geom/geometry.js                            |   29 +-
 src/ol/geom/geometrycollection.js                  |   20 +-
 src/ol/geom/linestring.js                          |    2 +-
 src/ol/geom/multilinestring.js                     |    4 +-
 src/ol/geom/point.js                               |   11 +-
 src/ol/geom/simplegeometry.js                      |   14 +-
 src/ol/interaction/dragrotateandzoominteraction.js |    4 +-
 src/ol/interaction/dragrotateinteraction.js        |    9 +-
 src/ol/interaction/interaction.js                  |    1 -
 src/ol/interaction/pinchrotateinteraction.js       |   10 +-
 src/ol/interaction/pinchzoominteraction.js         |   15 +-
 src/ol/interaction/pointerinteraction.js           |    1 -
 src/ol/interaction/selectinteraction.js            |   30 +-
 src/ol/layer/heatmaplayer.js                       |   10 +-
 src/ol/layer/layer.js                              |    1 -
 src/ol/layer/layerbase.js                          |   69 +-
 src/ol/layer/layergroup.js                         |    2 +-
 src/ol/layer/tilelayer.js                          |   23 +-
 src/ol/layer/vectorlayer.js                        |   18 +
 src/ol/map.js                                      |  129 +-
 src/ol/mapbrowserevent.js                          |   77 +-
 src/ol/object.js                                   |    4 +-
 src/ol/overlay.js                                  |    3 +-
 src/ol/pointer/mousesource.js                      |    6 +-
 src/ol/pointer/mssource.js                         |    3 +-
 src/ol/pointer/touchsource.js                      |    6 +-
 src/ol/proj/proj.js                                |    2 +-
 src/ol/render/canvas/canvasreplay.js               |  101 +-
 src/ol/render/ireplay.js                           |    1 -
 src/ol/render/vector.js                            |   17 +-
 src/ol/render/webgl/webglimmediate.js              |    1 +
 src/ol/render/webgl/webglreplay.js                 |  548 +++-
 src/ol/renderer/canvas/canvasimagelayerrenderer.js |   82 +-
 src/ol/renderer/canvas/canvaslayerrenderer.js      |   24 +-
 src/ol/renderer/canvas/canvastilelayerrenderer.js  |   38 +-
 .../renderer/canvas/canvasvectorlayerrenderer.js   |   15 +-
 src/ol/renderer/dom/domimagelayerrenderer.js       |   17 +-
 src/ol/renderer/dom/domlayerrenderer.js            |    1 -
 src/ol/renderer/dom/dommaprenderer.js              |    5 +-
 src/ol/renderer/dom/domtilelayerrenderer.js        |    3 -
 src/ol/renderer/dom/domvectorlayerrenderer.js      |   51 +-
 src/ol/renderer/layerrenderer.js                   |   73 +-
 src/ol/renderer/maprenderer.js                     |   85 +-
 src/ol/renderer/webgl/webglimagelayerrenderer.js   |  166 +-
 src/ol/renderer/webgl/webgllayerrenderer.js        |   12 +-
 src/ol/renderer/webgl/webglmaprenderer.js          |  183 +-
 src/ol/renderer/webgl/webgltilelayerrenderer.js    |   40 +-
 src/ol/renderer/webgl/webglvectorlayerrenderer.js  |   84 +-
 src/ol/source/bingmapssource.js                    |    8 +-
 src/ol/source/clustersource.js                     |    4 +-
 src/ol/source/imagemapguidesource.js               |   21 +
 src/ol/source/imagevectorsource.js                 |    8 +-
 src/ol/source/imagewmssource.js                    |   41 +-
 src/ol/source/mapquestsource.js                    |    3 +-
 src/ol/source/osmsource.js                         |    3 +-
 src/ol/source/servervectorsource.js                |    5 +-
 src/ol/source/source.js                            |    4 +-
 src/ol/source/tilewmssource.js                     |   23 +-
 src/ol/source/vectorsource.js                      |    4 +-
 src/ol/structs/rbush.js                            |   26 +-
 src/ol/style/atlasmanager.js                       |   33 +-
 src/ol/style/circlestyle.js                        |   16 -
 src/ol/style/iconstyle.js                          |    8 -
 src/ol/style/imagestyle.js                         |    7 -
 src/ol/style/regularshapestyle.js                  |   17 +-
 src/ol/tilecache.js                                |    1 -
 src/ol/view.js                                     |   11 +-
 src/ol/webgl/context.js                            |  126 +
 src/ol/xml.js                                      |    6 +-
 test/spec/ol/featureoverlay.test.js                |    8 +
 test/spec/ol/format/gml/ogr.xml                    |   26 +
 test/spec/ol/format/gmlformat.test.js              |   20 +
 test/spec/ol/format/gpxformat.test.js              |   12 +
 test/spec/ol/format/igcformat.test.js              |    7 +
 test/spec/ol/format/kmlformat.test.js              |   51 +
 test/spec/ol/format/osmxmlformat.test.js           |   12 +
 test/spec/ol/format/owsformat.test.js              |   46 +-
 test/spec/ol/format/polylineformat.test.js         |    6 +
 test/spec/ol/format/wfs/EmptyFeatureCollection.xml |    9 +
 test/spec/ol/format/wfsformat.test.js              |   15 +
 test/spec/ol/format/wktformat.test.js              |    7 +
 test/spec/ol/format/wmts/ogcsample.xml             |  184 ++
 test/spec/ol/format/wmtscapabilitiesformat.test.js |  106 +
 ...{reverseflatgeom.js => reverseflatgeom.test.js} |    0
 test/spec/ol/layer/layer.test.js                   |    8 +
 test/spec/ol/layer/tilelayer.test.js               |   37 +
 test/spec/ol/mapbrowserevent.test.js               |   10 +-
 test/spec/ol/render/vector.test.js                 |  148 +-
 test/spec/ol/render/webglreplay.test.js            |   12 +
 .../canvas/canvasvectorlayerrenderer.test.js       |    8 +-
 test/spec/ol/renderer/layerrenderer.test.js        |   89 +
 test/spec/ol/structs/rbush.test.js                 |    8 +
 test/spec/ol/style/atlasmanager.test.js            |   17 +-
 test/spec/ol/style/circlestyle.test.js             |    4 -
 test/spec/ol/style/regularshapestyle.test.js       |    4 -
 test/spec/ol/view.test.js                          |   18 +
 173 files changed, 6672 insertions(+), 1658 deletions(-)

diff --git a/.jshintrc b/.jshintrc
index 111b15e..036fde7 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,4 +1,5 @@
 {
-	"-W030": true,
-	"-W083": true
+  "-W030": true,
+  "-W083": true,
+  "-W069": true
 }
diff --git a/AUTHORS.md b/AUTHORS.md
deleted file mode 100644
index edb3f31..0000000
--- a/AUTHORS.md
+++ /dev/null
@@ -1,58 +0,0 @@
-OpenLayers contributors:
-
-* Antoine Abt
-* Mike Adair
-* Jeff Adams
-* Seb Benthall
-* Bruno Binet
-* Stéphane Brunner
-* Howard Butler
-* Bertil Chaupis
-* John Cole
-* Tim Coulter
-* Robert Coup
-* Jeff Dege
-* Roald de Wit
-* Schuyler Erle
-* Christian López Espínola
-* John Frank
-* Sean Gilles
-* Pierre Giraud
-* Ivan Grcic
-* Andreas Hocevar
-* Marc Jansen
-* Ian Johnson
-* Frédéric Junod
-* Eric Lemoine
-* Philip Lindsay
-* Martijn van Oosterhout
-* David Overstrom
-* Tom Payne
-* Corey Puffault
-* Peter William Robins
-* Gregers Rygg
-* Tim Schaub
-* Christopher Schmidt
-* Tobias Schwinger
-* Cameron Shorter
-* Pedro Simonetti
-* Paul Spencer
-* Paul Smith
-* Glen Stampoultzis
-* James Stembridge
-* Erik Uzureau
-* Bart van den Eijnden
-* Ivan Willig
-* Thomas Wood
-* Bill Woodall
-* Steve Woodbridge
-* David Zwarg
-
-Some portions of OpenLayers are used under the Apache 2.0 license, available
-in doc/licenses/APACHE-2.0.txt.
-
-Some portions of OpenLayers are used under the MIT license, availabie in
-doc/licenses/MIT-LICENSE.txt.
-
-Some portions of OpenLayers are Copyright 2001 Robert Penner, and are used
-under the BSD license, available in doc/licenses/BSD-LICENSE.txt
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 79b7cbb..0db2658 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,7 +42,7 @@ as described below.
 The minimum requirements are:
 
 * Git
-* [Node.js](http://nodejs.org/) 
+* [Node.js](http://nodejs.org/) (0.10.x or higher)
 * Python 2.6 or 2.7 with a couple of extra modules (see below)
 * Java 7 (JRE and JDK)
 
@@ -132,7 +132,7 @@ To run the tests on the console (headless testing with PhantomJS) use the `test`
 
     $ ./build.py test
 
-See also the test-specific [README](../blob/master/test/README.md).
+See also the test-specific [README](../master/test/README.md).
 
 ## Running the integration tests
 
@@ -239,6 +239,14 @@ style of the existing OpenLayers 3 code, which includes:
 
  * Do not use assignments inside expressions.
 
+ * Avoid the use of `goog.array.clone` with arrays (use slice instead).
+
+ * Use `array.length = 0` instead of `goog.array.clear`.
+
+ * Use bracket notation instead of `goog.object.set` and `goog.object.get` (with
+   two arguments).
+
+ * Use uppercase for `@const` variables.
 
 ### Pass the integration tests run automatically by the Travis CI system
 
diff --git a/LICENSE.md b/LICENSE.md
index 7315e48..d3e5675 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,5 +1,4 @@
-Copyright 2005-2014 OpenLayers Contributors. All rights reserved. See
-AUTHORS.md for full list.
+Copyright 2005-2014 OpenLayers Contributors. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the following conditions are met:
diff --git a/README.md b/README.md
index e46ba76..6a381c2 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # OpenLayers 3
 
-[![Travis CI Status](https://secure.travis-ci.org/openlayers/ol3.png)](http://travis-ci.org/#!/openlayers/ol3)
+[![Travis CI Status](https://secure.travis-ci.org/openlayers/ol3.svg)](http://travis-ci.org/#!/openlayers/ol3)
 
 Welcome to [OpenLayers 3](http://openlayers.org/)!
 
diff --git a/build.py b/build.py
index 9b53484..603a975 100755
--- a/build.py
+++ b/build.py
@@ -172,7 +172,8 @@ virtual('ci', 'lint', 'build', 'test',
     'build/examples/all.combined.js', 'check-examples', 'apidoc')
 
 
-virtual('build', 'build/ol.css', 'build/ol.js', 'build/ol-debug.js')
+virtual('build', 'build/ol.css', 'build/ol.js', 'build/ol-debug.js',
+    'build/ol.js.map')
 
 
 virtual('check', 'lint', 'build/ol.js', 'test')
@@ -192,12 +193,21 @@ def build_ol_css(t):
     t.output('%(CLEANCSS)s', 'css/ol.css')
 
 
- at target('build/ol.js', SRC, SHADER_SRC, 'config/ol.json', NPM_INSTALL)
-def build_ol_new_js(t):
+def _build_js(t):
     t.run('node', 'tasks/build.js', 'config/ol.json', 'build/ol.js')
+
+
+ at target('build/ol.js', SRC, SHADER_SRC, 'config/ol.json', NPM_INSTALL)
+def build_ol_js(t):
+    _build_js(t)
     report_sizes(t)
 
 
+ at target('build/ol.js.map', SRC, SHADER_SRC, 'config/ol.json', NPM_INSTALL)
+def build_ol_js_map(t):
+    _build_js(t)
+
+
 @target('build/ol-debug.js', SRC, SHADER_SRC, 'config/ol-debug.json',
         NPM_INSTALL)
 def build_ol_debug_js(t):
diff --git a/changelog/v3.2.0.md b/changelog/v3.2.0.md
new file mode 100644
index 0000000..86af8f5
--- /dev/null
+++ b/changelog/v3.2.0.md
@@ -0,0 +1,86 @@
+# 3.2.0
+
+## Summary
+
+The 3.1.0 release includes 70 merged pull requests since 3.1.0.  Of note, the KML format [now parses `NetworkingLink` tags](https://github.com/openlayers/ol3/pull/3171).  The [measure example](http://openlayers.org/en/v3.2.0/examples/measure.html) was [reworked](https://github.com/openlayers/ol3/pull/3206) to display measurements and help messages while drawing.  A WMTS GetCapabilities format was [added](https://github.com/openlayers/ol3/pull/3026).  The WebGL renderer [now supports feat [...]
+
+## Upgrade notes
+
+The 3.2.0 release maintains a backwards-compatible API with the 3.1.0 release, so upgrades should be painless.  Some special considerations below.
+
+ * You should not call `view.setRotation` with `undefined`, to reset the view rotation to `0` then use `view.setRotation(0)` (see [#3176](https://github.com/openlayers/ol3/pull/3176)).
+
+ * If you use `$(map.getViewport()).on('mousemove')` to detect features when the mouse is hovered on the map, you should now rely on the `pointermove` map event type and check in the `pointermove` listener that the `dragging` event property is `false` (see [#3190](https://github.com/openlayers/ol3/pull/3190)).
+
+## Changes
+
+ * [#3171](https://github.com/openlayers/ol3/pull/3171) - KML: Parsing of NetworkLink tag ([@oterral](https://github.com/oterral))
+ * [#3209](https://github.com/openlayers/ol3/pull/3209) - Coding style fixes ([@fredj](https://github.com/fredj))
+ * [#3208](https://github.com/openlayers/ol3/pull/3208) - Add setters and getters for imageLoadFunction ([@bartvde](https://github.com/bartvde))
+ * [#3019](https://github.com/openlayers/ol3/pull/3019) - Add option to allow Select interaction logic to select overlapping features ([@bjornharrtell](https://github.com/bjornharrtell))
+ * [#3206](https://github.com/openlayers/ol3/pull/3206) - Add tooltip to show measure + help message while drawing ([@pgiraud](https://github.com/pgiraud))
+ * [#3205](https://github.com/openlayers/ol3/pull/3205) - Use ol.extent.createOrUpdateFromCoordinate ([@fredj](https://github.com/fredj))
+ * [#3026](https://github.com/openlayers/ol3/pull/3026) - Add support of reading WMTS Get Cap document ([@htulipe](https://github.com/htulipe))
+ * [#3201](https://github.com/openlayers/ol3/pull/3201) - Pass on opt_fast to parent clear function in ol.source.ServerVector (r=@elemoine, at gberaudo) ([@bartvde](https://github.com/bartvde))
+ * [#3199](https://github.com/openlayers/ol3/pull/3199) - Minor jsdoc fixes ([@fredj](https://github.com/fredj))
+ * [#3059](https://github.com/openlayers/ol3/pull/3059) - Cache the buffered extent value ([@fredj](https://github.com/fredj))
+ * [#3196](https://github.com/openlayers/ol3/pull/3196) - Remove unnecessary newlines ([@fredj](https://github.com/fredj))
+ * [#3099](https://github.com/openlayers/ol3/pull/3099) - Fix up parsing of OGR GML with ol.format.GML ([@bartvde](https://github.com/bartvde))
+ * [#3195](https://github.com/openlayers/ol3/pull/3195) - Coding style ([@fredj](https://github.com/fredj))
+ * [#3192](https://github.com/openlayers/ol3/pull/3192) - Add "url" option to ol.source.MapQuest ([@elemoine](https://github.com/elemoine))
+ * [#3172](https://github.com/openlayers/ol3/pull/3172) - Introduce forEachLayerAtPixel ([@tsauerwein](https://github.com/tsauerwein))
+ * [#3178](https://github.com/openlayers/ol3/pull/3178) - GeoJSON externs fixes ([@fredj](https://github.com/fredj))
+ * [#3179](https://github.com/openlayers/ol3/pull/3179) - Disallow undefined values for ol.layer.Base ([@fredj](https://github.com/fredj))
+ * [#3161](https://github.com/openlayers/ol3/pull/3161) - Doc fix. writeFeaturesNode receives an array of Feature ([@3x0dv5](https://github.com/3x0dv5))
+ * [#3169](https://github.com/openlayers/ol3/pull/3169) - Fix default icon style in kml format ([@oterral](https://github.com/oterral))
+ * [#3190](https://github.com/openlayers/ol3/pull/3190) - Introduce `dragging` flag for MapBrowserEvent ([@tsauerwein](https://github.com/tsauerwein))
+ * [#3135](https://github.com/openlayers/ol3/pull/3135) - Make changing the label of ZoomToExtent/FullScreen control consistent ([@tsauerwein](https://github.com/tsauerwein))
+ * [#3186](https://github.com/openlayers/ol3/pull/3186) - Take the pixel ratio into account when clipping the layer ([@fredj](https://github.com/fredj))
+ * [#3183](https://github.com/openlayers/ol3/pull/3183) - Allow other params than 'mode' in example page query string. ([@htulipe](https://github.com/htulipe))
+ * [#2791](https://github.com/openlayers/ol3/pull/2791) - Re enable rotation button transition ([@fredj](https://github.com/fredj))
+ * [#3180](https://github.com/openlayers/ol3/pull/3180) - Add a getMap function to ol.FeatureOverlay (r=@ahocevar) ([@bartvde](https://github.com/bartvde))
+ * [#3176](https://github.com/openlayers/ol3/pull/3176) - Disallowed undefined rotation value ([@fredj](https://github.com/fredj))
+ * [#3177](https://github.com/openlayers/ol3/pull/3177) - Add example showing how to style polygon vertices ([@tsauerwein](https://github.com/tsauerwein))
+ * [#3174](https://github.com/openlayers/ol3/pull/3174) - Use view.getRotation or view.getResolution instead of view.getState ([@fredj](https://github.com/fredj))
+ * [#3170](https://github.com/openlayers/ol3/pull/3170) - Coding style ([@fredj](https://github.com/fredj))
+ * [#3108](https://github.com/openlayers/ol3/pull/3108) - Support skipping features in the WebGL renderer ([@tsauerwein](https://github.com/tsauerwein))
+ * [#3163](https://github.com/openlayers/ol3/pull/3163) - Use the layerStatesArray property from the frameState ([@fredj](https://github.com/fredj))
+ * [#3159](https://github.com/openlayers/ol3/pull/3159) - Don't pass specific options to the parent constructor ([@fredj](https://github.com/fredj))
+ * [#3066](https://github.com/openlayers/ol3/pull/3066) - Introduce hasFeatureAtPixel ([@tsauerwein](https://github.com/tsauerwein))
+ * [#3065](https://github.com/openlayers/ol3/pull/3065) - Add hit-detection support for WebGL ([@tsauerwein](https://github.com/tsauerwein))
+ * [#3128](https://github.com/openlayers/ol3/pull/3128) - Allow rendering of feature when download of icon failed ([@oterral](https://github.com/oterral))
+ * [#3156](https://github.com/openlayers/ol3/pull/3156) - Move readProjectionFrom* functions to the base classes ([@fredj](https://github.com/fredj))
+ * [#3107](https://github.com/openlayers/ol3/pull/3107) - Also listen on loading images ([@elemoine](https://github.com/elemoine))
+ * [#3153](https://github.com/openlayers/ol3/pull/3153) - Add missing GeoJSONFeature#bbox property ([@fredj](https://github.com/fredj))
+ * [#3154](https://github.com/openlayers/ol3/pull/3154) - Remove unnecessary newlines ([@fredj](https://github.com/fredj))
+ * [#3146](https://github.com/openlayers/ol3/pull/3146) - Enable tests for ol.geom.flat.reverse ([@icholy](https://github.com/icholy))
+ * [#3152](https://github.com/openlayers/ol3/pull/3152) - Update closure-library and closure-util version ([@fredj](https://github.com/fredj))
+ * [#3145](https://github.com/openlayers/ol3/pull/3145) - Add wrapX option to source.OSM and source.BingMaps ([@elemoine](https://github.com/elemoine))
+ * [#3139](https://github.com/openlayers/ol3/pull/3139) - Add ol.control.Control#setTarget ([@elemoine](https://github.com/elemoine))
+ * [#3144](https://github.com/openlayers/ol3/pull/3144) - Update CONTRIBUTING style guide with recent guidelines ([@bartvde](https://github.com/bartvde))
+ * [#3136](https://github.com/openlayers/ol3/pull/3136) - Use array.length = 0 instead of goog.array.clear ([@fredj](https://github.com/fredj))
+ * [#3140](https://github.com/openlayers/ol3/pull/3140) - Avoid use of goog.array.clone with arrays. ([@tschaub](https://github.com/tschaub))
+ * [#3122](https://github.com/openlayers/ol3/pull/3122) - Revert "Use offsetX and offsetY if available" ([@fredj](https://github.com/fredj))
+ * [#2385](https://github.com/openlayers/ol3/pull/2385) - Option to update vector layers while animating ([@ahocevar](https://github.com/ahocevar))
+ * [#3129](https://github.com/openlayers/ol3/pull/3129) - Only update the rbush item if the extent has changed ([@fredj](https://github.com/fredj))
+ * [#3117](https://github.com/openlayers/ol3/pull/3117) - Add pixelRatio support for DOM vector renderer ([@ahocevar](https://github.com/ahocevar))
+ * [#3124](https://github.com/openlayers/ol3/pull/3124) - Add a space between scale -value and -unit ([@sirtet](https://github.com/sirtet))
+ * [#3130](https://github.com/openlayers/ol3/pull/3130) - Document default value ([@fredj](https://github.com/fredj))
+ * [#3105](https://github.com/openlayers/ol3/pull/3105) - ol.geom.Geometry#getExtent re-factoring ([@fredj](https://github.com/fredj))
+ * [#3118](https://github.com/openlayers/ol3/pull/3118) - Bugfix: "Cannot read property 'firstElementChild' of null" (WFS) ([@naturalatlas](https://github.com/naturalatlas))
+ * [#3114](https://github.com/openlayers/ol3/pull/3114) - Specify node version in CONTRIBUTING.md ([@elemoine](https://github.com/elemoine))
+ * [#3106](https://github.com/openlayers/ol3/pull/3106) - Don't pass specific options to the parent constructor ([@fredj](https://github.com/fredj))
+ * [#3110](https://github.com/openlayers/ol3/pull/3110) - Use svg instead of png to get better image quality ([@PeterDaveHello](https://github.com/PeterDaveHello))
+ * [#2707](https://github.com/openlayers/ol3/pull/2707) - Generate source map of minified ol.js ([@gberaudo](https://github.com/gberaudo))
+ * [#3104](https://github.com/openlayers/ol3/pull/3104) - Don't pass renderBuffer option to the parent constructor ([@fredj](https://github.com/fredj))
+ * [#3096](https://github.com/openlayers/ol3/pull/3096) - popup example cleanup / simplification ([@fredj](https://github.com/fredj))
+ * [#3072](https://github.com/openlayers/ol3/pull/3072) - Optimize canvas hit detection by rendering features in a limited extent. ([@tschaub](https://github.com/tschaub))
+ * [#3101](https://github.com/openlayers/ol3/pull/3101) - Use bracket notation instead of goog.object functions. ([@tschaub](https://github.com/tschaub))
+ * [#3079](https://github.com/openlayers/ol3/pull/3079) - Exclude source files from docs. ([@tschaub](https://github.com/tschaub))
+ * [#3100](https://github.com/openlayers/ol3/pull/3100) - Assert that ol.layer.Tile#getPreload is always set ([@fredj](https://github.com/fredj))
+ * [#3084](https://github.com/openlayers/ol3/pull/3084) - Changes from the v3.1.x branch. ([@openlayers](https://github.com/openlayers))
+ * [#3094](https://github.com/openlayers/ol3/pull/3094) - Remove AUTHORS.md. ([@tschaub](https://github.com/tschaub))
+ * [#3089](https://github.com/openlayers/ol3/pull/3089) - Fixed URL link for test README ([@mike-mcgann](https://github.com/mike-mcgann))
+ * [#2894](https://github.com/openlayers/ol3/pull/2894) - Simplify CSS code in custom-controls example ([@fredj](https://github.com/fredj))
+ * [#3085](https://github.com/openlayers/ol3/pull/3085) - Fixed documentation typo for return value of ol.proj.Projection.isGlobal(). ([@mike-mcgann](https://github.com/mike-mcgann))
+ * [#3073](https://github.com/openlayers/ol3/pull/3073) - Make map's deviceOptions map options ([@ahocevar](https://github.com/ahocevar))
diff --git a/closure-util.json b/closure-util.json
index 153e451..1187d1d 100644
--- a/closure-util.json
+++ b/closure-util.json
@@ -1,3 +1,3 @@
 {
-  "library_url": "https://github.com/google/closure-library/archive/ad5e66c1e7d7829b0d77feae49aaf5f011265715.zip"
+  "library_url": "https://github.com/google/closure-library/archive/0011afd534469ba111786fe68300a634e08a4d80.zip"
 }
diff --git a/config/jsdoc/api/conf.json b/config/jsdoc/api/conf.json
index 4858196..9560ae8 100644
--- a/config/jsdoc/api/conf.json
+++ b/config/jsdoc/api/conf.json
@@ -34,7 +34,7 @@
         "cleverLinks": true,
         "monospaceLinks": true,
         "default": {
-            "outputSourceFiles": true
+            "outputSourceFiles": false
         },
         "applicationName": "OpenLayers 3"
     },
diff --git a/config/ol.json b/config/ol.json
index 5b09d26..c74879e 100644
--- a/config/ol.json
+++ b/config/ol.json
@@ -61,6 +61,8 @@
     "compilation_level": "ADVANCED",
     "warning_level": "VERBOSE",
     "use_types_for_optimization": true,
-    "manage_closure_dependencies": true
+    "manage_closure_dependencies": true,
+    "create_source_map": "build/ol.js.map",
+    "source_map_format": "V3"
   }
 }
diff --git a/css/ol.css b/css/ol.css
index 44ea0f3..a725e22 100644
--- a/css/ol.css
+++ b/css/ol.css
@@ -53,11 +53,12 @@
 .ol-rotate {
   top: .5em;
   right: .5em;
-  transition: opacity .25s;
+  transition: opacity .25s linear, visibility 0s linear;
 }
 .ol-rotate.ol-hidden {
   opacity: 0;
-  display: none;
+  visibility: hidden;
+  transition: opacity .25s linear, visibility 0s linear .25s;
 }
 .ol-zoom-extent {
   top: 4.643em;
@@ -114,21 +115,12 @@
   background-color: #4c6079;
   background-color: rgba(0,60,136,0.7);
 }
-.ol-zoom-extent button:after {
-    content: "E";
-}
 .ol-zoom .ol-zoom-in {
   border-radius: 2px 2px 0 0;
 }
 .ol-zoom .ol-zoom-out {
   border-radius: 0 0 2px 2px;
 }
-button.ol-full-screen-false:after {
-  content: "\2194";
-}
-button.ol-full-screen-true:after {
-  content: "\00d7";
-}
 
 
 .ol-attribution {
diff --git a/examples/animation.js b/examples/animation.js
index 2db3f7f..a5a85ee 100644
--- a/examples/animation.js
+++ b/examples/animation.js
@@ -55,6 +55,9 @@ var map = new ol.Map({
     })
   ],
   renderer: exampleNS.getRendererFromQueryString(),
+  // Improve user experience by loading tiles while animating. Will make
+  // animations stutter on mobile or slow devices.
+  loadTilesWhileAnimating: true,
   target: 'map',
   controls: ol.control.defaults({
     attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
diff --git a/examples/bing-maps.js b/examples/bing-maps.js
index 556076c..99795bf 100644
--- a/examples/bing-maps.js
+++ b/examples/bing-maps.js
@@ -29,6 +29,9 @@ for (i = 0, ii = styles.length; i < ii; ++i) {
 var map = new ol.Map({
   layers: layers,
   renderer: exampleNS.getRendererFromQueryString(),
+  // Improve user experience by loading tiles while dragging/zooming. Will make
+  // zooming choppy on mobile or slow devices.
+  loadTilesWhileInteracting: true,
   target: 'map',
   view: new ol.View({
     center: [-6655.5402445057125, 6709968.258934638],
diff --git a/examples/custom-controls.html b/examples/custom-controls.html
index 0d6cdf6..5e5cfb4 100644
--- a/examples/custom-controls.html
+++ b/examples/custom-controls.html
@@ -10,39 +10,12 @@
     <link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
     <style type="text/css">
       .rotate-north {
-        position: absolute;
         top: 65px;
-        left: 8px;
-        background: rgba(255,255,255,0.4);
-        border-radius: 4px;
-        padding: 2px;
+        left: .5em;
       }
       .ol-touch .rotate-north {
         top: 80px;
       }
-      .rotate-north a {
-        display: block;
-        color: white;
-        font-size: 16px;
-        font-family: 'Lucida Grande',Verdana,Geneva,Lucida,Arial,Helvetica,sans-serif;
-        font-weight: bold;
-        margin: 1px;
-        text-decoration: none;
-        text-align: center;
-        border-radius: 2px;
-        height: 22px;
-        width: 22px;
-        background: rgba(0,60,136,0.5);
-      }
-      .ol-touch .rotate-north a {
-        font-size: 20px;
-        height: 30px;
-        width: 30px;
-        line-height: 26px;
-      }
-      .rotate-north a:hover {
-        background: rgba(0,60,136,0.7);
-      }
     </style>
     <title>ol3 custom controls example</title>
   </head>
diff --git a/examples/custom-controls.js b/examples/custom-controls.js
index 8181eca..3da26b4 100644
--- a/examples/custom-controls.js
+++ b/examples/custom-controls.js
@@ -29,23 +29,20 @@ app.RotateNorthControl = function(opt_options) {
 
   var options = opt_options || {};
 
-  var anchor = document.createElement('a');
-  anchor.href = '#rotate-north';
-  anchor.innerHTML = 'N';
+  var button = document.createElement('button');
+  button.innerHTML = 'N';
 
   var this_ = this;
   var handleRotateNorth = function(e) {
-    // prevent #rotate-north anchor from getting appended to the url
-    e.preventDefault();
     this_.getMap().getView().setRotation(0);
   };
 
-  anchor.addEventListener('click', handleRotateNorth, false);
-  anchor.addEventListener('touchstart', handleRotateNorth, false);
+  button.addEventListener('click', handleRotateNorth, false);
+  button.addEventListener('touchstart', handleRotateNorth, false);
 
   var element = document.createElement('div');
-  element.className = 'rotate-north ol-unselectable';
-  element.appendChild(anchor);
+  element.className = 'rotate-north ol-unselectable ol-control';
+  element.appendChild(button);
 
   ol.control.Control.call(this, {
     element: element,
diff --git a/examples/data/WMTSCapabilities.xml b/examples/data/WMTSCapabilities.xml
index b58c464..8d8ed8c 100644
--- a/examples/data/WMTSCapabilities.xml
+++ b/examples/data/WMTSCapabilities.xml
@@ -1,374 +1,2357 @@
 <?xml version="1.0" encoding="UTF-8"?>
+
 <Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
-	<ows:ServiceIdentification>
-		<ows:Title>Web Map Tile Service</ows:Title>
-		<ows:ServiceType>OGC WMTS</ows:ServiceType>
-		<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
-		<ows:Fees>none</ows:Fees>
-		<ows:AccessConstraints>http://data.wien.gv.at/nutzungsbedingungen</ows:AccessConstraints>
-	</ows:ServiceIdentification>
-	<ows:ServiceProvider>
-		<ows:ProviderName>Magistrat Wien</ows:ProviderName>
-		<ows:ProviderSite xlink:href="http://www.wien.gv.at"/>
-		<ows:ServiceContact>
-			<ows:IndividualName>Gerhard Sommer</ows:IndividualName>
-		</ows:ServiceContact>
-	</ows:ServiceProvider>
-	<ows:OperationsMetadata>
-		<ows:Operation name="GetCapabilities">
-			<ows:DCP>
-				<ows:HTTP>
-					<ows:Get xlink:href="http://maps.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps1.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps2.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps3.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps4.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-				</ows:HTTP>
-			</ows:DCP>
-		</ows:Operation>
-		<ows:Operation name="GetTile">
-			<ows:DCP>
-				<ows:HTTP>
-					<ows:Get xlink:href="http://maps.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps1.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps2.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps3.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-					<ows:Get xlink:href="http://maps4.wien.gv.at/wmts">
-						<ows:Constraint name="GetEncoding">
-							<ows:AllowedValues>
-								<ows:Value>RESTful</ows:Value>
-							</ows:AllowedValues>
-						</ows:Constraint>
-					</ows:Get>
-				</ows:HTTP>
-			</ows:DCP>
-		</ows:Operation>
-	</ows:OperationsMetadata>
-	<Contents>
-		<Layer>
-			<ows:Title>Luftbild</ows:Title>
-			<ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
-				<ows:LowerCorner>16.17 48.10</ows:LowerCorner>
-				<ows:UpperCorner>16.58 48.33</ows:UpperCorner>
-			</ows:WGS84BoundingBox>
-			<ows:Identifier>lb</ows:Identifier>
-			<Style isDefault="true">
-				<ows:Identifier>farbe</ows:Identifier>
-			</Style>
-			<Format>image/jpeg</Format>
-			<TileMatrixSetLink>
-				<TileMatrixSet>google3857</TileMatrixSet>
-			</TileMatrixSetLink>
-			<ResourceURL format="image/jpeg" template="http://maps1.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps2.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps3.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps4.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-		</Layer>
-		<Layer>
-			<ows:Title>MZK Flächen</ows:Title>
-			<ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
-				<ows:LowerCorner>16.17 48.10</ows:LowerCorner>
-				<ows:UpperCorner>16.58 48.33</ows:UpperCorner>
-			</ows:WGS84BoundingBox>
-			<ows:Identifier>fmzk</ows:Identifier>
-			<Style isDefault="true">
-				<ows:Identifier>pastell</ows:Identifier>
-			</Style>
-			<Format>image/jpeg</Format>
-			<TileMatrixSetLink>
-				<TileMatrixSet>google3857</TileMatrixSet>
-			</TileMatrixSetLink>
-			<ResourceURL format="image/jpeg" template="http://maps1.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps2.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps3.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps4.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-		</Layer>
-		<Layer>
-			<ows:Title>Flächenwidmungs- und Bebauungsplan</ows:Title>
-			<ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
-				<ows:LowerCorner>16.17 48.10</ows:LowerCorner>
-				<ows:UpperCorner>16.58 48.33</ows:UpperCorner>
-			</ows:WGS84BoundingBox>
-			<ows:Identifier>flwbplmzk</ows:Identifier>
-			<Style isDefault="true">
-				<ows:Identifier>rot</ows:Identifier>
-			</Style>
-			<Format>image/jpeg</Format>
-			<TileMatrixSetLink>
-				<TileMatrixSet>google3857</TileMatrixSet>
-			</TileMatrixSetLink>
-			<ResourceURL format="image/jpeg" template="http://maps1.wien.gv.at/wmts/flwbplmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps2.wien.gv.at/wmts/flwbplmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps3.wien.gv.at/wmts/flwbplmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps4.wien.gv.at/wmts/flwbplmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-			<ResourceURL format="image/jpeg" template="http://maps.wien.gv.at/wmts/flwbplmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg" resourceType="tile"/>
-		</Layer>
-		<Layer>
-			<ows:Title>Beschriftung</ows:Title>
-			<ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
-				<ows:LowerCorner>16.17 48.10</ows:LowerCorner>
-				<ows:UpperCorner>16.58 48.33</ows:UpperCorner>
-			</ows:WGS84BoundingBox>
-			<ows:Identifier>beschriftung</ows:Identifier>
-			<Style isDefault="true">
-				<ows:Identifier>normal</ows:Identifier>
-			</Style>
-			<Format>image/png</Format>
-			<TileMatrixSetLink>
-				<TileMatrixSet>google3857</TileMatrixSet>
-			</TileMatrixSetLink>
-			<ResourceURL format="image/png" template="http://maps1.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png" resourceType="tile"/>
-			<ResourceURL format="image/png" template="http://maps2.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png" resourceType="tile"/>
-			<ResourceURL format="image/png" template="http://maps3.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png" resourceType="tile"/>
-			<ResourceURL format="image/png" template="http://maps4.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png" resourceType="tile"/>
-			<ResourceURL format="image/png" template="http://maps.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png" resourceType="tile"/>
-		</Layer>
-		<TileMatrixSet>
-			<!-- -180 85.05112878 -->
-			<ows:Identifier>google3857</ows:Identifier>
-			<ows:BoundingBox crs="urn:ogc:def:crs:EPSG:6.18:3:3857">
-				<ows:LowerCorner>1799448.394855 6124949.747770</ows:LowerCorner>
-				<ows:UpperCorner>1848250.442089 6162571.828177</ows:UpperCorner>
-			</ows:BoundingBox>
-			<ows:SupportedCRS>urn:ogc:def:crs:EPSG:6.18:3:3857</ows:SupportedCRS>
-			<WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible</WellKnownScaleSet>
-			<TileMatrix>
-				<ows:Identifier>0</ows:Identifier>
-				<ScaleDenominator>559082264.029</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>1</MatrixWidth>
-				<MatrixHeight>1</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>1</ows:Identifier>
-				<ScaleDenominator>279541132.015</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>2</MatrixWidth>
-				<MatrixHeight>2</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>2</ows:Identifier>
-				<ScaleDenominator>139770566.007</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>4</MatrixWidth>
-				<MatrixHeight>4</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>3</ows:Identifier>
-				<ScaleDenominator>69885283.0036</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>8</MatrixWidth>
-				<MatrixHeight>8</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>4</ows:Identifier>
-				<ScaleDenominator>34942641.5018</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>16</MatrixWidth>
-				<MatrixHeight>16</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>5</ows:Identifier>
-				<ScaleDenominator>17471320.7509</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>32</MatrixWidth>
-				<MatrixHeight>32</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>6</ows:Identifier>
-				<ScaleDenominator>8735660.37545</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>64</MatrixWidth>
-				<MatrixHeight>64</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>7</ows:Identifier>
-				<ScaleDenominator>4367830.18773</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>128</MatrixWidth>
-				<MatrixHeight>128</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>8</ows:Identifier>
-				<ScaleDenominator>2183915.09386</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>256</MatrixWidth>
-				<MatrixHeight>256</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>9</ows:Identifier>
-				<ScaleDenominator>1091957.54693</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>512</MatrixWidth>
-				<MatrixHeight>512</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>10</ows:Identifier>
-				<ScaleDenominator>545978.773466</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>1024</MatrixWidth>
-				<MatrixHeight>1024</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>11</ows:Identifier>
-				<ScaleDenominator>272989.386733</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>2048</MatrixWidth>
-				<MatrixHeight>2048</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>12</ows:Identifier>
-				<ScaleDenominator>136494.693366</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>4096</MatrixWidth>
-				<MatrixHeight>4096</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>13</ows:Identifier>
-				<ScaleDenominator>68247.3466832</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>8192</MatrixWidth>
-				<MatrixHeight>8192</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>14</ows:Identifier>
-				<ScaleDenominator>34123.6733416</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>16384</MatrixWidth>
-				<MatrixHeight>16384</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>15</ows:Identifier>
-				<ScaleDenominator>17061.8366708</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>32768</MatrixWidth>
-				<MatrixHeight>32768</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>16</ows:Identifier>
-				<ScaleDenominator>8530.91833540</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>65536</MatrixWidth>
-				<MatrixHeight>65536</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>17</ows:Identifier>
-				<ScaleDenominator>4265.45916770</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>131072</MatrixWidth>
-				<MatrixHeight>131072</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>18</ows:Identifier>
-				<ScaleDenominator>2132.72958385</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>262144</MatrixWidth>
-				<MatrixHeight>262144</MatrixHeight>
-			</TileMatrix>
-			<TileMatrix>
-				<ows:Identifier>19</ows:Identifier>
-				<ScaleDenominator>1066.36479193</ScaleDenominator>
-				<TopLeftCorner>-20037508.3428 20037508.3428</TopLeftCorner>
-				<TileWidth>256</TileWidth>
-				<TileHeight>256</TileHeight>
-				<MatrixWidth>524288</MatrixWidth>
-				<MatrixHeight>524288</MatrixHeight>
-			</TileMatrix>
-		</TileMatrixSet>
-	</Contents>
-	<ServiceMetadataURL xlink:href="http://maps.wien.gv.at/wmts/1.0.0/WMTSCapabilities.xml"/>
+    <ows:ServiceIdentification>
+        <ows:Title>Web Map Tile Service - GeoWebCache</ows:Title>
+        <ows:ServiceType>OGC WMTS</ows:ServiceType>
+        <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
+    </ows:ServiceIdentification>
+    <ows:ServiceProvider>
+        <ows:ProviderName>http://sdi.georchestra.org/geoserver/gwc/service/wmts</ows:ProviderName>
+        <ows:ProviderSite xlink:href="http://sdi.georchestra.org/geoserver/gwc/service/wmts"/>
+        <ows:ServiceContact>
+            <ows:IndividualName>GeoWebCache User</ows:IndividualName>
+        </ows:ServiceContact>
+    </ows:ServiceProvider>
+    <ows:OperationsMetadata>
+        <ows:Operation name="GetCapabilities">
+            <ows:DCP>
+                <ows:HTTP>
+                    <ows:Get xlink:href="http://sdi.georchestra.org/geoserver/gwc/service/wmts?">
+                        <ows:Constraint name="GetEncoding">
+                            <ows:AllowedValues>
+                                <ows:Value>KVP</ows:Value>
+                            </ows:AllowedValues>
+                        </ows:Constraint>
+                    </ows:Get>
+                </ows:HTTP>
+            </ows:DCP>
+        </ows:Operation>
+        <ows:Operation name="GetTile">
+            <ows:DCP>
+                <ows:HTTP>
+                    <ows:Get xlink:href="http://sdi.georchestra.org/geoserver/gwc/service/wmts?">
+                        <ows:Constraint name="GetEncoding">
+                            <ows:AllowedValues>
+                                <ows:Value>KVP</ows:Value>
+                            </ows:AllowedValues>
+                        </ows:Constraint>
+                    </ows:Get>
+                </ows:HTTP>
+            </ows:DCP>
+        </ows:Operation>
+        <ows:Operation name="GetFeatureInfo">
+            <ows:DCP>
+                <ows:HTTP>
+                    <ows:Get xlink:href="http://sdi.georchestra.org/geoserver/gwc/service/wmts?">
+                        <ows:Constraint name="GetEncoding">
+                            <ows:AllowedValues>
+                                <ows:Value>KVP</ows:Value>
+                            </ows:AllowedValues>
+                        </ows:Constraint>
+                    </ows:Get>
+                </ows:HTTP>
+            </ows:DCP>
+        </ows:Operation>
+    </ows:OperationsMetadata>
+    <Contents>
+        <Layer>
+            <ows:Title>Ocean Bottom</ows:Title>
+            <ows:Abstract>Blended depth colors and relief shading of the ocean bottom derived from CleanTOPO2 data. The ocean color extends beneath land areas as a flat tint—mask it with the 10m Natural Earth vector shoreline, or a shoreline from another data source.</ows:Abstract>
+            <ows:WGS84BoundingBox>
+                <ows:LowerCorner>-179.99999999999986 -60.002328474493446</ows:LowerCorner>
+                <ows:UpperCorner>179.98782010582556 60.000000000002935</ows:UpperCorner>
+            </ows:WGS84BoundingBox>
+            <ows:Identifier>dem:oceanbottom</ows:Identifier>
+            <Style isDefault="true">
+                <ows:Identifier/>
+            </Style>
+            <Format>image/jpeg</Format>
+            <InfoFormat>text/plain</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
+            <InfoFormat>text/html</InfoFormat>
+            <InfoFormat>application/json</InfoFormat>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:4326</TileMatrixSet>
+                <TileMatrixSetLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:0</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>0</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:1</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>1</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>3</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:2</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>3</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>7</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:3</TileMatrix>
+                        <MinTileRow>1</MinTileRow>
+                        <MaxTileRow>6</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>15</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:4</TileMatrix>
+                        <MinTileRow>2</MinTileRow>
+                        <MaxTileRow>13</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>31</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:5</TileMatrix>
+                        <MinTileRow>5</MinTileRow>
+                        <MaxTileRow>26</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>63</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:6</TileMatrix>
+                        <MinTileRow>10</MinTileRow>
+                        <MaxTileRow>53</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>127</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:7</TileMatrix>
+                        <MinTileRow>21</MinTileRow>
+                        <MaxTileRow>106</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>255</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:8</TileMatrix>
+                        <MinTileRow>42</MinTileRow>
+                        <MaxTileRow>213</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>511</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:9</TileMatrix>
+                        <MinTileRow>85</MinTileRow>
+                        <MaxTileRow>426</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1023</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:10</TileMatrix>
+                        <MinTileRow>170</MinTileRow>
+                        <MaxTileRow>853</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2047</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:11</TileMatrix>
+                        <MinTileRow>341</MinTileRow>
+                        <MaxTileRow>1706</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4095</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:12</TileMatrix>
+                        <MinTileRow>682</MinTileRow>
+                        <MaxTileRow>3413</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>8191</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:13</TileMatrix>
+                        <MinTileRow>1365</MinTileRow>
+                        <MaxTileRow>6826</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>16383</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:14</TileMatrix>
+                        <MinTileRow>2730</MinTileRow>
+                        <MaxTileRow>13653</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>32766</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:15</TileMatrix>
+                        <MinTileRow>5461</MinTileRow>
+                        <MaxTileRow>27307</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>65533</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:16</TileMatrix>
+                        <MinTileRow>10922</MinTileRow>
+                        <MaxTileRow>54614</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>131067</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:17</TileMatrix>
+                        <MinTileRow>21845</MinTileRow>
+                        <MaxTileRow>109228</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>262135</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:18</TileMatrix>
+                        <MinTileRow>43690</MinTileRow>
+                        <MaxTileRow>218456</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>524270</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:19</TileMatrix>
+                        <MinTileRow>87381</MinTileRow>
+                        <MaxTileRow>436913</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1048540</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:20</TileMatrix>
+                        <MinTileRow>174762</MinTileRow>
+                        <MaxTileRow>873826</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2097081</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:21</TileMatrix>
+                        <MinTileRow>349525</MinTileRow>
+                        <MaxTileRow>1747653</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4194162</MaxTileCol>
+                    </TileMatrixLimits>
+                </TileMatrixSetLimits>
+            </TileMatrixSetLink>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:3857</TileMatrixSet>
+                <TileMatrixSetLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:0</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>0</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>0</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:1</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>1</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:2</TileMatrix>
+                        <MinTileRow>1</MinTileRow>
+                        <MaxTileRow>2</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>3</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:3</TileMatrix>
+                        <MinTileRow>2</MinTileRow>
+                        <MaxTileRow>5</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>7</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:4</TileMatrix>
+                        <MinTileRow>4</MinTileRow>
+                        <MaxTileRow>11</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>15</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:5</TileMatrix>
+                        <MinTileRow>9</MinTileRow>
+                        <MaxTileRow>22</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>31</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:6</TileMatrix>
+                        <MinTileRow>18</MinTileRow>
+                        <MaxTileRow>45</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>63</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:7</TileMatrix>
+                        <MinTileRow>37</MinTileRow>
+                        <MaxTileRow>90</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>127</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:8</TileMatrix>
+                        <MinTileRow>74</MinTileRow>
+                        <MaxTileRow>181</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>255</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:9</TileMatrix>
+                        <MinTileRow>148</MinTileRow>
+                        <MaxTileRow>363</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>511</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:10</TileMatrix>
+                        <MinTileRow>297</MinTileRow>
+                        <MaxTileRow>726</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1023</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:11</TileMatrix>
+                        <MinTileRow>594</MinTileRow>
+                        <MaxTileRow>1453</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2047</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:12</TileMatrix>
+                        <MinTileRow>1189</MinTileRow>
+                        <MaxTileRow>2906</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4095</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:13</TileMatrix>
+                        <MinTileRow>2379</MinTileRow>
+                        <MaxTileRow>5814</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>8191</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:14</TileMatrix>
+                        <MinTileRow>4758</MinTileRow>
+                        <MaxTileRow>11627</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>16383</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:15</TileMatrix>
+                        <MinTileRow>9516</MinTileRow>
+                        <MaxTileRow>23253</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>32766</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:16</TileMatrix>
+                        <MinTileRow>19032</MinTileRow>
+                        <MaxTileRow>46506</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>65533</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:17</TileMatrix>
+                        <MinTileRow>38064</MinTileRow>
+                        <MaxTileRow>93011</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>131067</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:18</TileMatrix>
+                        <MinTileRow>76127</MinTileRow>
+                        <MaxTileRow>186021</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>262135</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:19</TileMatrix>
+                        <MinTileRow>152254</MinTileRow>
+                        <MaxTileRow>372043</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>524271</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:20</TileMatrix>
+                        <MinTileRow>304507</MinTileRow>
+                        <MaxTileRow>744085</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1048542</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:21</TileMatrix>
+                        <MinTileRow>609014</MinTileRow>
+                        <MaxTileRow>1488170</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2097085</MaxTileCol>
+                    </TileMatrixLimits>
+                </TileMatrixSetLimits>
+            </TileMatrixSetLink>
+        </Layer>
+        <Layer>
+            <ows:Title>Altitude : shaded relief</ows:Title>
+            <ows:Abstract>Shaded relief from CGIAR DEM 3" resolution. Process mixes gdaldem hillshade and slope.</ows:Abstract>
+            <ows:WGS84BoundingBox>
+                <ows:LowerCorner>-180.0 -90.0</ows:LowerCorner>
+                <ows:UpperCorner>180.0 90.0</ows:UpperCorner>
+            </ows:WGS84BoundingBox>
+            <ows:Identifier>dem:hillshading</ows:Identifier>
+            <Style isDefault="true">
+                <ows:Identifier/>
+            </Style>
+            <Format>image/jpeg</Format>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:4326</TileMatrixSet>
+            </TileMatrixSetLink>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:3857</TileMatrixSet>
+                <TileMatrixSetLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:0</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>0</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>0</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:1</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>1</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:2</TileMatrix>
+                        <MinTileRow>1</MinTileRow>
+                        <MaxTileRow>2</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>3</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:3</TileMatrix>
+                        <MinTileRow>2</MinTileRow>
+                        <MaxTileRow>5</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>7</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:4</TileMatrix>
+                        <MinTileRow>4</MinTileRow>
+                        <MaxTileRow>11</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>15</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:5</TileMatrix>
+                        <MinTileRow>9</MinTileRow>
+                        <MaxTileRow>22</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>31</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:6</TileMatrix>
+                        <MinTileRow>18</MinTileRow>
+                        <MaxTileRow>44</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>63</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:7</TileMatrix>
+                        <MinTileRow>37</MinTileRow>
+                        <MaxTileRow>88</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>127</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:8</TileMatrix>
+                        <MinTileRow>74</MinTileRow>
+                        <MaxTileRow>176</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>255</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:9</TileMatrix>
+                        <MinTileRow>148</MinTileRow>
+                        <MaxTileRow>352</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>511</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:10</TileMatrix>
+                        <MinTileRow>297</MinTileRow>
+                        <MaxTileRow>705</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1023</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:11</TileMatrix>
+                        <MinTileRow>594</MinTileRow>
+                        <MaxTileRow>1410</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2047</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:12</TileMatrix>
+                        <MinTileRow>1189</MinTileRow>
+                        <MaxTileRow>2821</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4095</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:13</TileMatrix>
+                        <MinTileRow>2379</MinTileRow>
+                        <MaxTileRow>5644</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>8191</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:14</TileMatrix>
+                        <MinTileRow>4758</MinTileRow>
+                        <MaxTileRow>11287</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>16383</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:15</TileMatrix>
+                        <MinTileRow>9516</MinTileRow>
+                        <MaxTileRow>22573</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>32767</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:16</TileMatrix>
+                        <MinTileRow>19032</MinTileRow>
+                        <MaxTileRow>45146</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>65535</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:17</TileMatrix>
+                        <MinTileRow>38064</MinTileRow>
+                        <MaxTileRow>90292</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>131071</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:18</TileMatrix>
+                        <MinTileRow>76127</MinTileRow>
+                        <MaxTileRow>180584</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>262143</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:19</TileMatrix>
+                        <MinTileRow>152254</MinTileRow>
+                        <MaxTileRow>361168</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>524287</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:20</TileMatrix>
+                        <MinTileRow>304507</MinTileRow>
+                        <MaxTileRow>722336</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1048575</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:21</TileMatrix>
+                        <MinTileRow>609014</MinTileRow>
+                        <MaxTileRow>1444672</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2097151</MaxTileCol>
+                    </TileMatrixLimits>
+                </TileMatrixSetLimits>
+            </TileMatrixSetLink>
+        </Layer>
+        <Layer>
+            <ows:Title>Altitude : color and shaded relief</ows:Title>
+            <ows:Abstract>Altitude map derived from a 3" DEM (90m on equator) covering latitudes from -90S to +60N. Jarvis A., H.I. Reuter, A.  Nelson, E. Guevara, 2008, Hole-filled  seamless SRTM data V4, International  Centre for Tropical  Agriculture (CIAT), available  from http://srtm.csi.cgiar.org.</ows:Abstract>
+            <ows:WGS84BoundingBox>
+                <ows:LowerCorner>-180.0 -60.0</ows:LowerCorner>
+                <ows:UpperCorner>180.0 60.0</ows:UpperCorner>
+            </ows:WGS84BoundingBox>
+            <ows:Identifier>dem:altitude</ows:Identifier>
+            <Style isDefault="true">
+                <ows:Identifier/>
+            </Style>
+            <Format>image/jpeg</Format>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:4326</TileMatrixSet>
+                <TileMatrixSetLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:0</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>0</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:1</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>1</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>3</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:2</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>3</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>7</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:3</TileMatrix>
+                        <MinTileRow>1</MinTileRow>
+                        <MaxTileRow>6</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>15</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:4</TileMatrix>
+                        <MinTileRow>2</MinTileRow>
+                        <MaxTileRow>13</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>31</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:5</TileMatrix>
+                        <MinTileRow>5</MinTileRow>
+                        <MaxTileRow>26</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>63</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:6</TileMatrix>
+                        <MinTileRow>10</MinTileRow>
+                        <MaxTileRow>53</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>127</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:7</TileMatrix>
+                        <MinTileRow>21</MinTileRow>
+                        <MaxTileRow>106</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>255</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:8</TileMatrix>
+                        <MinTileRow>42</MinTileRow>
+                        <MaxTileRow>213</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>511</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:9</TileMatrix>
+                        <MinTileRow>85</MinTileRow>
+                        <MaxTileRow>426</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1023</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:10</TileMatrix>
+                        <MinTileRow>170</MinTileRow>
+                        <MaxTileRow>853</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2047</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:11</TileMatrix>
+                        <MinTileRow>341</MinTileRow>
+                        <MaxTileRow>1706</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4095</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:12</TileMatrix>
+                        <MinTileRow>682</MinTileRow>
+                        <MaxTileRow>3413</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>8191</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:13</TileMatrix>
+                        <MinTileRow>1365</MinTileRow>
+                        <MaxTileRow>6826</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>16383</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:14</TileMatrix>
+                        <MinTileRow>2730</MinTileRow>
+                        <MaxTileRow>13653</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>32767</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:15</TileMatrix>
+                        <MinTileRow>5461</MinTileRow>
+                        <MaxTileRow>27306</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>65535</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:16</TileMatrix>
+                        <MinTileRow>10922</MinTileRow>
+                        <MaxTileRow>54613</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>131071</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:17</TileMatrix>
+                        <MinTileRow>21845</MinTileRow>
+                        <MaxTileRow>109226</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>262143</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:18</TileMatrix>
+                        <MinTileRow>43690</MinTileRow>
+                        <MaxTileRow>218453</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>524287</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:19</TileMatrix>
+                        <MinTileRow>87381</MinTileRow>
+                        <MaxTileRow>436906</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1048575</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:20</TileMatrix>
+                        <MinTileRow>174762</MinTileRow>
+                        <MaxTileRow>873813</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2097151</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:4326:21</TileMatrix>
+                        <MinTileRow>349525</MinTileRow>
+                        <MaxTileRow>1747626</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4194303</MaxTileCol>
+                    </TileMatrixLimits>
+                </TileMatrixSetLimits>
+            </TileMatrixSetLink>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:3857</TileMatrixSet>
+                <TileMatrixSetLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:0</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>0</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>0</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:1</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>1</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:2</TileMatrix>
+                        <MinTileRow>1</MinTileRow>
+                        <MaxTileRow>2</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>3</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:3</TileMatrix>
+                        <MinTileRow>2</MinTileRow>
+                        <MaxTileRow>5</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>7</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:4</TileMatrix>
+                        <MinTileRow>4</MinTileRow>
+                        <MaxTileRow>11</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>15</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:5</TileMatrix>
+                        <MinTileRow>9</MinTileRow>
+                        <MaxTileRow>22</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>31</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:6</TileMatrix>
+                        <MinTileRow>18</MinTileRow>
+                        <MaxTileRow>45</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>63</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:7</TileMatrix>
+                        <MinTileRow>37</MinTileRow>
+                        <MaxTileRow>90</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>127</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:8</TileMatrix>
+                        <MinTileRow>74</MinTileRow>
+                        <MaxTileRow>181</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>255</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:9</TileMatrix>
+                        <MinTileRow>148</MinTileRow>
+                        <MaxTileRow>363</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>511</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:10</TileMatrix>
+                        <MinTileRow>297</MinTileRow>
+                        <MaxTileRow>726</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1023</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:11</TileMatrix>
+                        <MinTileRow>594</MinTileRow>
+                        <MaxTileRow>1453</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2047</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:12</TileMatrix>
+                        <MinTileRow>1189</MinTileRow>
+                        <MaxTileRow>2906</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4095</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:13</TileMatrix>
+                        <MinTileRow>2379</MinTileRow>
+                        <MaxTileRow>5814</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>8191</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:14</TileMatrix>
+                        <MinTileRow>4758</MinTileRow>
+                        <MaxTileRow>11627</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>16383</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:15</TileMatrix>
+                        <MinTileRow>9516</MinTileRow>
+                        <MaxTileRow>23253</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>32767</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:16</TileMatrix>
+                        <MinTileRow>19032</MinTileRow>
+                        <MaxTileRow>46505</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>65535</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:17</TileMatrix>
+                        <MinTileRow>38064</MinTileRow>
+                        <MaxTileRow>93009</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>131071</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:18</TileMatrix>
+                        <MinTileRow>76127</MinTileRow>
+                        <MaxTileRow>186018</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>262143</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:19</TileMatrix>
+                        <MinTileRow>152254</MinTileRow>
+                        <MaxTileRow>372036</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>524287</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:20</TileMatrix>
+                        <MinTileRow>304507</MinTileRow>
+                        <MaxTileRow>744072</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1048575</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:21</TileMatrix>
+                        <MinTileRow>609014</MinTileRow>
+                        <MaxTileRow>1488143</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2097151</MaxTileCol>
+                    </TileMatrixLimits>
+                </TileMatrixSetLimits>
+            </TileMatrixSetLink>
+        </Layer>
+        <Layer>
+            <ows:Title>Earth at night 2012</ows:Title>
+            <ows:Abstract>This new global view and animation of Earth's city lights is a composite assembled from data acquired by the Suomi NPP satellite. The data was acquired over nine days in April 2012 and 13 days in October 2012. It took 312 orbits to get a clear shot of every parcel of Earth's land surface and islands. This new data was then mapped over existing Blue Marble imagery of Earth to provide a realistic view of the planet.</ows:Abstract>
+            <ows:WGS84BoundingBox>
+                <ows:LowerCorner>-180.0 -90.0</ows:LowerCorner>
+                <ows:UpperCorner>180.0 90.0</ows:UpperCorner>
+            </ows:WGS84BoundingBox>
+            <ows:Identifier>nasa:night_2012</ows:Identifier>
+            <Style isDefault="true">
+                <ows:Identifier/>
+            </Style>
+            <Format>image/png8</Format>
+            <InfoFormat>text/plain</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
+            <InfoFormat>text/html</InfoFormat>
+            <InfoFormat>application/json</InfoFormat>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:4326</TileMatrixSet>
+            </TileMatrixSetLink>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:3857</TileMatrixSet>
+            </TileMatrixSetLink>
+        </Layer>
+        <Layer>
+            <ows:Title>True Marble</ows:Title>
+            <ows:Abstract>A 250m res. global image composited from LandSat7 (1999-2002). True Marble by Unearthed Outdoors, LLC is licensed under a Creative Commons Attribution 3.0 United States License.</ows:Abstract>
+            <ows:WGS84BoundingBox>
+                <ows:LowerCorner>-180.0 -90.0</ows:LowerCorner>
+                <ows:UpperCorner>180.0 90.0</ows:UpperCorner>
+            </ows:WGS84BoundingBox>
+            <ows:Identifier>unearthedoutdoors:truemarble</ows:Identifier>
+            <Style isDefault="true">
+                <ows:Identifier/>
+            </Style>
+            <Format>image/jpeg</Format>
+            <InfoFormat>text/plain</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
+            <InfoFormat>text/html</InfoFormat>
+            <InfoFormat>application/json</InfoFormat>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:4326</TileMatrixSet>
+            </TileMatrixSetLink>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:3857</TileMatrixSet>
+            </TileMatrixSetLink>
+        </Layer>
+        <Layer>
+            <ows:Title>Altitude : digital elevation model</ows:Title>
+            <ows:Abstract>DEM 3" resolution (90m on equator) covering latitudes from -90S to +60N. Jarvis A., H.I. Reuter, A.  Nelson, E. Guevara, 2008, Hole-filled  seamless SRTM data V4, International  Centre for Tropical  Agriculture (CIAT), available  from http://srtm.csi.cgiar.org.</ows:Abstract>
+            <ows:WGS84BoundingBox>
+                <ows:LowerCorner>-180.0 -90.0</ows:LowerCorner>
+                <ows:UpperCorner>180.0 90.0</ows:UpperCorner>
+            </ows:WGS84BoundingBox>
+            <ows:Identifier>dem:srtm</ows:Identifier>
+            <Style>
+                <ows:Identifier>altitude-2012themovie</ows:Identifier>
+            </Style>
+            <Style>
+                <ows:Identifier>altitude-contour</ows:Identifier>
+            </Style>
+            <Style>
+                <ows:Identifier>altitude-warmhumid</ows:Identifier>
+            </Style>
+            <Format>image/jpeg</Format>
+            <InfoFormat>text/plain</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml</InfoFormat>
+            <InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
+            <InfoFormat>text/html</InfoFormat>
+            <InfoFormat>application/json</InfoFormat>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:4326</TileMatrixSet>
+            </TileMatrixSetLink>
+            <TileMatrixSetLink>
+                <TileMatrixSet>EPSG:3857</TileMatrixSet>
+                <TileMatrixSetLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:0</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>0</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>0</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:1</TileMatrix>
+                        <MinTileRow>0</MinTileRow>
+                        <MaxTileRow>1</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:2</TileMatrix>
+                        <MinTileRow>1</MinTileRow>
+                        <MaxTileRow>2</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>3</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:3</TileMatrix>
+                        <MinTileRow>2</MinTileRow>
+                        <MaxTileRow>5</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>7</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:4</TileMatrix>
+                        <MinTileRow>4</MinTileRow>
+                        <MaxTileRow>11</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>15</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:5</TileMatrix>
+                        <MinTileRow>9</MinTileRow>
+                        <MaxTileRow>22</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>31</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:6</TileMatrix>
+                        <MinTileRow>18</MinTileRow>
+                        <MaxTileRow>45</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>63</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:7</TileMatrix>
+                        <MinTileRow>37</MinTileRow>
+                        <MaxTileRow>90</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>127</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:8</TileMatrix>
+                        <MinTileRow>74</MinTileRow>
+                        <MaxTileRow>181</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>255</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:9</TileMatrix>
+                        <MinTileRow>148</MinTileRow>
+                        <MaxTileRow>363</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>511</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:10</TileMatrix>
+                        <MinTileRow>297</MinTileRow>
+                        <MaxTileRow>726</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1023</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:11</TileMatrix>
+                        <MinTileRow>594</MinTileRow>
+                        <MaxTileRow>1453</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2047</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:12</TileMatrix>
+                        <MinTileRow>1189</MinTileRow>
+                        <MaxTileRow>2906</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>4095</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:13</TileMatrix>
+                        <MinTileRow>2379</MinTileRow>
+                        <MaxTileRow>5814</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>8191</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:14</TileMatrix>
+                        <MinTileRow>4758</MinTileRow>
+                        <MaxTileRow>11627</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>16383</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:15</TileMatrix>
+                        <MinTileRow>9516</MinTileRow>
+                        <MaxTileRow>23253</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>32767</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:16</TileMatrix>
+                        <MinTileRow>19032</MinTileRow>
+                        <MaxTileRow>46505</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>65535</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:17</TileMatrix>
+                        <MinTileRow>38064</MinTileRow>
+                        <MaxTileRow>93009</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>131071</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:18</TileMatrix>
+                        <MinTileRow>76127</MinTileRow>
+                        <MaxTileRow>186018</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>262143</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:19</TileMatrix>
+                        <MinTileRow>152254</MinTileRow>
+                        <MaxTileRow>372036</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>524287</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:20</TileMatrix>
+                        <MinTileRow>304507</MinTileRow>
+                        <MaxTileRow>744072</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>1048575</MaxTileCol>
+                    </TileMatrixLimits>
+                    <TileMatrixLimits>
+                        <TileMatrix>EPSG:3857:21</TileMatrix>
+                        <MinTileRow>609014</MinTileRow>
+                        <MaxTileRow>1488143</MaxTileRow>
+                        <MinTileCol>0</MinTileCol>
+                        <MaxTileCol>2097151</MaxTileCol>
+                    </TileMatrixLimits>
+                </TileMatrixSetLimits>
+            </TileMatrixSetLink>
+        </Layer>
+        <TileMatrixSet>
+            <ows:Identifier>GlobalCRS84Scale</ows:Identifier>
+            <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:0</ows:Identifier>
+                <ScaleDenominator>5.0E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:1</ows:Identifier>
+                <ScaleDenominator>2.5E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>3</MatrixWidth>
+                <MatrixHeight>2</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:2</ows:Identifier>
+                <ScaleDenominator>1.0E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>6</MatrixWidth>
+                <MatrixHeight>3</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:3</ows:Identifier>
+                <ScaleDenominator>5.0E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>12</MatrixWidth>
+                <MatrixHeight>6</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:4</ows:Identifier>
+                <ScaleDenominator>2.5E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>23</MatrixWidth>
+                <MatrixHeight>12</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:5</ows:Identifier>
+                <ScaleDenominator>1.0E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>56</MatrixWidth>
+                <MatrixHeight>28</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:6</ows:Identifier>
+                <ScaleDenominator>5000000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>112</MatrixWidth>
+                <MatrixHeight>56</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:7</ows:Identifier>
+                <ScaleDenominator>2500000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>224</MatrixWidth>
+                <MatrixHeight>112</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:8</ows:Identifier>
+                <ScaleDenominator>1000000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>560</MatrixWidth>
+                <MatrixHeight>280</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:9</ows:Identifier>
+                <ScaleDenominator>500000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1119</MatrixWidth>
+                <MatrixHeight>560</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:10</ows:Identifier>
+                <ScaleDenominator>250000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2237</MatrixWidth>
+                <MatrixHeight>1119</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:11</ows:Identifier>
+                <ScaleDenominator>100000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>5591</MatrixWidth>
+                <MatrixHeight>2796</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:12</ows:Identifier>
+                <ScaleDenominator>50000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>11182</MatrixWidth>
+                <MatrixHeight>5591</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:13</ows:Identifier>
+                <ScaleDenominator>25000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>22364</MatrixWidth>
+                <MatrixHeight>11182</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:14</ows:Identifier>
+                <ScaleDenominator>10000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>55909</MatrixWidth>
+                <MatrixHeight>27955</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:15</ows:Identifier>
+                <ScaleDenominator>5000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>111817</MatrixWidth>
+                <MatrixHeight>55909</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:16</ows:Identifier>
+                <ScaleDenominator>2500.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>223633</MatrixWidth>
+                <MatrixHeight>111817</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:17</ows:Identifier>
+                <ScaleDenominator>1000.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>559083</MatrixWidth>
+                <MatrixHeight>279542</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:18</ows:Identifier>
+                <ScaleDenominator>500.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1118165</MatrixWidth>
+                <MatrixHeight>559083</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:19</ows:Identifier>
+                <ScaleDenominator>250.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2236330</MatrixWidth>
+                <MatrixHeight>1118165</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Scale:20</ows:Identifier>
+                <ScaleDenominator>100.0</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>5590823</MatrixWidth>
+                <MatrixHeight>2795412</MatrixHeight>
+            </TileMatrix>
+        </TileMatrixSet>
+        <TileMatrixSet>
+            <ows:Identifier>EPSG:4326</ows:Identifier>
+            <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:0</ows:Identifier>
+                <ScaleDenominator>2.795411320143589E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:1</ows:Identifier>
+                <ScaleDenominator>1.3977056600717944E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4</MatrixWidth>
+                <MatrixHeight>2</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:2</ows:Identifier>
+                <ScaleDenominator>6.988528300358972E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8</MatrixWidth>
+                <MatrixHeight>4</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:3</ows:Identifier>
+                <ScaleDenominator>3.494264150179486E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16</MatrixWidth>
+                <MatrixHeight>8</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:4</ows:Identifier>
+                <ScaleDenominator>1.747132075089743E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32</MatrixWidth>
+                <MatrixHeight>16</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:5</ows:Identifier>
+                <ScaleDenominator>8735660.375448715</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>64</MatrixWidth>
+                <MatrixHeight>32</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:6</ows:Identifier>
+                <ScaleDenominator>4367830.1877243575</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>128</MatrixWidth>
+                <MatrixHeight>64</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:7</ows:Identifier>
+                <ScaleDenominator>2183915.0938621787</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>256</MatrixWidth>
+                <MatrixHeight>128</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:8</ows:Identifier>
+                <ScaleDenominator>1091957.5469310894</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>512</MatrixWidth>
+                <MatrixHeight>256</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:9</ows:Identifier>
+                <ScaleDenominator>545978.7734655447</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1024</MatrixWidth>
+                <MatrixHeight>512</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:10</ows:Identifier>
+                <ScaleDenominator>272989.38673277234</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2048</MatrixWidth>
+                <MatrixHeight>1024</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:11</ows:Identifier>
+                <ScaleDenominator>136494.69336638617</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4096</MatrixWidth>
+                <MatrixHeight>2048</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:12</ows:Identifier>
+                <ScaleDenominator>68247.34668319309</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8192</MatrixWidth>
+                <MatrixHeight>4096</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:13</ows:Identifier>
+                <ScaleDenominator>34123.67334159654</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16384</MatrixWidth>
+                <MatrixHeight>8192</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:14</ows:Identifier>
+                <ScaleDenominator>17061.83667079827</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32768</MatrixWidth>
+                <MatrixHeight>16384</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:15</ows:Identifier>
+                <ScaleDenominator>8530.918335399136</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>65536</MatrixWidth>
+                <MatrixHeight>32768</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:16</ows:Identifier>
+                <ScaleDenominator>4265.459167699568</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>131072</MatrixWidth>
+                <MatrixHeight>65536</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:17</ows:Identifier>
+                <ScaleDenominator>2132.729583849784</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>262144</MatrixWidth>
+                <MatrixHeight>131072</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:18</ows:Identifier>
+                <ScaleDenominator>1066.364791924892</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>524288</MatrixWidth>
+                <MatrixHeight>262144</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:19</ows:Identifier>
+                <ScaleDenominator>533.182395962446</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1048576</MatrixWidth>
+                <MatrixHeight>524288</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:20</ows:Identifier>
+                <ScaleDenominator>266.591197981223</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2097152</MatrixWidth>
+                <MatrixHeight>1048576</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:4326:21</ows:Identifier>
+                <ScaleDenominator>133.2955989906115</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4194304</MatrixWidth>
+                <MatrixHeight>2097152</MatrixHeight>
+            </TileMatrix>
+        </TileMatrixSet>
+        <TileMatrixSet>
+            <ows:Identifier>GoogleCRS84Quad</ows:Identifier>
+            <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:0</ows:Identifier>
+                <ScaleDenominator>5.590822640287178E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:1</ows:Identifier>
+                <ScaleDenominator>2.795411320143589E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:2</ows:Identifier>
+                <ScaleDenominator>1.397705660071794E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4</MatrixWidth>
+                <MatrixHeight>2</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:3</ows:Identifier>
+                <ScaleDenominator>6.988528300358972E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8</MatrixWidth>
+                <MatrixHeight>4</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:4</ows:Identifier>
+                <ScaleDenominator>3.494264150179486E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16</MatrixWidth>
+                <MatrixHeight>8</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:5</ows:Identifier>
+                <ScaleDenominator>1.747132075089743E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32</MatrixWidth>
+                <MatrixHeight>16</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:6</ows:Identifier>
+                <ScaleDenominator>8735660.375448715</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>64</MatrixWidth>
+                <MatrixHeight>32</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:7</ows:Identifier>
+                <ScaleDenominator>4367830.187724357</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>128</MatrixWidth>
+                <MatrixHeight>64</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:8</ows:Identifier>
+                <ScaleDenominator>2183915.093862179</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>256</MatrixWidth>
+                <MatrixHeight>128</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:9</ows:Identifier>
+                <ScaleDenominator>1091957.546931089</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>512</MatrixWidth>
+                <MatrixHeight>256</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:10</ows:Identifier>
+                <ScaleDenominator>545978.7734655447</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1024</MatrixWidth>
+                <MatrixHeight>512</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:11</ows:Identifier>
+                <ScaleDenominator>272989.3867327723</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2048</MatrixWidth>
+                <MatrixHeight>1024</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:12</ows:Identifier>
+                <ScaleDenominator>136494.6933663862</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4096</MatrixWidth>
+                <MatrixHeight>2048</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:13</ows:Identifier>
+                <ScaleDenominator>68247.34668319309</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8192</MatrixWidth>
+                <MatrixHeight>4096</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:14</ows:Identifier>
+                <ScaleDenominator>34123.67334159654</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16384</MatrixWidth>
+                <MatrixHeight>8192</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:15</ows:Identifier>
+                <ScaleDenominator>17061.83667079827</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32768</MatrixWidth>
+                <MatrixHeight>16384</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:16</ows:Identifier>
+                <ScaleDenominator>8530.918335399136</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>65536</MatrixWidth>
+                <MatrixHeight>32768</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:17</ows:Identifier>
+                <ScaleDenominator>4265.459167699568</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>131072</MatrixWidth>
+                <MatrixHeight>65536</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GoogleCRS84Quad:18</ows:Identifier>
+                <ScaleDenominator>2132.729583849784</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>262144</MatrixWidth>
+                <MatrixHeight>131072</MatrixHeight>
+            </TileMatrix>
+        </TileMatrixSet>
+        <TileMatrixSet>
+            <ows:Identifier>EPSG:900913</ows:Identifier>
+            <ows:SupportedCRS>urn:ogc:def:crs:EPSG::900913</ows:SupportedCRS>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:0</ows:Identifier>
+                <ScaleDenominator>5.590822639508929E8</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:1</ows:Identifier>
+                <ScaleDenominator>2.7954113197544646E8</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2</MatrixWidth>
+                <MatrixHeight>2</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:2</ows:Identifier>
+                <ScaleDenominator>1.3977056598772323E8</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4</MatrixWidth>
+                <MatrixHeight>4</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:3</ows:Identifier>
+                <ScaleDenominator>6.988528299386162E7</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8</MatrixWidth>
+                <MatrixHeight>8</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:4</ows:Identifier>
+                <ScaleDenominator>3.494264149693081E7</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16</MatrixWidth>
+                <MatrixHeight>16</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:5</ows:Identifier>
+                <ScaleDenominator>1.7471320748465404E7</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32</MatrixWidth>
+                <MatrixHeight>32</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:6</ows:Identifier>
+                <ScaleDenominator>8735660.374232702</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>64</MatrixWidth>
+                <MatrixHeight>64</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:7</ows:Identifier>
+                <ScaleDenominator>4367830.187116351</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>128</MatrixWidth>
+                <MatrixHeight>128</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:8</ows:Identifier>
+                <ScaleDenominator>2183915.0935581755</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>256</MatrixWidth>
+                <MatrixHeight>256</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:9</ows:Identifier>
+                <ScaleDenominator>1091957.5467790877</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>512</MatrixWidth>
+                <MatrixHeight>512</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:10</ows:Identifier>
+                <ScaleDenominator>545978.7733895439</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1024</MatrixWidth>
+                <MatrixHeight>1024</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:11</ows:Identifier>
+                <ScaleDenominator>272989.38669477194</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2048</MatrixWidth>
+                <MatrixHeight>2048</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:12</ows:Identifier>
+                <ScaleDenominator>136494.69334738597</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4096</MatrixWidth>
+                <MatrixHeight>4096</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:13</ows:Identifier>
+                <ScaleDenominator>68247.34667369298</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8192</MatrixWidth>
+                <MatrixHeight>8192</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:14</ows:Identifier>
+                <ScaleDenominator>34123.67333684649</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16384</MatrixWidth>
+                <MatrixHeight>16384</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:15</ows:Identifier>
+                <ScaleDenominator>17061.836668423246</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32768</MatrixWidth>
+                <MatrixHeight>32768</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:16</ows:Identifier>
+                <ScaleDenominator>8530.918334211623</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>65536</MatrixWidth>
+                <MatrixHeight>65536</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:17</ows:Identifier>
+                <ScaleDenominator>4265.4591671058115</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>131072</MatrixWidth>
+                <MatrixHeight>131072</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:18</ows:Identifier>
+                <ScaleDenominator>2132.7295835529058</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>262144</MatrixWidth>
+                <MatrixHeight>262144</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:19</ows:Identifier>
+                <ScaleDenominator>1066.3647917764529</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>524288</MatrixWidth>
+                <MatrixHeight>524288</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:20</ows:Identifier>
+                <ScaleDenominator>533.1823958882264</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1048576</MatrixWidth>
+                <MatrixHeight>1048576</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:21</ows:Identifier>
+                <ScaleDenominator>266.5911979441132</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2097152</MatrixWidth>
+                <MatrixHeight>2097152</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:22</ows:Identifier>
+                <ScaleDenominator>133.2955989720566</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4194304</MatrixWidth>
+                <MatrixHeight>4194304</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:23</ows:Identifier>
+                <ScaleDenominator>66.6477994860283</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8388608</MatrixWidth>
+                <MatrixHeight>8388608</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:24</ows:Identifier>
+                <ScaleDenominator>33.32389974301415</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16777216</MatrixWidth>
+                <MatrixHeight>16777216</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:25</ows:Identifier>
+                <ScaleDenominator>16.661949871507076</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>33554432</MatrixWidth>
+                <MatrixHeight>33554432</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:26</ows:Identifier>
+                <ScaleDenominator>8.330974935753538</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>67108864</MatrixWidth>
+                <MatrixHeight>67108864</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:27</ows:Identifier>
+                <ScaleDenominator>4.165487467876769</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>134217728</MatrixWidth>
+                <MatrixHeight>134217728</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:28</ows:Identifier>
+                <ScaleDenominator>2.0827437339383845</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>268435456</MatrixWidth>
+                <MatrixHeight>268435456</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:29</ows:Identifier>
+                <ScaleDenominator>1.0413718669691923</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>536870912</MatrixWidth>
+                <MatrixHeight>536870912</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:900913:30</ows:Identifier>
+                <ScaleDenominator>0.5206859334845961</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1073741824</MatrixWidth>
+                <MatrixHeight>1073741824</MatrixHeight>
+            </TileMatrix>
+        </TileMatrixSet>
+        <TileMatrixSet>
+            <ows:Identifier>EPSG:3857</ows:Identifier>
+            <ows:SupportedCRS>urn:ogc:def:crs:EPSG::3857</ows:SupportedCRS>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:0</ows:Identifier>
+                <ScaleDenominator>5.590811457640435E8</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:1</ows:Identifier>
+                <ScaleDenominator>2.795405728820217E8</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2</MatrixWidth>
+                <MatrixHeight>2</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:2</ows:Identifier>
+                <ScaleDenominator>1.3977028644101086E8</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4</MatrixWidth>
+                <MatrixHeight>4</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:3</ows:Identifier>
+                <ScaleDenominator>6.988514322050543E7</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8</MatrixWidth>
+                <MatrixHeight>8</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:4</ows:Identifier>
+                <ScaleDenominator>3.4942571610252716E7</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16</MatrixWidth>
+                <MatrixHeight>16</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:5</ows:Identifier>
+                <ScaleDenominator>1.7471285805126358E7</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32</MatrixWidth>
+                <MatrixHeight>32</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:6</ows:Identifier>
+                <ScaleDenominator>8735642.902563179</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>64</MatrixWidth>
+                <MatrixHeight>64</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:7</ows:Identifier>
+                <ScaleDenominator>4367821.451281589</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>128</MatrixWidth>
+                <MatrixHeight>128</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:8</ows:Identifier>
+                <ScaleDenominator>2183910.7256407947</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>256</MatrixWidth>
+                <MatrixHeight>256</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:9</ows:Identifier>
+                <ScaleDenominator>1091955.3628203974</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>512</MatrixWidth>
+                <MatrixHeight>512</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:10</ows:Identifier>
+                <ScaleDenominator>545977.6814101987</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1024</MatrixWidth>
+                <MatrixHeight>1024</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:11</ows:Identifier>
+                <ScaleDenominator>272988.84070509934</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2048</MatrixWidth>
+                <MatrixHeight>2048</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:12</ows:Identifier>
+                <ScaleDenominator>136494.42035254967</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037428E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>4096</MatrixWidth>
+                <MatrixHeight>4096</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:13</ows:Identifier>
+                <ScaleDenominator>68247.21017627484</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.004232E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>8193</MatrixWidth>
+                <MatrixHeight>8193</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:14</ows:Identifier>
+                <ScaleDenominator>34123.60508813742</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0039874E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16385</MatrixWidth>
+                <MatrixHeight>16385</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:15</ows:Identifier>
+                <ScaleDenominator>17061.80254406871</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0038651E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>32769</MatrixWidth>
+                <MatrixHeight>32769</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:16</ows:Identifier>
+                <ScaleDenominator>8530.901272034354</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.003804E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>65537</MatrixWidth>
+                <MatrixHeight>65537</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:17</ows:Identifier>
+                <ScaleDenominator>4265.450636017177</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037734E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>131073</MatrixWidth>
+                <MatrixHeight>131073</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:18</ows:Identifier>
+                <ScaleDenominator>2132.7253180085886</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037581E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>262145</MatrixWidth>
+                <MatrixHeight>262145</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:19</ows:Identifier>
+                <ScaleDenominator>1066.3626590042943</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037581E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>524290</MatrixWidth>
+                <MatrixHeight>524290</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:20</ows:Identifier>
+                <ScaleDenominator>533.1813295021472</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037543E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1048579</MatrixWidth>
+                <MatrixHeight>1048579</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>EPSG:3857:21</ows:Identifier>
+                <ScaleDenominator>266.5906647510736</ScaleDenominator>
+                <TopLeftCorner>-2.003750834E7 2.0037524E7</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2097157</MatrixWidth>
+                <MatrixHeight>2097157</MatrixHeight>
+            </TileMatrix>
+        </TileMatrixSet>
+        <TileMatrixSet>
+            <ows:Identifier>GlobalCRS84Pixel</ows:Identifier>
+            <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:0</ows:Identifier>
+                <ScaleDenominator>7.951392199519542E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:1</ows:Identifier>
+                <ScaleDenominator>3.975696099759771E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>2</MatrixWidth>
+                <MatrixHeight>1</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:2</ows:Identifier>
+                <ScaleDenominator>1.9878480498798856E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>3</MatrixWidth>
+                <MatrixHeight>2</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:3</ows:Identifier>
+                <ScaleDenominator>1.325232033253257E8</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>5</MatrixWidth>
+                <MatrixHeight>3</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:4</ows:Identifier>
+                <ScaleDenominator>6.626160166266285E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>9</MatrixWidth>
+                <MatrixHeight>5</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:5</ows:Identifier>
+                <ScaleDenominator>3.3130800831331424E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>17</MatrixWidth>
+                <MatrixHeight>9</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:6</ows:Identifier>
+                <ScaleDenominator>1.325232033253257E7</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>43</MatrixWidth>
+                <MatrixHeight>22</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:7</ows:Identifier>
+                <ScaleDenominator>6626160.166266285</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>85</MatrixWidth>
+                <MatrixHeight>43</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:8</ows:Identifier>
+                <ScaleDenominator>3313080.0831331424</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>169</MatrixWidth>
+                <MatrixHeight>85</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:9</ows:Identifier>
+                <ScaleDenominator>1656540.0415665712</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>338</MatrixWidth>
+                <MatrixHeight>169</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:10</ows:Identifier>
+                <ScaleDenominator>552180.0138555238</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1013</MatrixWidth>
+                <MatrixHeight>507</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:11</ows:Identifier>
+                <ScaleDenominator>331308.00831331423</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>1688</MatrixWidth>
+                <MatrixHeight>844</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:12</ows:Identifier>
+                <ScaleDenominator>110436.00277110476</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>5063</MatrixWidth>
+                <MatrixHeight>2532</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:13</ows:Identifier>
+                <ScaleDenominator>55218.00138555238</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>10125</MatrixWidth>
+                <MatrixHeight>5063</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:14</ows:Identifier>
+                <ScaleDenominator>33130.80083133143</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>16875</MatrixWidth>
+                <MatrixHeight>8438</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:15</ows:Identifier>
+                <ScaleDenominator>11043.600277110474</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>50625</MatrixWidth>
+                <MatrixHeight>25313</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:16</ows:Identifier>
+                <ScaleDenominator>3313.080083133142</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>168750</MatrixWidth>
+                <MatrixHeight>84375</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>GlobalCRS84Pixel:17</ows:Identifier>
+                <ScaleDenominator>1104.3600277110472</ScaleDenominator>
+                <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>506250</MatrixWidth>
+                <MatrixHeight>253125</MatrixHeight>
+            </TileMatrix>
+        </TileMatrixSet>
+    </Contents>
+    <ServiceMetadataURL xlink:href="http://sdi.georchestra.org/geoserver/gwc/service/wmts?REQUEST=getcapabilities&VERSION=1.0.0"/>
 </Capabilities>
\ No newline at end of file
diff --git a/examples/drag-and-drop-image-vector.js b/examples/drag-and-drop-image-vector.js
index 90a6f54..6e35336 100644
--- a/examples/drag-and-drop-image-vector.js
+++ b/examples/drag-and-drop-image-vector.js
@@ -145,7 +145,10 @@ var displayFeatureInfo = function(pixel) {
   }
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var pixel = map.getEventPixel(evt.originalEvent);
   displayFeatureInfo(pixel);
 });
diff --git a/examples/drag-and-drop.js b/examples/drag-and-drop.js
index c481a7f..830b6bc 100644
--- a/examples/drag-and-drop.js
+++ b/examples/drag-and-drop.js
@@ -141,7 +141,10 @@ var displayFeatureInfo = function(pixel) {
   }
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var pixel = map.getEventPixel(evt.originalEvent);
   displayFeatureInfo(pixel);
 });
diff --git a/examples/getfeatureinfo-image.html b/examples/getfeatureinfo-image.html
index 85b3044..bde7a85 100644
--- a/examples/getfeatureinfo-image.html
+++ b/examples/getfeatureinfo-image.html
@@ -35,9 +35,11 @@
           <h4 id="title">GetFeatureInfo example (image layer)</h4>
           <p id="shortdesc">This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS image layer.</p>
           <div id="docs">
+            <p>Additionally <code>map.forEachLayerAtPixel</code> is used to change the mouse
+            pointer when hovering a non-transparent pixel on the map.</p>
             <p>See the <a href="getfeatureinfo-image.js" target="_blank">getfeatureinfo-image.js source</a> to see how this is done.</p>
           </div>
-          <div id="tags">getfeatureinfo</div>
+          <div id="tags">getfeatureinfo, forEachLayerAtPixel</div>
         </div>
         <div class="span4 offset4">
           <div id="info" class="alert alert-success">
diff --git a/examples/getfeatureinfo-image.js b/examples/getfeatureinfo-image.js
index 1806cae..316fda1 100644
--- a/examples/getfeatureinfo-image.js
+++ b/examples/getfeatureinfo-image.js
@@ -7,7 +7,8 @@ goog.require('ol.source.ImageWMS');
 var wmsSource = new ol.source.ImageWMS({
   url: 'http://demo.boundlessgeo.com/geoserver/wms',
   params: {'LAYERS': 'ne:ne'},
-  serverType: 'geoserver'
+  serverType: 'geoserver',
+  crossOrigin: ''
 });
 
 var wmsLayer = new ol.layer.Image({
@@ -20,6 +21,7 @@ var view = new ol.View({
 });
 
 var map = new ol.Map({
+  renderer: exampleNS.getRendererFromQueryString(),
   layers: [wmsLayer],
   target: 'map',
   view: view
@@ -36,3 +38,14 @@ map.on('singleclick', function(evt) {
         '<iframe seamless src="' + url + '"></iframe>';
   }
 });
+
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
+  var pixel = map.getEventPixel(evt.originalEvent);
+  var hit = map.forEachLayerAtPixel(pixel, function(layer) {
+    return true;
+  });
+  map.getTargetElement().style.cursor = hit ? 'pointer' : '';
+});
diff --git a/examples/getfeatureinfo-tile.html b/examples/getfeatureinfo-tile.html
index 46a5289..b59b920 100644
--- a/examples/getfeatureinfo-tile.html
+++ b/examples/getfeatureinfo-tile.html
@@ -35,9 +35,11 @@
           <h4 id="title">WMS GetFeatureInfo example (tile layer)</h4>
           <p id="shortdesc">This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS tile layer.</p>
           <div id="docs">
+            <p>Additionally <code>map.forEachLayerAtPixel</code> is used to change the mouse
+            pointer when hovering a non-transparent pixel on the map.</p>
             <p>See the <a href="getfeatureinfo-tile.js" target="_blank">getfeatureinfo-tile.js source</a> to see how this is done.</p>
           </div>
-          <div id="tags">getfeatureinfo</div>
+          <div id="tags">getfeatureinfo, forEachLayerAtPixel</div>
         </div>
         <div class="span4 offset4">
           <div id="info" class="alert alert-success">
diff --git a/examples/getfeatureinfo-tile.js b/examples/getfeatureinfo-tile.js
index 70704a7..022606b 100644
--- a/examples/getfeatureinfo-tile.js
+++ b/examples/getfeatureinfo-tile.js
@@ -7,7 +7,8 @@ goog.require('ol.source.TileWMS');
 var wmsSource = new ol.source.TileWMS({
   url: 'http://demo.boundlessgeo.com/geoserver/wms',
   params: {'LAYERS': 'ne:ne'},
-  serverType: 'geoserver'
+  serverType: 'geoserver',
+  crossOrigin: ''
 });
 
 var wmsLayer = new ol.layer.Tile({
@@ -20,6 +21,7 @@ var view = new ol.View({
 });
 
 var map = new ol.Map({
+  renderer: exampleNS.getRendererFromQueryString(),
   layers: [wmsLayer],
   target: 'map',
   view: view
@@ -36,3 +38,14 @@ map.on('singleclick', function(evt) {
         '<iframe seamless src="' + url + '"></iframe>';
   }
 });
+
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
+  var pixel = map.getEventPixel(evt.originalEvent);
+  var hit = map.forEachLayerAtPixel(pixel, function(layer) {
+    return true;
+  });
+  map.getTargetElement().style.cursor = hit ? 'pointer' : '';
+});
diff --git a/examples/gpx.js b/examples/gpx.js
index 13024a6..d98dceb 100644
--- a/examples/gpx.js
+++ b/examples/gpx.js
@@ -85,7 +85,10 @@ var displayFeatureInfo = function(pixel) {
   }
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var pixel = map.getEventPixel(evt.originalEvent);
   displayFeatureInfo(pixel);
 });
diff --git a/examples/icon-sprite-webgl.html b/examples/icon-sprite-webgl.html
index 7f07409..1d50a55 100644
--- a/examples/icon-sprite-webgl.html
+++ b/examples/icon-sprite-webgl.html
@@ -30,7 +30,7 @@
 
       <div class="row-fluid">
 
-        <div class="span12">
+        <div class="span8">
           <h4 id="title">Icon sprite with WebGL example</h4>
           <p id="shortdesc">Icon sprite with WebGL.</p>
           <div id="docs">
@@ -39,6 +39,11 @@
           </div>
           <div id="tags">webgl, icon, sprite, vector, point</div>
         </div>
+        <div class="span2 offset2">
+          <div id="info" class="alert alert-success">
+             
+          </div>
+        </div>
 
       </div>
 
diff --git a/examples/icon-sprite-webgl.js b/examples/icon-sprite-webgl.js
index 26933f3..8487020 100644
--- a/examples/icon-sprite-webgl.js
+++ b/examples/icon-sprite-webgl.js
@@ -109,3 +109,34 @@ var featureOverlay = new ol.FeatureOverlay({
   }),
   features: overlayFeatures
 });
+
+map.on('click', function(evt) {
+  var info = document.getElementById('info');
+  info.innerHTML =
+      'Hold on a second, while I catch those butterflies for you ...';
+
+  window.setTimeout(function() {
+    var features = [];
+    map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
+      features.push(features);
+      return false;
+    });
+
+    if (features.length === 1) {
+      info.innerHTML = 'Got one butterfly';
+    } else if (features.length > 1) {
+      info.innerHTML = 'Got ' + features.length + ' butterflies';
+    } else {
+      info.innerHTML = 'Couldn\'t catch a single butterfly';
+    }
+  }, 1);
+});
+
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
+  var pixel = map.getEventPixel(evt.originalEvent);
+  var hit = map.hasFeatureAtPixel(pixel);
+  map.getTarget().style.cursor = hit ? 'pointer' : '';
+});
diff --git a/examples/icon.js b/examples/icon.js
index d510cf1..f435b0f 100644
--- a/examples/icon.js
+++ b/examples/icon.js
@@ -40,11 +40,13 @@ var vectorLayer = new ol.layer.Vector({
 
 var rasterLayer = new ol.layer.Tile({
   source: new ol.source.TileJSON({
-    url: 'http://api.tiles.mapbox.com/v3/mapbox.geography-class.jsonp'
+    url: 'http://api.tiles.mapbox.com/v3/mapbox.geography-class.jsonp',
+    crossOrigin: ''
   })
 });
 
 var map = new ol.Map({
+  renderer: exampleNS.getRendererFromQueryString(),
   layers: [rasterLayer, vectorLayer],
   target: document.getElementById('map'),
   view: new ol.View({
@@ -84,14 +86,12 @@ map.on('click', function(evt) {
 });
 
 // change mouse cursor when over marker
-$(map.getViewport()).on('mousemove', function(e) {
-  var pixel = map.getEventPixel(e.originalEvent);
-  var hit = map.forEachFeatureAtPixel(pixel, function(feature, layer) {
-    return true;
-  });
-  if (hit) {
-    map.getTarget().style.cursor = 'pointer';
-  } else {
-    map.getTarget().style.cursor = '';
+map.on('pointermove', function(e) {
+  if (e.dragging) {
+    $(element).popover('destroy');
+    return;
   }
+  var pixel = map.getEventPixel(e.originalEvent);
+  var hit = map.hasFeatureAtPixel(pixel);
+  map.getTarget().style.cursor = hit ? 'pointer' : '';
 });
diff --git a/examples/igc.js b/examples/igc.js
index afb3e00..95b380d 100644
--- a/examples/igc.js
+++ b/examples/igc.js
@@ -126,7 +126,10 @@ var displaySnap = function(coordinate) {
   map.render();
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var coordinate = map.getEventCoordinate(evt.originalEvent);
   displaySnap(coordinate);
 });
diff --git a/examples/image-vector-layer.js b/examples/image-vector-layer.js
index 01e79fe..6cf67e6 100644
--- a/examples/image-vector-layer.js
+++ b/examples/image-vector-layer.js
@@ -80,7 +80,10 @@ var displayFeatureInfo = function(pixel) {
 
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var pixel = map.getEventPixel(evt.originalEvent);
   displayFeatureInfo(pixel);
 });
diff --git a/examples/kml-earthquakes.js b/examples/kml-earthquakes.js
index 793ebf4..10ae729 100644
--- a/examples/kml-earthquakes.js
+++ b/examples/kml-earthquakes.js
@@ -85,7 +85,11 @@ var displayFeatureInfo = function(pixel) {
   }
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    info.tooltip('hide');
+    return;
+  }
   displayFeatureInfo(map.getEventPixel(evt.originalEvent));
 });
 
diff --git a/examples/kml-timezones.js b/examples/kml-timezones.js
index f7b5244..8b8038e 100644
--- a/examples/kml-timezones.js
+++ b/examples/kml-timezones.js
@@ -92,7 +92,11 @@ var displayFeatureInfo = function(pixel) {
   }
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    info.tooltip('hide');
+    return;
+  }
   displayFeatureInfo(map.getEventPixel(evt.originalEvent));
 });
 
diff --git a/examples/kml.js b/examples/kml.js
index ac86d71..3323628 100644
--- a/examples/kml.js
+++ b/examples/kml.js
@@ -52,7 +52,10 @@ var displayFeatureInfo = function(pixel) {
   }
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var pixel = map.getEventPixel(evt.originalEvent);
   displayFeatureInfo(pixel);
 });
diff --git a/examples/measure.html b/examples/measure.html
index e13c2d9..2a1773f 100644
--- a/examples/measure.html
+++ b/examples/measure.html
@@ -9,6 +9,40 @@
     <link rel="stylesheet" href="../resources/layout.css" type="text/css">
     <link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
     <title>Measure example</title>
+    <style>
+      .tooltip {
+        position: relative;
+        background: rgba(0, 0, 0, 0.5);
+        border-radius: 4px;
+        color: white;
+        padding: 4px 8px;
+        opacity: 0.7;
+        white-space: nowrap;
+      }
+      .tooltip-measure {
+        opacity: 1;
+        font-weight: bold;
+      }
+      .tooltip-static {
+        background-color: #ffcc33;
+        color: black;
+        border: 1px solid white;
+      }
+      .tooltip-measure:before,
+      .tooltip-static:before {
+        border-top: 6px solid rgba(0, 0, 0, 0.5);
+        border-right: 6px solid transparent;
+        border-left: 6px solid transparent;
+        content: "";
+        position: absolute;
+        bottom: -6px;
+        margin-left: -7px;
+        left: 50%;
+      }
+      .tooltip-static:before {
+        border-top-color: #ffcc33;
+      }
+    </style>
   </head>
   <body>
 
@@ -43,8 +77,6 @@
               </select>
           </form>
 
-          <ol id="measureOutput" reversed></ol>
-
           <div id="docs">
               <p><i>NOTE: Measure is done in simple way on projected plane. Earth
               curvature is not taken into account</i></p>
diff --git a/examples/measure.js b/examples/measure.js
index 6f6c9f2..127eeaf 100644
--- a/examples/measure.js
+++ b/examples/measure.js
@@ -1,4 +1,5 @@
 goog.require('ol.Map');
+goog.require('ol.Overlay');
 goog.require('ol.View');
 goog.require('ol.geom.LineString');
 goog.require('ol.geom.Polygon');
@@ -40,35 +41,85 @@ var vector = new ol.layer.Vector({
 
 
 /**
- * Currently drawed feature
+ * Currently drawn feature.
  * @type {ol.Feature}
  */
 var sketch;
 
 
 /**
- * Element for currently drawed feature
+ * The help tooltip element.
  * @type {Element}
  */
-var sketchElement;
+var helpTooltipElement;
 
 
 /**
- * handle pointer move
- * @param {Event} evt
+ * Overlay to show the help messages.
+ * @type {ol.Overlay}
  */
-var mouseMoveHandler = function(evt) {
+var helpTooltip;
+
+
+/**
+ * The measure tooltip element.
+ * @type {Element}
+ */
+var measureTooltipElement;
+
+
+/**
+ * Overlay to show the measurement.
+ * @type {ol.Overlay}
+ */
+var measureTooltip;
+
+
+/**
+ * Message to show when the user is drawing a polygon.
+ * @type {string}
+ */
+var continuePolygonMsg = 'Click to continue drawing the polygon';
+
+
+/**
+ * Message to show when the user is drawing a line.
+ * @type {string}
+ */
+var continueLineMsg = 'Click to continue drawing the line';
+
+
+/**
+ * Handle pointer move.
+ * @param {ol.MapBrowserEvent} evt
+ */
+var pointerMoveHandler = function(evt) {
+  if (evt.dragging) {
+    return;
+  }
+  /** @type {string} */
+  var helpMsg = 'Click to start drawing';
+  /** @type {ol.Coordinate|undefined} */
+  var tooltipCoord = evt.coordinate;
+
   if (sketch) {
     var output;
     var geom = (sketch.getGeometry());
     if (geom instanceof ol.geom.Polygon) {
       output = formatArea(/** @type {ol.geom.Polygon} */ (geom));
-
+      helpMsg = continuePolygonMsg;
+      tooltipCoord = geom.getInteriorPoint().getCoordinates();
     } else if (geom instanceof ol.geom.LineString) {
       output = formatLength( /** @type {ol.geom.LineString} */ (geom));
+      helpMsg = continueLineMsg;
+      tooltipCoord = geom.getLastCoordinate();
     }
-    sketchElement.innerHTML = output;
+    measureTooltipElement.innerHTML = output;
+    measureTooltip.setPosition(tooltipCoord);
   }
+
+  helpTooltipElement.innerHTML = helpMsg;
+  helpTooltip.setPosition(evt.coordinate);
 };
 
 
@@ -81,7 +132,7 @@ var map = new ol.Map({
   })
 });
 
-$(map.getViewport()).on('mousemove', mouseMoveHandler);
+map.on('pointermove', pointerMoveHandler);
 
 var typeSelect = document.getElementById('type');
 
@@ -90,34 +141,88 @@ function addInteraction() {
   var type = (typeSelect.value == 'area' ? 'Polygon' : 'LineString');
   draw = new ol.interaction.Draw({
     source: source,
-    type: /** @type {ol.geom.GeometryType} */ (type)
+    type: /** @type {ol.geom.GeometryType} */ (type),
+    style: new ol.style.Style({
+      fill: new ol.style.Fill({
+        color: 'rgba(255, 255, 255, 0.2)'
+      }),
+      stroke: new ol.style.Stroke({
+        color: 'rgba(0, 0, 0, 0.5)',
+        lineDash: [10, 10],
+        width: 2
+      }),
+      image: new ol.style.Circle({
+        radius: 5,
+        stroke: new ol.style.Stroke({
+          color: 'rgba(0, 0, 0, 0.7)'
+        }),
+        fill: new ol.style.Fill({
+          color: 'rgba(255, 255, 255, 0.2)'
+        })
+      })
+    })
   });
   map.addInteraction(draw);
 
+  createMeasureTooltip();
+  createHelpTooltip();
+
   draw.on('drawstart',
       function(evt) {
         // set sketch
         sketch = evt.feature;
-        sketchElement = document.createElement('li');
-        var outputList = document.getElementById('measureOutput');
-
-        if (outputList.childNodes) {
-          outputList.insertBefore(sketchElement, outputList.firstChild);
-        } else {
-          outputList.appendChild(sketchElement);
-        }
       }, this);
 
   draw.on('drawend',
       function(evt) {
+        measureTooltipElement.className = 'tooltip tooltip-static';
+        measureTooltip.setOffset([0, -7]);
         // unset sketch
         sketch = null;
-        sketchElement = null;
+        // unset tooltip so that a new one can be created
+        measureTooltipElement = null;
+        createMeasureTooltip();
       }, this);
 }
 
 
 /**
+ * Creates a new help tooltip
+ */
+function createHelpTooltip() {
+  if (helpTooltipElement) {
+    helpTooltipElement.parentNode.removeChild(helpTooltipElement);
+  }
+  helpTooltipElement = document.createElement('div');
+  helpTooltipElement.className = 'tooltip';
+  helpTooltip = new ol.Overlay({
+    element: helpTooltipElement,
+    offset: [15, 0],
+    positioning: 'center-left'
+  });
+  map.addOverlay(helpTooltip);
+}
+
+
+/**
+ * Creates a new measure tooltip
+ */
+function createMeasureTooltip() {
+  if (measureTooltipElement) {
+    measureTooltipElement.parentNode.removeChild(measureTooltipElement);
+  }
+  measureTooltipElement = document.createElement('div');
+  measureTooltipElement.className = 'tooltip tooltip-measure';
+  measureTooltip = new ol.Overlay({
+    element: measureTooltipElement,
+    offset: [0, -15],
+    positioning: 'bottom-center'
+  });
+  map.addOverlay(measureTooltip);
+}
+
+
+/**
  * Let user change the geometry type.
  * @param {Event} e Change event.
  */
diff --git a/examples/getfeatureinfo-image.html b/examples/polygon-styles.html
similarity index 66%
copy from examples/getfeatureinfo-image.html
copy to examples/polygon-styles.html
index 85b3044..5464d2f 100644
--- a/examples/getfeatureinfo-image.html
+++ b/examples/polygon-styles.html
@@ -9,7 +9,12 @@
     <link rel="stylesheet" href="../resources/bootstrap/css/bootstrap.min.css" type="text/css">
     <link rel="stylesheet" href="../resources/layout.css" type="text/css">
     <link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
-    <title>GetFeatureInfo example (image layer)</title>
+    <title>Custom styles for polygons</title>
+    <style>
+      .map {
+        background: grey;
+      }
+    </style>
   </head>
   <body>
 
@@ -31,18 +36,13 @@
 
       <div class="row-fluid">
 
-        <div class="span4">
-          <h4 id="title">GetFeatureInfo example (image layer)</h4>
-          <p id="shortdesc">This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS image layer.</p>
+        <div class="span12">
+          <h4 id="title">Custom styles for polygons</h4>
+          <p id="shortdesc">Showing the vertices of a polygon with a custom style geometry.</p>
           <div id="docs">
-            <p>See the <a href="getfeatureinfo-image.js" target="_blank">getfeatureinfo-image.js source</a> to see how this is done.</p>
-          </div>
-          <div id="tags">getfeatureinfo</div>
-        </div>
-        <div class="span4 offset4">
-          <div id="info" class="alert alert-success">
-             
+            <p>See the <a href="polygon-styles.js" target="_blank">polygon-styles.js source</a> to see how this is done.</p>
           </div>
+          <div id="tags">polygon, vector, style, GeometryFunction</div>
         </div>
 
       </div>
@@ -50,8 +50,8 @@
     </div>
 
     <script src="../resources/jquery.min.js" type="text/javascript"></script>
+    <script src="loader.js?id=polygon-styles" type="text/javascript"></script>
     <script src="../resources/example-behaviour.js" type="text/javascript"></script>
-    <script src="loader.js?id=getfeatureinfo-image" type="text/javascript"></script>
 
   </body>
 </html>
diff --git a/examples/polygon-styles.js b/examples/polygon-styles.js
new file mode 100644
index 0000000..70d18b1
--- /dev/null
+++ b/examples/polygon-styles.js
@@ -0,0 +1,101 @@
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.GeoJSON');
+goog.require('ol.style.Circle');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Style');
+
+var styles = [
+  /* We are using two different styles for the polygons:
+   *  - The first style is for the polygons themselves.
+   *  - The second style is to draw the vertices of the polygons.
+   *    In a custom `geometry` function the vertices of a polygon are
+   *    returned as `MultiPoint` geometry, which will be used to render
+   *    the style.
+   */
+  new ol.style.Style({
+    stroke: new ol.style.Stroke({
+      color: 'blue',
+      width: 3
+    }),
+    fill: new ol.style.Fill({
+      color: 'rgba(0, 0, 255, 0.1)'
+    })
+  }),
+  new ol.style.Style({
+    image: new ol.style.Circle({
+      radius: 5,
+      fill: new ol.style.Fill({
+        color: 'orange'
+      })
+    }),
+    geometry: function(feature) {
+      // return the coordinates of the first ring of the polygon
+      var coordinates = feature.getGeometry().getCoordinates()[0];
+      return new ol.geom.MultiPoint(coordinates);
+    }
+  })
+];
+
+var source = new ol.source.GeoJSON(/** @type {olx.source.GeoJSONOptions} */ ({
+  object: {
+    'type': 'FeatureCollection',
+    'crs': {
+      'type': 'name',
+      'properties': {
+        'name': 'EPSG:3857'
+      }
+    },
+    'features': [
+      {
+        'type': 'Feature',
+        'geometry': {
+          'type': 'Polygon',
+          'coordinates': [[[-5e6, 6e6], [-5e6, 8e6], [-3e6, 8e6],
+              [-3e6, 6e6], [-5e6, 6e6]]]
+        }
+      },
+      {
+        'type': 'Feature',
+        'geometry': {
+          'type': 'Polygon',
+          'coordinates': [[[-2e6, 6e6], [-2e6, 8e6], [0, 8e6],
+              [0, 6e6], [-2e6, 6e6]]]
+        }
+      },
+      {
+        'type': 'Feature',
+        'geometry': {
+          'type': 'Polygon',
+          'coordinates': [[[1e6, 6e6], [1e6, 8e6], [3e6, 8e6],
+              [3e6, 6e6], [1e6, 6e6]]]
+        }
+      },
+      {
+        'type': 'Feature',
+        'geometry': {
+          'type': 'Polygon',
+          'coordinates': [[[-2e6, -1e6], [-1e6, 1e6],
+              [0, -1e6], [-2e6, -1e6]]]
+        }
+      }
+    ]
+  }
+}));
+
+var layer = new ol.layer.Vector({
+  source: source,
+  style: styles
+});
+
+var map = new ol.Map({
+  layers: [layer],
+  target: 'map',
+  view: new ol.View({
+    center: [0, 1000000],
+    zoom: 2
+  })
+});
diff --git a/examples/popup.html b/examples/popup.html
index 9cdda57..272fa75 100644
--- a/examples/popup.html
+++ b/examples/popup.html
@@ -10,10 +10,8 @@
     <link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
     <style type="text/css">
       .ol-popup {
-        display: none;
         position: absolute;
         background-color: white;
-        -moz-box-shadow: 0 1px 4px rgba(0,0,0,0.2);
         -webkit-filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
         filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
         padding: 15px;
diff --git a/examples/popup.js b/examples/popup.js
index 3cdb5ac..f6c051d 100644
--- a/examples/popup.js
+++ b/examples/popup.js
@@ -20,7 +20,7 @@ var closer = document.getElementById('popup-closer');
  * @return {boolean} Don't follow the href.
  */
 closer.onclick = function() {
-  container.style.display = 'none';
+  overlay.setPosition(undefined);
   closer.blur();
   return false;
 };
@@ -65,9 +65,7 @@ map.on('click', function(evt) {
   var hdms = ol.coordinate.toStringHDMS(ol.proj.transform(
       coordinate, 'EPSG:3857', 'EPSG:4326'));
 
-  overlay.setPosition(coordinate);
   content.innerHTML = '<p>You clicked here:</p><code>' + hdms +
       '</code>';
-  container.style.display = 'block';
-
+  overlay.setPosition(coordinate);
 });
diff --git a/examples/synthetic-points.js b/examples/synthetic-points.js
index 4381fd3..606eb7c 100644
--- a/examples/synthetic-points.js
+++ b/examples/synthetic-points.js
@@ -83,7 +83,10 @@ var displaySnap = function(coordinate) {
   map.render();
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var coordinate = map.getEventCoordinate(evt.originalEvent);
   displaySnap(coordinate);
 });
@@ -116,13 +119,12 @@ map.on('postcompose', function(evt) {
   }
 });
 
-$(map.getViewport()).on('mousemove', function(e) {
-  var pixel = map.getEventPixel(e.originalEvent);
-
-  var hit = map.forEachFeatureAtPixel(pixel, function(feature, layer) {
-    return true;
-  });
-
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
+  var pixel = map.getEventPixel(evt.originalEvent);
+  var hit = map.hasFeatureAtPixel(pixel);
   if (hit) {
     map.getTarget().style.cursor = 'pointer';
   } else {
diff --git a/examples/tileutfgrid.js b/examples/tileutfgrid.js
index b6462e3..89a2675 100644
--- a/examples/tileutfgrid.js
+++ b/examples/tileutfgrid.js
@@ -58,7 +58,10 @@ var displayCountryInfo = function(coordinate) {
       });
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var coordinate = map.getEventCoordinate(evt.originalEvent);
   displayCountryInfo(coordinate);
 });
diff --git a/examples/vector-layer.js b/examples/vector-layer.js
index 25f70d6..390ffe8 100644
--- a/examples/vector-layer.js
+++ b/examples/vector-layer.js
@@ -114,7 +114,10 @@ var displayFeatureInfo = function(pixel) {
 
 };
 
-$(map.getViewport()).on('mousemove', function(evt) {
+map.on('pointermove', function(evt) {
+  if (evt.dragging) {
+    return;
+  }
   var pixel = map.getEventPixel(evt.originalEvent);
   displayFeatureInfo(pixel);
 });
diff --git a/examples/icon-sprite-webgl.html b/examples/wmts-capabilities.html
similarity index 60%
copy from examples/icon-sprite-webgl.html
copy to examples/wmts-capabilities.html
index 7f07409..1a37d77 100644
--- a/examples/icon-sprite-webgl.html
+++ b/examples/wmts-capabilities.html
@@ -8,7 +8,7 @@
     <link rel="stylesheet" href="../resources/bootstrap/css/bootstrap.min.css" type="text/css">
     <link rel="stylesheet" href="../resources/layout.css" type="text/css">
     <link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
-    <title>Icon sprites with WebGL example</title>
+    <title>WMTS GetCapabilities parsing example</title>
   </head>
   <body>
 
@@ -23,21 +23,18 @@
     <div class="container-fluid">
 
       <div class="row-fluid">
-        <div class="span12">
-          <div id="map" class="map"></div>
-        </div>
-      </div>
-
-      <div class="row-fluid">
 
-        <div class="span12">
-          <h4 id="title">Icon sprite with WebGL example</h4>
-          <p id="shortdesc">Icon sprite with WebGL.</p>
+        <div class="span4">
+          <h4 id="title">WMTS GetCapabilities parsing example</h4>
+          <p id="shortdesc">Example of parsing a WMTS GetCapabilities response.</p>
           <div id="docs">
-            <p>See the <a href="icon-sprite-webgl.js" target="_blank">icon-sprite-webgl.js source</a> to see how this is done.</p>
-            <p>In this example a sprite image is used for the icon styles. Using a sprite is required to get good performance with WebGL.</p>
+            <p>See the <a href="wmts-capabilities.js" target="_blank">wmts-capabilities.js source</a> to see how this is done.</p>
           </div>
-          <div id="tags">webgl, icon, sprite, vector, point</div>
+          <div id="tags">wmts, capabilities, getcapabilities</div>
+        </div>
+
+        <div class="span8">
+          <pre id="log"></pre>
         </div>
 
       </div>
@@ -45,9 +42,8 @@
     </div>
 
     <script src="../resources/jquery.min.js" type="text/javascript"></script>
-    <script src="../resources/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
     <script src="../resources/example-behaviour.js" type="text/javascript"></script>
-    <script src="loader.js?id=icon-sprite-webgl" type="text/javascript"></script>
+    <script src="loader.js?id=wmts-capabilities" type="text/javascript"></script>
 
   </body>
 </html>
diff --git a/examples/wmts-capabilities.js b/examples/wmts-capabilities.js
new file mode 100644
index 0000000..61f8820
--- /dev/null
+++ b/examples/wmts-capabilities.js
@@ -0,0 +1,8 @@
+goog.require('ol.format.WMTSCapabilities');
+
+var parser = new ol.format.WMTSCapabilities();
+
+$.ajax('data/WMTSCapabilities.xml').then(function(response) {
+  var result = parser.read(response);
+  $('#log').html(window.JSON.stringify(result, null, 2));
+});
diff --git a/externs/geojson.js b/externs/geojson.js
index 4d65845..0b7c2c7 100644
--- a/externs/geojson.js
+++ b/externs/geojson.js
@@ -14,6 +14,12 @@ var GeoJSONObject = function() {};
 
 
 /**
+ * @type {!Array.<number>|undefined}
+ */
+GeoJSONObject.prototype.bbox;
+
+
+/**
  * @type {string}
  */
 GeoJSONObject.prototype.type;
@@ -28,12 +34,18 @@ GeoJSONObject.prototype.crs;
 
 /**
  * @constructor
- * @extends {GeoJSONObject}
  */
 var GeoJSONCRS = function() {};
 
 
 /**
+ * CRS type. One of `link` or `name`.
+ * @type {string}
+ */
+GeoJSONCRS.prototype.type;
+
+
+/**
  * TODO: remove GeoJSONCRSCode when http://jira.codehaus.org/browse/GEOS-5996
  * is fixed and widely deployed.
  * @type {!GeoJSONCRSCode|!GeoJSONCRSName|!GeoJSONLink}
@@ -142,16 +154,9 @@ var GeoJSONFeatureCollection = function() {};
 GeoJSONFeatureCollection.prototype.features;
 
 
-/**
- * @type {!Array.<number>|undefined}
- */
-GeoJSONFeatureCollection.prototype.bbox;
-
-
 
 /**
  * @constructor
- * @extends {GeoJSONObject}
  */
 var GeoJSONLink = function() {};
 
@@ -160,3 +165,8 @@ var GeoJSONLink = function() {};
  * @type {string}
  */
 GeoJSONLink.prototype.href;
+
+/**
+ * @type {string}
+ */
+GeoJSONLink.prototype.type;
diff --git a/externs/oli.js b/externs/oli.js
index 903fd6e..9ffb887 100644
--- a/externs/oli.js
+++ b/externs/oli.js
@@ -92,6 +92,12 @@ oli.MapBrowserEvent.prototype.originalEvent;
 oli.MapBrowserEvent.prototype.pixel;
 
 
+/**
+ * @type {boolean}
+ */
+oli.MapBrowserEvent.prototype.dragging;
+
+
 
 /**
  * @interface
diff --git a/externs/olx.js b/externs/olx.js
index c1c2a45..c00a799 100644
--- a/externs/olx.js
+++ b/externs/olx.js
@@ -24,32 +24,6 @@ olx.AttributionOptions.prototype.html;
 
 
 /**
- * @typedef {{loadTilesWhileAnimating: (boolean|undefined),
- *     loadTilesWhileInteracting: (boolean|undefined)}}
- * @api
- */
-olx.DeviceOptions;
-
-
-/**
- * When set to false, no tiles will be loaded while animating, which improves
- * responsiveness on devices with slow memory. Default is `true`.
- * @type {boolean|undefined}
- * @api
- */
-olx.DeviceOptions.prototype.loadTilesWhileAnimating;
-
-
-/**
- * When set to false, no tiles will be loaded while interacting, which improves
- * responsiveness on devices with slow memory. Default is `true`.
- * @type {boolean|undefined}
- * @api
- */
-olx.DeviceOptions.prototype.loadTilesWhileInteracting;
-
-
-/**
  * @typedef {{tracking: (boolean|undefined)}}
  * @api
  */
@@ -194,11 +168,12 @@ olx.interaction.InteractionOptions.prototype.handleEvent;
 /**
  * Object literal with config options for the map.
  * @typedef {{controls: (ol.Collection.<ol.control.Control>|Array.<ol.control.Control>|undefined),
- *     deviceOptions: (olx.DeviceOptions|undefined),
  *     pixelRatio: (number|undefined),
  *     interactions: (ol.Collection.<ol.interaction.Interaction>|Array.<ol.interaction.Interaction>|undefined),
  *     keyboardEventTarget: (Element|Document|string|undefined),
  *     layers: (Array.<ol.layer.Base>|ol.Collection.<ol.layer.Base>|undefined),
+ *     loadTilesWhileAnimating: (boolean|undefined),
+ *     loadTilesWhileInteracting: (boolean|undefined),
  *     logo: (boolean|string|olx.LogoOptions|undefined),
  *     overlays: (ol.Collection.<ol.Overlay>|Array.<ol.Overlay>|undefined),
  *     renderer: (ol.RendererType|Array.<ol.RendererType|string>|string|undefined),
@@ -219,14 +194,6 @@ olx.MapOptions.prototype.controls;
 
 
 /**
- * Device options for the map.
- * @type {olx.DeviceOptions|undefined}
- * @api
- */
-olx.MapOptions.prototype.deviceOptions;
-
-
-/**
  * The ratio between physical pixels and device-independent pixels (dips) on the
  * device. If `undefined` then it gets set by using `window.devicePixelRatio`.
  * @type {number|undefined}
@@ -267,6 +234,26 @@ olx.MapOptions.prototype.layers;
 
 
 /**
+ * When set to true, tiles will be loaded during animations. This may improve
+ * the user experience, but can also make animations stutter on devices with
+ * slow memory. Default is `false`.
+ * @type {boolean|undefined}
+ * @api
+ */
+olx.MapOptions.prototype.loadTilesWhileAnimating;
+
+
+/**
+ * When set to true, tiles will be loaded while interacting with the map. This
+ * may improve the user experience, but can also make map panning and zooming
+ * choppy on devices with slow memory. Default is `false`.
+ * @type {boolean|undefined}
+ * @api
+ */
+olx.MapOptions.prototype.loadTilesWhileInteracting;
+
+
+/**
  * The map logo. A logo to be displayed on the map at all times. If a string is
  * provided, it will be set as the image source of the logo. If an object is
  * provided, the `src` property should be the URL for an image and the `href`
@@ -819,8 +806,8 @@ olx.control;
  *     collapsible: (boolean|undefined),
  *     collapsed: (boolean|undefined),
  *     tipLabel: (string|undefined),
- *     label: (string|undefined),
- *     collapseLabel: (string|undefined),
+ *     label: (string|Node|undefined),
+ *     collapseLabel: (string|Node|undefined),
  *     render: (function(ol.MapEvent)|undefined),
  *     target: (Element|undefined)}}
  * @api
@@ -872,15 +859,17 @@ olx.control.AttributionOptions.prototype.tipLabel;
 
 
 /**
- * Text label to use for the collapsed attributions button. Default is `i`
- * @type {string|undefined}
+ * Text label to use for the collapsed attributions button. Default is `i`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
  * @api
  */
 olx.control.AttributionOptions.prototype.label;
 
 /**
- * Text label to use for the expanded attributions button. Default is `»`
- * @type {string|undefined}
+ * Text label to use for the expanded attributions button. Default is `»`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
  * @api
  */
 olx.control.AttributionOptions.prototype.collapseLabel;
@@ -993,6 +982,8 @@ olx.control.DefaultsOptions.prototype.zoomOptions;
 
 /**
  * @typedef {{className: (string|undefined),
+ *     label: (string|Node|undefined),
+ *     labelActive: (string|Node|undefined),
  *     tipLabel: (string|undefined),
  *     keys: (boolean|undefined),
  *     target: (Element|undefined)}}
@@ -1010,6 +1001,25 @@ olx.control.FullScreenOptions.prototype.className;
 
 
 /**
+ * Text label to use for the button. Default is `\u2194` (an arrow).
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
+ * @api
+ */
+olx.control.FullScreenOptions.prototype.label;
+
+
+/**
+ * Text label to use for the button when full-screen is active.
+ * Default is `\u00d7` (a cross).
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
+ * @api
+ */
+olx.control.FullScreenOptions.prototype.labelActive;
+
+
+/**
  * Text label to use for the button tip. Default is `Toggle full-screen`
  * @type {string|undefined}
  * @api
@@ -1096,9 +1106,9 @@ olx.control.MousePositionOptions.prototype.undefinedHTML;
 
 /**
  * @typedef {{collapsed: (boolean|undefined),
- *     collapseLabel: (string|undefined),
+ *     collapseLabel: (string|Node|undefined),
  *     collapsible: (boolean|undefined),
- *     label: (string|undefined),
+ *     label: (string|Node|undefined),
  *     layers: (Array.<ol.layer.Layer>|ol.Collection|undefined),
  *     render: (function(ol.MapEvent)|undefined),
  *     target: (Element|undefined),
@@ -1118,8 +1128,9 @@ olx.control.OverviewMapOptions.prototype.collapsed;
 
 
 /**
- * Text label to use for the expanded overviewmap button. Default is `«`
- * @type {string|undefined}
+ * Text label to use for the expanded overviewmap button. Default is `«`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
  * @api
  */
 olx.control.OverviewMapOptions.prototype.collapseLabel;
@@ -1134,8 +1145,9 @@ olx.control.OverviewMapOptions.prototype.collapsible;
 
 
 /**
- * Text label to use for the collapsed overviewmap button. Default is `»`
- * @type {string|undefined}
+ * Text label to use for the collapsed overviewmap button. Default is `»`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
  * @api
  */
 olx.control.OverviewMapOptions.prototype.label;
@@ -1231,7 +1243,7 @@ olx.control.ScaleLineOptions.prototype.units;
 /**
  * @typedef {{duration: (number|undefined),
  *     className: (string|undefined),
- *     label: (string|undefined),
+ *     label: (string|Node|undefined),
  *     tipLabel: (string|undefined),
  *     target: (Element|undefined),
  *     render: (function(ol.MapEvent)|undefined),
@@ -1250,8 +1262,9 @@ olx.control.RotateOptions.prototype.className;
 
 
 /**
- * Text label to use for the rotate button. Default is `⇧`
- * @type {string|undefined}
+ * Text label to use for the rotate button. Default is `⇧`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
  * @api stable
  */
 olx.control.RotateOptions.prototype.label;
@@ -1301,8 +1314,8 @@ olx.control.RotateOptions.prototype.target;
 /**
  * @typedef {{duration: (number|undefined),
  *     className: (string|undefined),
- *     zoomInLabel: (string|undefined),
- *     zoomOutLabel: (string|undefined),
+ *     zoomInLabel: (string|Node|undefined),
+ *     zoomOutLabel: (string|Node|undefined),
  *     zoomInTipLabel: (string|undefined),
  *     zoomOutTipLabel: (string|undefined),
  *     delta: (number|undefined),
@@ -1329,16 +1342,18 @@ olx.control.ZoomOptions.prototype.className;
 
 
 /**
- * Text label to use for the zoom-in button. Default is `+`
- * @type {string|undefined}
+ * Text label to use for the zoom-in button. Default is `+`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
  * @api stable
  */
 olx.control.ZoomOptions.prototype.zoomInLabel;
 
 
 /**
- * Text label to use for the zoom-out button. Default is `-`
- * @type {string|undefined}
+ * Text label to use for the zoom-out button. Default is `-`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
  * @api stable
  */
 olx.control.ZoomOptions.prototype.zoomOutLabel;
@@ -1422,6 +1437,7 @@ olx.control.ZoomSliderOptions.prototype.render;
 /**
  * @typedef {{className: (string|undefined),
  *     target: (Element|undefined),
+ *     label: (string|Node|undefined),
  *     tipLabel: (string|undefined),
  *     extent: (ol.Extent|undefined)}}
  * @api stable
@@ -1446,6 +1462,15 @@ olx.control.ZoomToExtentOptions.prototype.target;
 
 
 /**
+ * Text label to use for the button. Default is `E`.
+ * Instead of text, also a Node (e.g. a `span` element) can be used.
+ * @type {string|Node|undefined}
+ * @api stable
+ */
+olx.control.ZoomToExtentOptions.prototype.label;
+
+
+/**
  * Text label to use for the button tip. Default is `Zoom to extent`
  * @type {string|undefined}
  * @api stable
@@ -2494,7 +2519,8 @@ olx.interaction.PointerOptions.prototype.handleUpEvent;
  *     layers: (Array.<ol.layer.Layer>|function(ol.layer.Layer): boolean|undefined),
  *     style: (ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined),
  *     removeCondition: (ol.events.ConditionType|undefined),
- *     toggleCondition: (ol.events.ConditionType|undefined)}}
+ *     toggleCondition: (ol.events.ConditionType|undefined),
+ *     multi: (boolean|undefined)}}
  * @api
  */
 olx.interaction.SelectOptions;
@@ -2571,6 +2597,15 @@ olx.interaction.SelectOptions.prototype.removeCondition;
  */
 olx.interaction.SelectOptions.prototype.toggleCondition;
 
+/**
+ * A boolean that determines if the default behaviour should select only
+ * single features or all (overlapping) features at the clicked map
+ * position. Default is false i.e single select
+ * @type {boolean|undefined}
+ * @api
+ */
+olx.interaction.SelectOptions.prototype.multi;
+
 
 /**
  * Namespace.
@@ -3233,6 +3268,7 @@ olx.layer.TileOptions.prototype.useInterimTilesOnError;
  *     saturation: (number|undefined),
  *     source: (ol.source.Vector|undefined),
  *     style: (ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined),
+ *     updateWhileAnimating: (boolean|undefined),
  *     visible: (boolean|undefined)}}
  * @api
  */
@@ -3342,6 +3378,17 @@ olx.layer.VectorOptions.prototype.style;
 
 
 /**
+ * When set to `true`, feature batches will be recreated during animations.
+ * This means that no vectors will be shown clipped, but the setting will have a
+ * performance impact for large amounts of vector data. When set to `false`,
+ * batches will be recreated when no animation is active.  Default is `false`.
+ * @type {boolean|undefined}
+ * @api
+ */
+olx.layer.VectorOptions.prototype.updateWhileAnimating;
+
+
+/**
  * Visibility. Default is `true` (visible).
  * @type {boolean|undefined}
  * @api stable
@@ -3394,7 +3441,8 @@ olx.source;
  *     key: string,
  *     imagerySet: string,
  *     maxZoom: (number|undefined),
- *     tileLoadFunction: (ol.TileLoadFunctionType|undefined)}}
+ *     tileLoadFunction: (ol.TileLoadFunctionType|undefined),
+ *     wrapX: (boolean|undefined)}}
  * @api
  */
 olx.source.BingMapsOptions;
@@ -3440,6 +3488,15 @@ olx.source.BingMapsOptions.prototype.maxZoom;
  */
 olx.source.BingMapsOptions.prototype.tileLoadFunction;
 
+
+/**
+ * Whether to wrap the world horizontally. Default is `true`.
+ * @type {boolean|undefined}
+ * @api
+ */
+olx.source.BingMapsOptions.prototype.wrapX;
+
+
 /**
  * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
  *     distance: (number|undefined),
@@ -3555,7 +3612,7 @@ olx.source.FormatVectorOptions.prototype.projection;
  * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
  *     defaultProjection: ol.proj.ProjectionLike,
  *     logo: (string|olx.LogoOptions|undefined),
- *     object: (GeoJSONObject|undefined),
+ *     object: (GeoJSONFeature|GeoJSONFeatureCollection|undefined),
  *     projection: ol.proj.ProjectionLike,
  *     text: (string|undefined),
  *     url: (string|undefined),
@@ -3590,8 +3647,8 @@ olx.source.GeoJSONOptions.prototype.logo;
 
 
 /**
- * Object.
- * @type {GeoJSONObject|undefined}
+ * GeoJSON feature or feature collection.
+ * @type {GeoJSONFeature|GeoJSONFeatureCollection|undefined}
  * @api
  */
 olx.source.GeoJSONOptions.prototype.object;
@@ -4254,7 +4311,8 @@ olx.source.KMLOptions.prototype.urls;
 
 /**
  * @typedef {{layer: string,
- *     tileLoadFunction: (ol.TileLoadFunctionType|undefined)}}
+ *     tileLoadFunction: (ol.TileLoadFunctionType|undefined),
+ *     url: (string|undefined)}}
  * @api
  */
 olx.source.MapQuestOptions;
@@ -4277,6 +4335,14 @@ olx.source.MapQuestOptions.prototype.tileLoadFunction;
 
 
 /**
+ * URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders.
+ * @type {string|undefined}
+ * @api
+ */
+olx.source.MapQuestOptions.prototype.url;
+
+
+/**
  * @typedef {{projection: ol.proj.ProjectionLike,
  *     tileGrid: (ol.tilegrid.TileGrid|undefined)}}
  * @api
@@ -4305,7 +4371,8 @@ olx.source.TileDebugOptions.prototype.tileGrid;
  *     crossOrigin: (null|string|undefined),
  *     maxZoom: (number|undefined),
  *     tileLoadFunction: (ol.TileLoadFunctionType|undefined),
- *     url: (string|undefined)}}
+ *     url: (string|undefined),
+ *     wrapX: (boolean|undefined)}}
  * @api
  */
 olx.source.OSMOptions;
@@ -4359,6 +4426,14 @@ olx.source.OSMOptions.prototype.url;
 
 
 /**
+ * Whether to wrap the world horizontally. Default is `true`.
+ * @type {boolean|undefined}
+ * @api
+ */
+olx.source.OSMOptions.prototype.wrapX;
+
+
+/**
  * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
  *     doc: (Document|undefined),
  *     logo: (string|olx.LogoOptions|undefined),
@@ -6506,6 +6581,13 @@ olx.ViewState.prototype.center;
 
 
 /**
+ * @type {ol.proj.Projection}
+ * @api
+ */
+olx.ViewState.prototype.projection;
+
+
+/**
  * @type {number}
  * @api
  */
diff --git a/package.json b/package.json
index 278c0a5..e52685d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "openlayers",
-  "version": "3.1.1",
+  "version": "3.2.0",
   "description": "Build tools and sources for developing OpenLayers based mapping applications",
   "keywords": [
     "map",
@@ -25,7 +25,7 @@
   },
   "dependencies": {
     "async": "0.9.0",
-    "closure-util": "1.2.0",
+    "closure-util": "1.3.0",
     "fs-extra": "0.12.0",
     "graceful-fs": "3.0.2",
     "htmlparser2": "3.7.3",
diff --git a/resources/example-behaviour.js b/resources/example-behaviour.js
index 2d54501..161f51d 100644
--- a/resources/example-behaviour.js
+++ b/resources/example-behaviour.js
@@ -29,11 +29,13 @@
         pairs = [],
         i,
         pair,
-        adjusted;
+        adjusted,
+        modeFound = false;
     for (i = chunks.length - 1; i >= 0; --i) {
       pair = chunks[i].split('=');
       if (pair[0].toLowerCase() === 'mode') {
         pair[1] = newMode;
+        modeFound = true;
       }
       adjusted = encodeURIComponent(pair[0]);
       if (typeof pair[1] !== undefined) {
@@ -41,8 +43,8 @@
       }
       pairs.push(adjusted);
     }
-    if (pairs.length === 0) {
-      pairs[0] = 'mode=' + encodeURIComponent(newMode);
+    if (!modeFound) {
+      pairs.push('mode=' + encodeURIComponent(newMode));
     }
     location.href = baseUrl + '?' + pairs.join('&');
   };
diff --git a/src/ol/collection.js b/src/ol/collection.js
index 77acfc6..dacf2f3 100644
--- a/src/ol/collection.js
+++ b/src/ol/collection.js
@@ -95,7 +95,7 @@ ol.Collection = function(opt_array) {
    * @private
    * @type {Array.<T>}
    */
-  this.array_ = opt_array || [];
+  this.array_ = goog.isDef(opt_array) ? opt_array : [];
 
   this.updateLength_();
 
diff --git a/src/ol/control/attributioncontrol.js b/src/ol/control/attributioncontrol.js
index 224f4dc..a2623b9 100644
--- a/src/ol/control/attributioncontrol.js
+++ b/src/ol/control/attributioncontrol.js
@@ -69,32 +69,33 @@ ol.control.Attribution = function(opt_options) {
   var tipLabel = goog.isDef(options.tipLabel) ?
       options.tipLabel : 'Attributions';
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.collapseLabel_ = goog.isDef(options.collapseLabel) ?
+  var collapseLabel = goog.isDef(options.collapseLabel) ?
       options.collapseLabel : '\u00BB';
 
   /**
    * @private
-   * @type {string}
+   * @type {Node}
    */
-  this.label_ = goog.isDef(options.label) ? options.label : 'i';
-  var label = goog.dom.createDom(goog.dom.TagName.SPAN, {},
-      (this.collapsible_ && !this.collapsed_) ?
-      this.collapseLabel_ : this.label_);
+  this.collapseLabel_ = /** @type {Node} */ (goog.isString(collapseLabel) ?
+      goog.dom.createDom(goog.dom.TagName.SPAN, {}, collapseLabel) :
+      collapseLabel);
 
+  var label = goog.isDef(options.label) ? options.label : 'i';
 
   /**
    * @private
-   * @type {Element}
+   * @type {Node}
    */
-  this.labelSpan_ = label;
+  this.label_ = /** @type {Node} */ (goog.isString(label) ?
+      goog.dom.createDom(goog.dom.TagName.SPAN, {}, label) :
+      label);
+
+  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
+      this.collapseLabel_ : this.label_;
   var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
     'type': 'button',
     'title': tipLabel
-  }, this.labelSpan_);
+  }, activeLabel);
 
   goog.events.listen(button, goog.events.EventType.CLICK,
       this.handleClick_, false, this);
@@ -341,8 +342,11 @@ ol.control.Attribution.prototype.handleClick_ = function(event) {
  */
 ol.control.Attribution.prototype.handleToggle_ = function() {
   goog.dom.classlist.toggle(this.element, 'ol-collapsed');
-  goog.dom.setTextContent(this.labelSpan_,
-      (this.collapsed_) ? this.collapseLabel_ : this.label_);
+  if (this.collapsed_) {
+    goog.dom.replaceNode(this.collapseLabel_, this.label_);
+  } else {
+    goog.dom.replaceNode(this.label_, this.collapseLabel_);
+  }
   this.collapsed_ = !this.collapsed_;
 };
 
diff --git a/src/ol/control/control.js b/src/ol/control/control.js
index 73ce0e1..ead9a74 100644
--- a/src/ol/control/control.js
+++ b/src/ol/control/control.js
@@ -51,8 +51,7 @@ ol.control.Control = function(options) {
    * @private
    * @type {Element}
    */
-  this.target_ = goog.isDef(options.target) ?
-      goog.dom.getElement(options.target) : null;
+  this.target_ = null;
 
   /**
    * @private
@@ -71,6 +70,10 @@ ol.control.Control = function(options) {
    */
   this.render = goog.isDef(options.render) ? options.render : goog.nullFunction;
 
+  if (goog.isDef(options.target)) {
+    this.setTarget(options.target);
+  }
+
 };
 goog.inherits(ol.control.Control, ol.Object);
 
@@ -121,3 +124,17 @@ ol.control.Control.prototype.setMap = function(map) {
     map.render();
   }
 };
+
+
+/**
+ * This function is used to set a target element for the control. It has no
+ * effect if it is called after the control has been added to the map (i.e.
+ * after `setMap` is called on the control). If no `target` is set in the
+ * options passed to the control constructor and if `setTarget` is not called
+ * then the control is added to the map's overlay container.
+ * @param {Element|string} target Target.
+ * @api
+ */
+ol.control.Control.prototype.setTarget = function(target) {
+  this.target_ = goog.dom.getElement(target);
+};
diff --git a/src/ol/control/fullscreencontrol.js b/src/ol/control/fullscreencontrol.js
index 8b688e3..6ddf69b 100644
--- a/src/ol/control/fullscreencontrol.js
+++ b/src/ol/control/fullscreencontrol.js
@@ -3,7 +3,6 @@ goog.provide('ol.control.FullScreen');
 goog.require('goog.asserts');
 goog.require('goog.dom');
 goog.require('goog.dom.TagName');
-goog.require('goog.dom.classlist');
 goog.require('goog.dom.fullscreen');
 goog.require('goog.dom.fullscreen.EventType');
 goog.require('goog.events');
@@ -37,13 +36,32 @@ ol.control.FullScreen = function(opt_options) {
   this.cssClassName_ = goog.isDef(options.className) ?
       options.className : 'ol-full-screen';
 
+  var label = goog.isDef(options.label) ? options.label : '\u2194';
+
+  /**
+   * @private
+   * @type {Node}
+   */
+  this.labelNode_ = /** @type {Node} */ (goog.isString(label) ?
+          goog.dom.createTextNode(label) : label);
+
+  var labelActive = goog.isDef(options.labelActive) ?
+      options.labelActive : '\u00d7';
+
+  /**
+   * @private
+   * @type {Node}
+   */
+  this.labelActiveNode_ = /** @type {Node} */ (goog.isString(labelActive) ?
+          goog.dom.createTextNode(labelActive) : labelActive);
+
   var tipLabel = goog.isDef(options.tipLabel) ?
       options.tipLabel : 'Toggle full-screen';
   var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
     'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen(),
     'type': 'button',
     'title': tipLabel
-  });
+  }, this.labelNode_);
 
   goog.events.listen(button, goog.events.EventType.CLICK,
       this.handleClick_, false, this);
@@ -120,14 +138,11 @@ ol.control.FullScreen.prototype.handleFullScreen_ = function() {
  * @private
  */
 ol.control.FullScreen.prototype.handleFullScreenChange_ = function() {
-  var opened = this.cssClassName_ + '-true';
-  var closed = this.cssClassName_ + '-false';
-  var anchor = goog.dom.getFirstElementChild(this.element);
   var map = this.getMap();
   if (goog.dom.fullscreen.isFullScreen()) {
-    goog.dom.classlist.swap(anchor, closed, opened);
+    goog.dom.replaceNode(this.labelActiveNode_, this.labelNode_);
   } else {
-    goog.dom.classlist.swap(anchor, opened, closed);
+    goog.dom.replaceNode(this.labelNode_, this.labelActiveNode_);
   }
   if (!goog.isNull(map)) {
     map.updateSize();
diff --git a/src/ol/control/overviewmapcontrol.js b/src/ol/control/overviewmapcontrol.js
index 628bdc7..307c76b 100644
--- a/src/ol/control/overviewmapcontrol.js
+++ b/src/ol/control/overviewmapcontrol.js
@@ -57,31 +57,33 @@ ol.control.OverviewMap = function(opt_options) {
   var tipLabel = goog.isDef(options.tipLabel) ?
       options.tipLabel : 'Overview map';
 
-  /**
-   * @private
-   * @type {string}
-   */
-  this.collapseLabel_ = goog.isDef(options.collapseLabel) ?
+  var collapseLabel = goog.isDef(options.collapseLabel) ?
       options.collapseLabel : '\u00AB';
 
   /**
    * @private
-   * @type {string}
+   * @type {Node}
    */
-  this.label_ = goog.isDef(options.label) ? options.label : '\u00BB';
-  var label = goog.dom.createDom(goog.dom.TagName.SPAN, {},
-      (this.collapsible_ && !this.collapsed_) ?
-      this.collapseLabel_ : this.label_);
+  this.collapseLabel_ = /** @type {Node} */ (goog.isString(collapseLabel) ?
+      goog.dom.createDom(goog.dom.TagName.SPAN, {}, collapseLabel) :
+      collapseLabel);
+
+  var label = goog.isDef(options.label) ? options.label : '\u00BB';
 
   /**
    * @private
-   * @type {Element}
+   * @type {Node}
    */
-  this.labelSpan_ = label;
+  this.label_ = /** @type {Node} */ (goog.isString(label) ?
+      goog.dom.createDom(goog.dom.TagName.SPAN, {}, label) :
+      label);
+
+  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
+      this.collapseLabel_ : this.label_;
   var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
     'type': 'button',
     'title': tipLabel
-  }, this.labelSpan_);
+  }, activeLabel);
 
   goog.events.listen(button, goog.events.EventType.CLICK,
       this.handleClick_, false, this);
@@ -427,8 +429,11 @@ ol.control.OverviewMap.prototype.handleClick_ = function(event) {
  */
 ol.control.OverviewMap.prototype.handleToggle_ = function() {
   goog.dom.classlist.toggle(this.element, 'ol-collapsed');
-  goog.dom.setTextContent(this.labelSpan_,
-      (this.collapsed_) ? this.collapseLabel_ : this.label_);
+  if (this.collapsed_) {
+    goog.dom.replaceNode(this.collapseLabel_, this.label_);
+  } else {
+    goog.dom.replaceNode(this.label_, this.collapseLabel_);
+  }
   this.collapsed_ = !this.collapsed_;
 
   // manage overview map if it had not been rendered before and control
diff --git a/src/ol/control/rotatecontrol.js b/src/ol/control/rotatecontrol.js
index 4073063..7853802 100644
--- a/src/ol/control/rotatecontrol.js
+++ b/src/ol/control/rotatecontrol.js
@@ -1,6 +1,5 @@
 goog.provide('ol.control.Rotate');
 
-goog.require('goog.asserts');
 goog.require('goog.dom');
 goog.require('goog.dom.TagName');
 goog.require('goog.dom.classlist');
@@ -32,12 +31,22 @@ ol.control.Rotate = function(opt_options) {
   var className = goog.isDef(options.className) ?
       options.className : 'ol-rotate';
 
+  var label = goog.isDef(options.label) ?
+      options.label : '\u21E7';
+
   /**
-   * @type {Element}
+   * @type {Node}
    * @private
    */
-  this.label_ = goog.dom.createDom(goog.dom.TagName.SPAN,
-      'ol-compass', goog.isDef(options.label) ? options.label : '\u21E7');
+  this.label_ = null;
+
+  if (goog.isString(label)) {
+    this.label_ = goog.dom.createDom(goog.dom.TagName.SPAN,
+        'ol-compass', label);
+  } else {
+    this.label_ = label;
+    goog.dom.classlist.add(this.label_, 'ol-compass');
+  }
 
   var tipLabel = goog.isDef(options.tipLabel) ?
       options.tipLabel : 'Reset rotation';
diff --git a/src/ol/control/scalelinecontrol.js b/src/ol/control/scalelinecontrol.js
index 6ef6e4c..0396578 100644
--- a/src/ol/control/scalelinecontrol.js
+++ b/src/ol/control/scalelinecontrol.js
@@ -325,7 +325,7 @@ ol.control.ScaleLine.prototype.updateElement_ = function() {
     ++i;
   }
 
-  var html = count + suffix;
+  var html = count + ' ' + suffix;
   if (this.renderedHTML_ != html) {
     this.innerElement_.innerHTML = html;
     this.renderedHTML_ = html;
diff --git a/src/ol/control/zoomtoextentcontrol.js b/src/ol/control/zoomtoextentcontrol.js
index 0af7824..52053fe 100644
--- a/src/ol/control/zoomtoextentcontrol.js
+++ b/src/ol/control/zoomtoextentcontrol.js
@@ -32,12 +32,13 @@ ol.control.ZoomToExtent = function(opt_options) {
   var className = goog.isDef(options.className) ? options.className :
       'ol-zoom-extent';
 
+  var label = goog.isDef(options.label) ? options.label : 'E';
   var tipLabel = goog.isDef(options.tipLabel) ?
       options.tipLabel : 'Fit to extent';
   var button = goog.dom.createDom(goog.dom.TagName.BUTTON, {
     'type': 'button',
     'title': tipLabel
-  });
+  }, label);
 
   goog.events.listen(button, goog.events.EventType.CLICK,
       this.handleClick_, false, this);
diff --git a/src/ol/featureoverlay.js b/src/ol/featureoverlay.js
index 12e6c7a..c21e5db 100644
--- a/src/ol/featureoverlay.js
+++ b/src/ol/featureoverlay.js
@@ -78,7 +78,7 @@ ol.FeatureOverlay = function(opt_options) {
 
   if (goog.isDef(options.features)) {
     if (goog.isArray(options.features)) {
-      this.setFeatures(new ol.Collection(goog.array.clone(options.features)));
+      this.setFeatures(new ol.Collection(options.features.slice()));
     } else {
       goog.asserts.assertInstanceof(options.features, ol.Collection);
       this.setFeatures(options.features);
@@ -113,6 +113,15 @@ ol.FeatureOverlay.prototype.getFeatures = function() {
 
 
 /**
+ * @return {?ol.Map} The map with which this feature overlay is associated.
+ * @api
+ */
+ol.FeatureOverlay.prototype.getMap = function() {
+  return this.map_;
+};
+
+
+/**
  * @private
  */
 ol.FeatureOverlay.prototype.handleFeatureChange_ = function() {
diff --git a/src/ol/format/featureformat.js b/src/ol/format/featureformat.js
index 363428e..a553dc1 100644
--- a/src/ol/format/featureformat.js
+++ b/src/ol/format/featureformat.js
@@ -1,6 +1,5 @@
 goog.provide('ol.format.Feature');
 
-goog.require('goog.array');
 goog.require('ol.geom.Geometry');
 goog.require('ol.proj');
 
@@ -41,8 +40,7 @@ ol.format.Feature.prototype.getExtensions = goog.abstractMethod;
  * @return {olx.format.ReadOptions|undefined} Options.
  * @protected
  */
-ol.format.Feature.prototype.getReadOptions = function(
-    source, opt_options) {
+ol.format.Feature.prototype.getReadOptions = function(source, opt_options) {
   var options;
   if (goog.isDef(opt_options)) {
     options = {
@@ -64,8 +62,7 @@ ol.format.Feature.prototype.getReadOptions = function(
  * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined}
  *     Updated options.
  */
-ol.format.Feature.prototype.adaptOptions = function(
-    options) {
+ol.format.Feature.prototype.adaptOptions = function(options) {
   var updatedOptions;
   if (goog.isDef(options)) {
     updatedOptions = {
@@ -177,7 +174,7 @@ ol.format.Feature.transformWithOptions = function(
       // FIXME this is necessary because ol.format.GML treats extents
       // as geometries
       return ol.proj.transformExtent(
-          write ? goog.array.clone(geometry) : geometry,
+          write ? geometry.slice() : geometry,
           write ? featureProjection : dataProjection,
           write ? dataProjection : featureProjection);
     }
diff --git a/src/ol/format/geojsonformat.js b/src/ol/format/geojsonformat.js
index 01ebe4f..c29a15b 100644
--- a/src/ol/format/geojsonformat.js
+++ b/src/ol/format/geojsonformat.js
@@ -64,7 +64,7 @@ ol.format.GeoJSON.EXTENSIONS_ = ['.geojson'];
 
 
 /**
- * @param {GeoJSONObject} object Object.
+ * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object.
  * @param {olx.format.ReadOptions=} opt_options Read options.
  * @private
  * @return {ol.geom.Geometry} Geometry.
@@ -92,7 +92,7 @@ ol.format.GeoJSON.readGeometryCollectionGeometry_ = function(
   goog.asserts.assert(object.type == 'GeometryCollection');
   var geometries = goog.array.map(object.geometries,
       /**
-       * @param {GeoJSONObject} geometry Geometry.
+       * @param {GeoJSONGeometry} geometry Geometry.
        * @return {ol.geom.Geometry} geometry Geometry.
        */
       function(geometry) {
@@ -505,18 +505,17 @@ ol.format.GeoJSON.prototype.writeFeatureObject = function(
   };
   var id = feature.getId();
   if (goog.isDefAndNotNull(id)) {
-    goog.object.set(object, 'id', id);
+    object['id'] = id;
   }
   var geometry = feature.getGeometry();
   if (goog.isDefAndNotNull(geometry)) {
-    goog.object.set(
-        object, 'geometry',
-        ol.format.GeoJSON.writeGeometry_(geometry, opt_options));
+    object['geometry'] =
+        ol.format.GeoJSON.writeGeometry_(geometry, opt_options);
   }
   var properties = feature.getProperties();
   goog.object.remove(properties, feature.getGeometryName());
   if (!goog.object.isEmpty(properties)) {
-    goog.object.set(object, 'properties', properties);
+    object['properties'] = properties;
   }
   return object;
 };
diff --git a/src/ol/format/gml/gml2format.js b/src/ol/format/gml/gml2format.js
index f1564bc..9b5774a 100644
--- a/src/ol/format/gml/gml2format.js
+++ b/src/ol/format/gml/gml2format.js
@@ -1,9 +1,7 @@
 goog.provide('ol.format.GML2');
 
 goog.require('goog.asserts');
-goog.require('goog.dom');
 goog.require('goog.dom.NodeType');
-goog.require('goog.object');
 goog.require('ol.extent');
 goog.require('ol.format.GML');
 goog.require('ol.format.GMLBase');
@@ -44,8 +42,8 @@ goog.inherits(ol.format.GML2, ol.format.GMLBase);
  * @type {string}
  * @private
  */
-ol.format.GML2.schemaLocation_ = 'http://www.opengis.net/gml ' +
-    'http://schemas.opengis.net/gml/2.1.2/feature.xsd';
+ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS +
+    ' http://schemas.opengis.net/gml/2.1.2/feature.xsd';
 
 
 /**
@@ -58,7 +56,7 @@ ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) {
   var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
   var context = objectStack[0];
   goog.asserts.assert(goog.isObject(context));
-  var containerSrs = goog.object.get(context, 'srsName');
+  var containerSrs = context['srsName'];
   var containerDimension = node.parentNode.getAttribute('srsDimension');
   var axisOrientation = 'enu';
   if (!goog.isNull(containerSrs)) {
diff --git a/src/ol/format/gml/gml3format.js b/src/ol/format/gml/gml3format.js
index d0d879b..1595f9a 100644
--- a/src/ol/format/gml/gml3format.js
+++ b/src/ol/format/gml/gml3format.js
@@ -3,7 +3,6 @@ goog.provide('ol.format.GML3');
 
 goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.dom');
 goog.require('goog.dom.NodeType');
 goog.require('goog.object');
 goog.require('ol.Feature');
@@ -84,8 +83,8 @@ goog.inherits(ol.format.GML3, ol.format.GMLBase);
  * @type {string}
  * @private
  */
-ol.format.GML3.schemaLocation_ = 'http://www.opengis.net/gml ' +
-    'http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
+ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS +
+    ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
     '1.0.0/gmlsf.xsd';
 
 
@@ -356,7 +355,7 @@ ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) {
   }
   var context = objectStack[0];
   goog.asserts.assert(goog.isObject(context));
-  var containerSrs = goog.object.get(context, 'srsName');
+  var containerSrs = context['srsName'];
   var axisOrientation = 'enu';
   if (!goog.isNull(containerSrs)) {
     var proj = ol.proj.get(containerSrs);
@@ -392,7 +391,7 @@ ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) {
   var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
   var context = objectStack[0];
   goog.asserts.assert(goog.isObject(context));
-  var containerSrs = goog.object.get(context, 'srsName');
+  var containerSrs = context['srsName'];
   var containerDimension = node.parentNode.getAttribute('srsDimension');
   var axisOrientation = 'enu';
   if (!goog.isNull(containerSrs)) {
@@ -614,7 +613,7 @@ ol.format.GML3.prototype.SEGMENTS_PARSERS_ = Object({
 ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   var axisOrientation = 'enu';
   if (goog.isDefAndNotNull(srsName)) {
     axisOrientation = ol.proj.get(srsName).getAxisOrientation();
@@ -657,7 +656,7 @@ ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) {
 ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   // only 2d for simple features profile
   var points = value.getCoordinates();
   var len = points.length;
@@ -680,7 +679,7 @@ ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) {
 ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   if (goog.isDefAndNotNull(srsName)) {
     node.setAttribute('srsName', srsName);
   }
@@ -711,7 +710,7 @@ ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) {
   goog.asserts.assert(extent.length == 4);
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   if (goog.isDef(srsName)) {
     node.setAttribute('srsName', srsName);
   }
@@ -735,7 +734,7 @@ ol.format.GML3.prototype.writeLinearRing_ =
     function(node, geometry, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   if (goog.isDefAndNotNull(srsName)) {
     node.setAttribute('srsName', srsName);
   }
@@ -757,9 +756,9 @@ ol.format.GML3.prototype.RING_NODE_FACTORY_ =
   var context = objectStack[objectStack.length - 1];
   var parentNode = context.node;
   goog.asserts.assert(goog.isObject(context));
-  var exteriorWritten = goog.object.get(context, 'exteriorWritten');
+  var exteriorWritten = context['exteriorWritten'];
   if (!goog.isDef(exteriorWritten)) {
-    goog.object.set(context, 'exteriorWritten', true);
+    context['exteriorWritten'] = true;
   }
   return ol.xml.createElementNS(parentNode.namespaceURI,
       goog.isDef(exteriorWritten) ? 'interior' : 'exterior');
@@ -776,7 +775,7 @@ ol.format.GML3.prototype.writeSurfaceOrPolygon_ =
     function(node, geometry, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   if (node.nodeName !== 'PolygonPatch' && goog.isDefAndNotNull(srsName)) {
     node.setAttribute('srsName', srsName);
   }
@@ -806,7 +805,7 @@ ol.format.GML3.prototype.writeCurveOrLineString_ =
     function(node, geometry, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   if (node.nodeName !== 'LineStringSegment' &&
       goog.isDefAndNotNull(srsName)) {
     node.setAttribute('srsName', srsName);
@@ -835,8 +834,8 @@ ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ =
     function(node, geometry, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  var surface = goog.object.get(context, 'surface');
+  var srsName = context['srsName'];
+  var surface = context['surface'];
   if (goog.isDefAndNotNull(srsName)) {
     node.setAttribute('srsName', srsName);
   }
@@ -858,7 +857,7 @@ ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry,
     objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
+  var srsName = context['srsName'];
   if (goog.isDefAndNotNull(srsName)) {
     node.setAttribute('srsName', srsName);
   }
@@ -880,8 +879,8 @@ ol.format.GML3.prototype.writeMultiCurveOrLineString_ =
     function(node, geometry, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var srsName = goog.object.get(context, 'srsName');
-  var curve = goog.object.get(context, 'curve');
+  var srsName = context['srsName'];
+  var curve = context['curve'];
   if (goog.isDefAndNotNull(srsName)) {
     node.setAttribute('srsName', srsName);
   }
@@ -1030,7 +1029,7 @@ ol.format.GML3.prototype.writeFeatureElement =
   }
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var featureNS = goog.object.get(context, 'featureNS');
+  var featureNS = context['featureNS'];
   var geometryName = feature.getGeometryName();
   if (!goog.isDef(context.serializers)) {
     context.serializers = {};
@@ -1076,8 +1075,8 @@ ol.format.GML3.prototype.writeFeatureMembers_ =
     function(node, features, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featureNS = goog.object.get(context, 'featureNS');
+  var featureType = context['featureType'];
+  var featureNS = context['featureNS'];
   var serializers = {};
   serializers[featureNS] = {};
   serializers[featureNS][featureType] = ol.xml.makeChildAppender(
@@ -1219,10 +1218,10 @@ ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ =
     function(value, objectStack, opt_nodeName) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var multiSurface = goog.object.get(context, 'multiSurface');
-  var surface = goog.object.get(context, 'surface');
-  var curve = goog.object.get(context, 'curve');
-  var multiCurve = goog.object.get(context, 'multiCurve');
+  var multiSurface = context['multiSurface'];
+  var surface = context['surface'];
+  var curve = context['curve'];
+  var multiCurve = context['multiCurve'];
   var parentNode = objectStack[objectStack.length - 1].node;
   goog.asserts.assert(ol.xml.isNode(parentNode));
   var nodeName;
@@ -1343,7 +1342,7 @@ ol.format.GML.prototype.writeFeatures;
  * Encode an array of features in the GML 3.1.1 format as an XML node.
  *
  * @function
- * @param {ol.Feature} feature Feature.
+ * @param {Array.<ol.Feature>} features Features.
  * @param {olx.format.WriteOptions=} opt_options Options.
  * @return {Node} Node.
  * @api
diff --git a/src/ol/format/gml/gmlbaseformat.js b/src/ol/format/gml/gmlbaseformat.js
index 415fe1c..771d526 100644
--- a/src/ol/format/gml/gmlbaseformat.js
+++ b/src/ol/format/gml/gmlbaseformat.js
@@ -5,7 +5,6 @@ goog.provide('ol.format.GMLBase');
 
 goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.dom');
 goog.require('goog.dom.NodeType');
 goog.require('goog.object');
 goog.require('goog.string');
@@ -68,18 +67,35 @@ ol.format.GMLBase = function(opt_options) {
    */
   this.schemaLocation = '';
 
+  /**
+   * @type {Object.<string, Object.<string, Object>>}
+   */
+  this.FEATURE_COLLECTION_PARSERS = {};
+  this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS] = {
+    'featureMember': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readFeaturesInternal),
+    'featureMembers': ol.xml.makeReplacer(
+        ol.format.GMLBase.prototype.readFeaturesInternal)
+  };
+
   goog.base(this);
 };
 goog.inherits(ol.format.GMLBase, ol.format.XMLFeature);
 
 
 /**
+ * @const
+ * @type {string}
+ */
+ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml';
+
+
+/**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @return {Array.<ol.Feature>} Features.
- * @private
  */
-ol.format.GMLBase.prototype.readFeatures_ = function(node, objectStack) {
+ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   var localName = ol.xml.getLocalName(node);
   var features;
@@ -90,19 +106,19 @@ ol.format.GMLBase.prototype.readFeatures_ = function(node, objectStack) {
   } else if (localName == 'featureMembers' || localName == 'featureMember') {
     var context = objectStack[0];
     goog.asserts.assert(goog.isObject(context));
-    var featureType = goog.object.get(context, 'featureType');
+    var featureType = context['featureType'];
     if (!goog.isDef(featureType) && !goog.isNull(node.firstElementChild)) {
       var member = node.firstElementChild;
       featureType = member.nodeName.split(':').pop();
-      goog.object.set(context, 'featureType', featureType);
-      goog.object.set(context, 'featureNS', member.namespaceURI);
+      context['featureType'] = featureType;
+      context['featureNS'] = member.namespaceURI;
     }
     var parsers = {};
     var parsersNS = {};
     parsers[featureType] = (localName == 'featureMembers') ?
         ol.xml.makeArrayPusher(this.readFeatureElement, this) :
         ol.xml.makeReplacer(this.readFeatureElement, this);
-    parsersNS[goog.object.get(context, 'featureNS')] = parsers;
+    parsersNS[context['featureNS']] = parsers;
     features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack);
   }
   if (!goog.isDef(features)) {
@@ -113,19 +129,6 @@ ol.format.GMLBase.prototype.readFeatures_ = function(node, objectStack) {
 
 
 /**
- * @type {Object.<string, Object.<string, Object>>}
- */
-ol.format.GMLBase.prototype.FEATURE_COLLECTION_PARSERS = Object({
-  'http://www.opengis.net/gml': {
-    'featureMember': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readFeatures_),
-    'featureMembers': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readFeatures_)
-  }
-});
-
-
-/**
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @return {ol.geom.Geometry|undefined} Geometry.
@@ -133,8 +136,7 @@ ol.format.GMLBase.prototype.FEATURE_COLLECTION_PARSERS = Object({
 ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) {
   var context = objectStack[0];
   goog.asserts.assert(goog.isObject(context));
-  goog.object.set(context, 'srsName',
-      node.firstElementChild.getAttribute('srsName'));
+  context['srsName'] = node.firstElementChild.getAttribute('srsName');
   var geometry = ol.xml.pushParseAndPop(/** @type {ol.geom.Geometry} */(null),
       this.GEOMETRY_PARSERS_, node, objectStack, this);
   if (goog.isDefAndNotNull(geometry)) {
@@ -154,7 +156,7 @@ ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) {
 ol.format.GMLBase.prototype.readFeatureElement = function(node, objectStack) {
   var n;
   var fid = node.getAttribute('fid') ||
-      ol.xml.getAttributeNS(node, 'http://www.opengis.net/gml', 'id');
+      ol.xml.getAttributeNS(node, ol.format.GMLBase.GMLNS, 'id');
   var values = {}, geometryName;
   for (n = node.firstElementChild; !goog.isNull(n);
       n = n.nextElementSibling) {
@@ -550,7 +552,7 @@ ol.format.GMLBase.prototype.readFeaturesFromNode =
   if (goog.isDef(opt_options)) {
     goog.object.extend(options, this.getReadOptions(node, opt_options));
   }
-  return this.readFeatures_(node, [options]);
+  return this.readFeaturesInternal(node, [options]);
 };
 
 
diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js
index e37220b..ec234de 100644
--- a/src/ol/format/gpxformat.js
+++ b/src/ol/format/gpxformat.js
@@ -71,14 +71,14 @@ ol.format.GPX.appendCoordinate_ = function(flatCoordinates, node, values) {
       parseFloat(node.getAttribute('lat')));
   if (goog.object.containsKey(values, 'ele')) {
     flatCoordinates.push(
-        /** @type {number} */ (goog.object.get(values, 'ele')));
+        /** @type {number} */ (values['ele']));
     goog.object.remove(values, 'ele');
   } else {
     flatCoordinates.push(0);
   }
   if (goog.object.containsKey(values, 'time')) {
     flatCoordinates.push(
-        /** @type {number} */ (goog.object.get(values, 'time')));
+        /** @type {number} */ (values['time']));
     goog.object.remove(values, 'time');
   } else {
     flatCoordinates.push(0);
@@ -98,7 +98,7 @@ ol.format.GPX.parseLink_ = function(node, objectStack) {
   var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
   var href = node.getAttribute('href');
   if (!goog.isNull(href)) {
-    goog.object.set(values, 'link', href);
+    values['link'] = href;
   }
   ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack);
 };
@@ -113,7 +113,7 @@ ol.format.GPX.parseExtensions_ = function(node, objectStack) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   goog.asserts.assert(node.localName == 'extensions');
   var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  goog.object.set(values, 'extensionsNode_', node);
+  values['extensionsNode_'] = node;
 };
 
 
@@ -130,7 +130,7 @@ ol.format.GPX.parseRtePt_ = function(node, objectStack) {
   if (goog.isDef(values)) {
     var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
     var flatCoordinates = /** @type {Array.<number>} */
-        (goog.object.get(rteValues, 'flatCoordinates'));
+        (rteValues['flatCoordinates']);
     ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
   }
 };
@@ -149,7 +149,7 @@ ol.format.GPX.parseTrkPt_ = function(node, objectStack) {
   if (goog.isDef(values)) {
     var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
     var flatCoordinates = /** @type {Array.<number>} */
-        (goog.object.get(trkValues, 'flatCoordinates'));
+        (trkValues['flatCoordinates']);
     ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
   }
 };
@@ -166,8 +166,8 @@ ol.format.GPX.parseTrkSeg_ = function(node, objectStack) {
   var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
   ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack);
   var flatCoordinates = /** @type {Array.<number>} */
-      (goog.object.get(values, 'flatCoordinates'));
-  var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends'));
+      (values['flatCoordinates']);
+  var ends = /** @type {Array.<number>} */ (values['ends']);
   ends.push(flatCoordinates.length);
 };
 
@@ -189,7 +189,7 @@ ol.format.GPX.readRte_ = function(node, objectStack) {
     return undefined;
   }
   var flatCoordinates = /** @type {Array.<number>} */
-      (goog.object.get(values, 'flatCoordinates'));
+      (values['flatCoordinates']);
   goog.object.remove(values, 'flatCoordinates');
   var geometry = new ol.geom.LineString(null);
   geometry.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
@@ -218,9 +218,9 @@ ol.format.GPX.readTrk_ = function(node, objectStack) {
     return undefined;
   }
   var flatCoordinates = /** @type {Array.<number>} */
-      (goog.object.get(values, 'flatCoordinates'));
+      (values['flatCoordinates']);
   goog.object.remove(values, 'flatCoordinates');
-  var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends'));
+  var ends = /** @type {Array.<number>} */ (values['ends']);
   goog.object.remove(values, 'ends');
   var geometry = new ol.geom.MultiLineString(null);
   geometry.setFlatCoordinates(
@@ -502,22 +502,6 @@ ol.format.GPX.prototype.readProjection;
 
 
 /**
- * @inheritDoc
- */
-ol.format.GPX.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.GPX.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
-};
-
-
-/**
  * @param {Node} node Node.
  * @param {string} value Value for the link's `href` attribute.
  * @param {Array.<*>} objectStack Node stack.
@@ -527,10 +511,10 @@ ol.format.GPX.writeLink_ = function(node, value, objectStack) {
   node.setAttribute('href', value);
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var properties = goog.object.get(context, 'properties');
+  var properties = context['properties'];
   var link = [
-    goog.object.get(properties, 'linkText'),
-    goog.object.get(properties, 'linkType')
+    properties['linkText'],
+    properties['linkType']
   ];
   ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}),
       ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
@@ -550,25 +534,25 @@ ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) {
   var parentNode = context.node;
   goog.asserts.assert(ol.xml.isNode(parentNode));
   var namespaceURI = parentNode.namespaceURI;
-  var properties = goog.object.get(context, 'properties');
+  var properties = context['properties'];
   //FIXME Projection handling
   ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]);
   ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]);
-  var geometryLayout = goog.object.get(context, 'geometryLayout');
+  var geometryLayout = context['geometryLayout'];
   /* jshint -W086 */
   switch (geometryLayout) {
     case ol.geom.GeometryLayout.XYZM:
       if (coordinate[3] !== 0) {
-        goog.object.set(properties, 'time', coordinate[3]);
+        properties['time'] = coordinate[3];
       }
     case ol.geom.GeometryLayout.XYZ:
       if (coordinate[2] !== 0) {
-        goog.object.set(properties, 'ele', coordinate[2]);
+        properties['ele'] = coordinate[2];
       }
       break;
     case ol.geom.GeometryLayout.XYM:
       if (coordinate[2] !== 0) {
-        goog.object.set(properties, 'time', coordinate[2]);
+        properties['time'] = coordinate[2];
       }
   }
   /* jshint +W086 */
@@ -596,8 +580,8 @@ ol.format.GPX.writeRte_ = function(node, feature, objectStack) {
     goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
     geometry = /** @type {ol.geom.LineString} */
         (ol.format.Feature.transformWithOptions(geometry, true, options));
-    goog.object.set(context, 'geometryLayout', geometry.getLayout());
-    goog.object.set(properties, 'rtept', geometry.getCoordinates());
+    context['geometryLayout'] = geometry.getLayout();
+    properties['rtept'] = geometry.getCoordinates();
   }
   var parentNode = objectStack[objectStack.length - 1].node;
   var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI];
@@ -623,7 +607,7 @@ ol.format.GPX.writeTrk_ = function(node, feature, objectStack) {
     goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString);
     geometry = /** @type {ol.geom.MultiLineString} */
         (ol.format.Feature.transformWithOptions(geometry, true, options));
-    goog.object.set(properties, 'trkseg', geometry.getLineStrings());
+    properties['trkseg'] = geometry.getLineStrings();
   }
   var parentNode = objectStack[objectStack.length - 1].node;
   var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI];
@@ -659,13 +643,13 @@ ol.format.GPX.writeWpt_ = function(node, feature, objectStack) {
   var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  goog.object.set(context, 'properties', feature.getProperties());
+  context['properties'] = feature.getProperties();
   var geometry = feature.getGeometry();
   if (goog.isDef(geometry)) {
     goog.asserts.assertInstanceof(geometry, ol.geom.Point);
     geometry = /** @type {ol.geom.Point} */
         (ol.format.Feature.transformWithOptions(geometry, true, options));
-    goog.object.set(context, 'geometryLayout', geometry.getLayout());
+    context['geometryLayout'] = geometry.getLayout();
     ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack);
   }
 };
@@ -754,10 +738,7 @@ ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS(
 
 /**
  * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
+ * @type {function(*, Array.<*>, string=): (Node|undefined)}
  * @private
  */
 ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');
diff --git a/src/ol/format/igcformat.js b/src/ol/format/igcformat.js
index c6d9a98..aa57c62 100644
--- a/src/ol/format/igcformat.js
+++ b/src/ol/format/igcformat.js
@@ -218,11 +218,3 @@ ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) {
  * @api
  */
 ol.format.IGC.prototype.readProjection;
-
-
-/**
- * @inheritDoc
- */
-ol.format.IGC.prototype.readProjectionFromText = function(text) {
-  return this.defaultDataProjection;
-};
diff --git a/src/ol/format/kmlformat.js b/src/ol/format/kmlformat.js
index 36114a3..de4c4d4 100644
--- a/src/ol/format/kmlformat.js
+++ b/src/ol/format/kmlformat.js
@@ -208,7 +208,7 @@ ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({
  * @type {ol.Size}
  * @private
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [2, 20]; // FIXME maybe [8, 32] ?
+ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ?
 
 
 /**
@@ -234,7 +234,7 @@ ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ =
  * @type {ol.Size}
  * @private
  */
-ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [32, 32];
+ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64];
 
 
 /**
@@ -253,11 +253,12 @@ ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
  */
 ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({
   anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_,
+  anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
   anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_,
   anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_,
   crossOrigin: 'anonymous',
   rotation: 0,
-  scale: 1,
+  scale: 0.5,
   size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_,
   src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_
 });
@@ -472,7 +473,7 @@ ol.format.KML.IconStyleParser_ = function(node, objectStack) {
   var IconObject = /** @type {Object} */ (goog.object.get(object, 'Icon', {}));
   var src;
   var href = /** @type {string|undefined} */
-      (goog.object.get(IconObject, 'href'));
+      (IconObject['href']);
   if (goog.isDef(href)) {
     src = href;
   } else {
@@ -480,7 +481,7 @@ ol.format.KML.IconStyleParser_ = function(node, objectStack) {
   }
   var anchor, anchorXUnits, anchorYUnits;
   var hotSpot = /** @type {ol.format.KMLVec2_|undefined} */
-      (goog.object.get(object, 'hotSpot'));
+      (object['hotSpot']);
   if (goog.isDef(hotSpot)) {
     anchor = [hotSpot.x, hotSpot.y];
     anchorXUnits = hotSpot.xunits;
@@ -497,31 +498,31 @@ ol.format.KML.IconStyleParser_ = function(node, objectStack) {
 
   var offset;
   var x = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'x'));
+      (IconObject['x']);
   var y = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'y'));
+      (IconObject['y']);
   if (goog.isDef(x) && goog.isDef(y)) {
     offset = [x, y];
   }
 
   var size;
   var w = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'w'));
+      (IconObject['w']);
   var h = /** @type {number|undefined} */
-      (goog.object.get(IconObject, 'h'));
+      (IconObject['h']);
   if (goog.isDef(w) && goog.isDef(h)) {
     size = [w, h];
   }
 
   var rotation;
   var heading = /** @type {number|undefined} */
-      (goog.object.get(object, 'heading'));
+      (object['heading']);
   if (goog.isDef(heading)) {
     rotation = goog.math.toRadians(heading);
   }
 
   var scale = /** @type {number|undefined} */
-      (goog.object.get(object, 'scale'));
+      (object['scale']);
   if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
     size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
   }
@@ -539,7 +540,7 @@ ol.format.KML.IconStyleParser_ = function(node, objectStack) {
     size: size,
     src: src
   });
-  goog.object.set(styleObject, 'imageStyle', imageStyle);
+  styleObject['imageStyle'] = imageStyle;
 };
 
 
@@ -565,9 +566,9 @@ ol.format.KML.LabelStyleParser_ = function(node, objectStack) {
           (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_))
     }),
     scale: /** @type {number|undefined} */
-        (goog.object.get(object, 'scale'))
+        (object['scale'])
   });
-  goog.object.set(styleObject, 'textStyle', textStyle);
+  styleObject['textStyle'] = textStyle;
 };
 
 
@@ -596,7 +597,7 @@ ol.format.KML.LineStyleParser_ = function(node, objectStack) {
         (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)),
     width: /** @type {number} */ (goog.object.get(object, 'width', 1))
   });
-  goog.object.set(styleObject, 'strokeStyle', strokeStyle);
+  styleObject['strokeStyle'] = strokeStyle;
 };
 
 
@@ -620,15 +621,15 @@ ol.format.KML.PolyStyleParser_ = function(node, objectStack) {
     color: /** @type {ol.Color} */
         (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_))
   });
-  goog.object.set(styleObject, 'fillStyle', fillStyle);
-  var fill = /** @type {boolean|undefined} */ (goog.object.get(object, 'fill'));
+  styleObject['fillStyle'] = fillStyle;
+  var fill = /** @type {boolean|undefined} */ (object['fill']);
   if (goog.isDef(fill)) {
-    goog.object.set(styleObject, 'fill', fill);
+    styleObject['fill'] = fill;
   }
   var outline =
-      /** @type {boolean|undefined} */ (goog.object.get(object, 'outline'));
+      /** @type {boolean|undefined} */ (object['outline']);
   if (goog.isDef(outline)) {
-    goog.object.set(styleObject, 'outline', outline);
+    styleObject['outline'] = outline;
   }
 };
 
@@ -944,7 +945,7 @@ ol.format.KML.readStyle_ = function(node, objectStack) {
   var fillStyle = /** @type {ol.style.Fill} */ (goog.object.get(
       styleObject, 'fillStyle', ol.format.KML.DEFAULT_FILL_STYLE_));
   var fill = /** @type {boolean|undefined} */
-      (goog.object.get(styleObject, 'fill'));
+      (styleObject['fill']);
   if (goog.isDef(fill) && !fill) {
     fillStyle = null;
   }
@@ -955,7 +956,7 @@ ol.format.KML.readStyle_ = function(node, objectStack) {
   var strokeStyle = /** @type {ol.style.Stroke} */ (goog.object.get(
       styleObject, 'strokeStyle', ol.format.KML.DEFAULT_STROKE_STYLE_));
   var outline = /** @type {boolean|undefined} */
-      (goog.object.get(styleObject, 'outline'));
+      (styleObject['outline']);
   if (goog.isDef(outline) && !outline) {
     strokeStyle = null;
   }
@@ -985,7 +986,7 @@ ol.format.KML.DataParser_ = function(node, objectStack) {
       var featureObject =
           /** @type {Object} */ (objectStack[objectStack.length - 1]);
       goog.asserts.assert(goog.isObject(featureObject));
-      goog.object.set(featureObject, name, data);
+      featureObject[name] = data;
     }
   }
 };
@@ -1017,15 +1018,15 @@ ol.format.KML.PairDataParser_ = function(node, objectStack) {
     return;
   }
   var key = /** @type {string|undefined} */
-      (goog.object.get(pairObject, 'key'));
+      (pairObject['key']);
   if (goog.isDef(key) && key == 'normal') {
     var styleUrl = /** @type {string|undefined} */
-        (goog.object.get(pairObject, 'styleUrl'));
+        (pairObject['styleUrl']);
     if (goog.isDef(styleUrl)) {
       objectStack[objectStack.length - 1] = styleUrl;
     }
     var Style = /** @type {ol.style.Style} */
-        (goog.object.get(pairObject, 'Style'));
+        (pairObject['Style']);
     if (goog.isDef(Style)) {
       objectStack[objectStack.length - 1] = Style;
     }
@@ -1048,9 +1049,9 @@ ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) {
   var placemarkObject = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(placemarkObject));
   if (goog.isArray(styleMapValue)) {
-    goog.object.set(placemarkObject, 'Style', styleMapValue);
+    placemarkObject['Style'] = styleMapValue;
   } else if (goog.isString(styleMapValue)) {
-    goog.object.set(placemarkObject, 'styleUrl', styleMapValue);
+    placemarkObject['styleUrl'] = styleMapValue;
   } else {
     goog.asserts.fail();
   }
@@ -1082,7 +1083,7 @@ ol.format.KML.SimpleDataParser_ = function(node, objectStack) {
     var data = ol.format.XSD.readString(node);
     var featureObject =
         /** @type {Object} */ (objectStack[objectStack.length - 1]);
-    goog.object.set(featureObject, name, data);
+    featureObject[name] = data;
   }
 };
 
@@ -1134,6 +1135,18 @@ ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) {
  * @param {Array.<*>} objectStack Object stack.
  * @private
  */
+ol.format.KML.LinkParser_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Link');
+  ol.xml.parseNode(ol.format.KML.LINK_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
 ol.format.KML.whenParser_ = function(node, objectStack) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   goog.asserts.assert(node.localName == 'when');
@@ -1335,6 +1348,35 @@ ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS(
  * @type {Object.<string, Object.<string, ol.xml.Parser>>}
  * @private
  */
+ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'ExtendedData': ol.format.KML.ExtendedDataParser_,
+      'Link': ol.format.KML.LinkParser_,
+      'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
+      'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.KML.LINK_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.KML.NAMESPACE_URIS_, {
+      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
 ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.KML.NAMESPACE_URIS_, {
       'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
@@ -1711,33 +1753,82 @@ ol.format.KML.prototype.readNameFromNode = function(node) {
 
 
 /**
- * Read the projection from a KML source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
+ * @param {Document|Node|string} source Souce.
+ * @return {Array.<Object>} Network links.
+ * @api
  */
-ol.format.KML.prototype.readProjection;
+ol.format.KML.prototype.readNetworkLinks = function(source) {
+  var networkLinks = [];
+  if (ol.xml.isDocument(source)) {
+    goog.array.extend(networkLinks, this.readNetworkLinksFromDocument(
+        /** @type {Document} */ (source)));
+  } else if (ol.xml.isNode(source)) {
+    goog.array.extend(networkLinks, this.readNetworkLinksFromNode(
+        /** @type {Node} */ (source)));
+  } else if (goog.isString(source)) {
+    var doc = ol.xml.parse(source);
+    goog.array.extend(networkLinks, this.readNetworkLinksFromDocument(doc));
+  } else {
+    goog.asserts.fail();
+  }
+  return networkLinks;
+};
 
 
 /**
- * @inheritDoc
+ * @param {Document} doc Document.
+ * @return {Array.<Object>} Network links.
  */
-ol.format.KML.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
+ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) {
+  var n, networkLinks = [];
+  for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      goog.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
+    }
+  }
+  return networkLinks;
 };
 
 
 /**
- * @inheritDoc
+ * @param {Node} node Node.
+ * @return {Array.<Object>} Network links.
  */
-ol.format.KML.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
+ol.format.KML.prototype.readNetworkLinksFromNode = function(node) {
+  var n, networkLinks = [];
+  for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) {
+    if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+        n.localName == 'NetworkLink') {
+      var obj = ol.xml.pushParseAndPop({}, ol.format.KML.NETWORK_LINK_PARSERS_,
+          n, []);
+      networkLinks.push(obj);
+    }
+  }
+  for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) {
+    var localName = ol.xml.getLocalName(n);
+    if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
+        (localName == 'Document' ||
+         localName == 'Folder' ||
+         localName == 'kml')) {
+      goog.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
+    }
+  }
+  return networkLinks;
 };
 
 
 /**
+ * Read the projection from a KML source.
+ *
+ * @function
+ * @param {ArrayBuffer|Document|Node|Object|string} source Source.
+ * @return {ol.proj.Projection} Projection.
+ * @api stable
+ */
+ol.format.KML.prototype.readProjection;
+
+
+/**
  * @param {Node} node Node to append a TextNode with the color to.
  * @param {ol.Color|string} color Color.
  * @private
@@ -1766,8 +1857,8 @@ ol.format.KML.writeCoordinatesTextNode_ =
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
 
-  var layout = goog.object.get(context, 'layout');
-  var stride = goog.object.get(context, 'stride');
+  var layout = context['layout'];
+  var stride = context['stride'];
 
   var dimension;
   if (layout == ol.geom.GeometryLayout.XY ||
@@ -1851,16 +1942,15 @@ ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
   };
 
   if (!goog.isNull(size)) {
-    goog.object.set(iconProperties, 'w', size[0]);
-    goog.object.set(iconProperties, 'h', size[1]);
+    iconProperties['w'] = size[0];
+    iconProperties['h'] = size[1];
     var anchor = style.getAnchor(); // top-left
     var origin = style.getOrigin(); // top-left
 
     if (!goog.isNull(origin) && !goog.isNull(iconImageSize) &&
         origin[0] !== 0 && origin[1] !== size[1]) {
-      goog.object.set(iconProperties, 'x', origin[0]);
-      goog.object.set(iconProperties, 'y',
-          iconImageSize[1] - (origin[1] + size[1]));
+      iconProperties['x'] = origin[0];
+      iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]);
     }
 
     if (!goog.isNull(anchor) &&
@@ -1871,20 +1961,20 @@ ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
         y: size[1] - anchor[1],
         yunits: ol.style.IconAnchorUnits.PIXELS
       };
-      goog.object.set(properties, 'hotSpot', hotSpot);
+      properties['hotSpot'] = hotSpot;
     }
   }
 
-  goog.object.set(properties, 'Icon', iconProperties);
+  properties['Icon'] = iconProperties;
 
   var scale = style.getScale();
   if (scale !== 1) {
-    goog.object.set(properties, 'scale', scale);
+    properties['scale'] = scale;
   }
 
   var rotation = style.getRotation();
   if (rotation !== 0) {
-    goog.object.set(properties, 'heading', rotation); // 0-360
+    properties['heading'] = rotation; // 0-360
   }
 
   var parentNode = objectStack[objectStack.length - 1].node;
@@ -1906,11 +1996,11 @@ ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) {
   var properties = {};
   var fill = style.getFill();
   if (!goog.isNull(fill)) {
-    goog.object.set(properties, 'color', fill.getColor());
+    properties['color'] = fill.getColor();
   }
   var scale = style.getScale();
   if (goog.isDef(scale) && scale !== 1) {
-    goog.object.set(properties, 'scale', scale);
+    properties['scale'] = scale;
   }
   var parentNode = objectStack[objectStack.length - 1].node;
   var orderedKeys =
@@ -2019,10 +2109,10 @@ ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
     // resolution-independent here
     var styles = styleFunction.call(feature, 0);
     if (!goog.isNull(styles) && styles.length > 0) {
-      goog.object.set(properties, 'Style', styles[0]);
+      properties['Style'] = styles[0];
       var textStyle = styles[0].getText();
       if (!goog.isNull(textStyle)) {
-        goog.object.set(properties, 'name', textStyle.getText());
+        properties['name'] = textStyle.getText();
       }
     }
   }
@@ -2057,8 +2147,8 @@ ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) {
       (geometry instanceof ol.geom.LinearRing));
   var flatCoordinates = geometry.getFlatCoordinates();
   var /** @type {ol.xml.NodeStackItem} */ context = {node: node};
-  goog.object.set(context, 'layout', geometry.getLayout());
-  goog.object.set(context, 'stride', geometry.getStride());
+  context['layout'] = geometry.getLayout();
+  context['stride'] = geometry.getStride();
   ol.xml.pushSerializeAndPop(context,
       ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_,
       ol.format.KML.COORDINATES_NODE_FACTORY_,
@@ -2128,16 +2218,16 @@ ol.format.KML.writeStyle_ = function(node, style, objectStack) {
   var imageStyle = style.getImage();
   var textStyle = style.getText();
   if (!goog.isNull(imageStyle)) {
-    goog.object.set(properties, 'IconStyle', imageStyle);
+    properties['IconStyle'] = imageStyle;
   }
   if (!goog.isNull(textStyle)) {
-    goog.object.set(properties, 'LabelStyle', textStyle);
+    properties['LabelStyle'] = textStyle;
   }
   if (!goog.isNull(strokeStyle)) {
-    goog.object.set(properties, 'LineStyle', strokeStyle);
+    properties['LineStyle'] = strokeStyle;
   }
   if (!goog.isNull(fillStyle)) {
-    goog.object.set(properties, 'PolyStyle', fillStyle);
+    properties['PolyStyle'] = fillStyle;
   }
   var parentNode = objectStack[objectStack.length - 1].node;
   var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI];
@@ -2608,9 +2698,9 @@ ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) {
   var /** @type {ol.xml.NodeStackItem} */ context = {node: kml};
   var properties = {};
   if (features.length > 1) {
-    goog.object.set(properties, 'Document', features);
+    properties['Document'] = features;
   } else if (features.length == 1) {
-    goog.object.set(properties, 'Placemark', features[0]);
+    properties['Placemark'] = features[0];
   }
   var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI];
   var values = ol.xml.makeSequence(properties, orderedKeys);
diff --git a/src/ol/format/osmxmlformat.js b/src/ol/format/osmxmlformat.js
index ebc332d..7f7d795 100644
--- a/src/ol/format/osmxmlformat.js
+++ b/src/ol/format/osmxmlformat.js
@@ -67,7 +67,7 @@ ol.format.OSMXML.readNode_ = function(node, objectStack) {
     parseFloat(node.getAttribute('lon')),
     parseFloat(node.getAttribute('lat'))
   ]);
-  goog.object.set(state.nodes, id, coordinates);
+  state.nodes[id] = coordinates;
 
   var values = ol.xml.pushParseAndPop({
     tags: {}
@@ -100,7 +100,7 @@ ol.format.OSMXML.readWay_ = function(node, objectStack) {
   var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
   var flatCoordinates = /** @type {Array.<number>} */ ([]);
   for (var i = 0, ii = values.ndrefs.length; i < ii; i++) {
-    var point = goog.object.get(state.nodes, values.ndrefs[i]);
+    var point = state.nodes[values.ndrefs[i]];
     goog.array.extend(flatCoordinates, point);
   }
   var geometry;
@@ -145,7 +145,7 @@ ol.format.OSMXML.readTag_ = function(node, objectStack) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   goog.asserts.assert(node.localName == 'tag');
   var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  goog.object.set(values.tags, node.getAttribute('k'), node.getAttribute('v'));
+  values.tags[node.getAttribute('k')] = node.getAttribute('v');
 };
 
 
@@ -234,19 +234,3 @@ ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) {
  * @api stable
  */
 ol.format.OSMXML.prototype.readProjection;
-
-
-/**
- * @inheritDoc
- */
-ol.format.OSMXML.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.OSMXML.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
-};
diff --git a/src/ol/format/owsformat.js b/src/ol/format/owsformat.js
index 5a4e5d4..8bd7590 100644
--- a/src/ol/format/owsformat.js
+++ b/src/ol/format/owsformat.js
@@ -2,7 +2,6 @@ goog.provide('ol.format.OWS');
 
 goog.require('goog.asserts');
 goog.require('goog.dom.NodeType');
-goog.require('goog.object');
 goog.require('ol.format.XLink');
 goog.require('ol.format.XML');
 goog.require('ol.format.XSD');
@@ -84,20 +83,13 @@ ol.format.OWS.readAllowedValues_ = function(node, objectStack) {
 ol.format.OWS.readConstraint_ = function(node, objectStack) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   goog.asserts.assert(node.localName == 'Constraint');
-  var object = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(object));
   var name = node.getAttribute('name');
-  var value = ol.xml.pushParseAndPop({},
-      ol.format.OWS.CONSTRAINT_PARSERS_, node,
-      objectStack);
-  if (!goog.isDef(value)) {
+  if (!goog.isDef(name)) {
     return undefined;
   }
-  if (!goog.isDef(object.constraints)) {
-    object.constraints = {};
-  }
-  object.constraints[name] = value;
-
+  return ol.xml.pushParseAndPop({'name': name},
+      ol.format.OWS.CONSTRAINT_PARSERS_, node,
+      objectStack);
 };
 
 
@@ -138,22 +130,12 @@ ol.format.OWS.readDcp_ = function(node, objectStack) {
 ol.format.OWS.readGet_ = function(node, objectStack) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   goog.asserts.assert(node.localName == 'Get');
-  var object = objectStack[objectStack.length - 1];
-  var url = ol.format.XLink.readHref(node);
-  goog.asserts.assert(goog.isObject(object));
-  var value = ol.xml.pushParseAndPop({'url': url},
-      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
-  if (!goog.isDef(value)) {
+  var href = ol.format.XLink.readHref(node);
+  if (!goog.isDef(href)) {
     return undefined;
   }
-  var get = goog.object.get(object, 'get');
-  if (!goog.isDef(get)) {
-    goog.object.set(object, 'get', [value]);
-  }else {
-    goog.asserts.assert(goog.isArray(get));
-    get.push(value);
-  }
-
+  return ol.xml.pushParseAndPop({'href': href},
+      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
 };
 
 
@@ -189,7 +171,7 @@ ol.format.OWS.readOperation_ = function(node, objectStack) {
   var object = /** @type {Object} */
       (objectStack[objectStack.length - 1]);
   goog.asserts.assert(goog.isObject(object));
-  goog.object.set(object, name, value);
+  object[name] = value;
 
 };
 
@@ -274,18 +256,12 @@ ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
  * @param {Node} node Node.
  * @param {Array.<*>} objectStack Object stack.
  * @private
- * @return {Object|undefined}
+ * @return {string|undefined}
  */
 ol.format.OWS.readValue_ = function(node, objectStack) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   goog.asserts.assert(node.localName == 'Value');
-  var object = objectStack[objectStack.length - 1];
-  goog.asserts.assert(goog.isObject(object));
-  var key = ol.format.XSD.readString(node);
-  if (!goog.isDef(key)) {
-    return undefined;
-  }
-  goog.object.set(object, key, true);
+  return ol.format.XSD.readString(node);
 };
 
 
@@ -308,14 +284,11 @@ ol.format.OWS.NAMESPACE_URIS_ = [
 ol.format.OWS.PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
       'ServiceIdentification': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceIdentification_,
-          'serviceIdentification'),
+          ol.format.OWS.readServiceIdentification_),
       'ServiceProvider': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceProvider_,
-          'serviceProvider'),
+          ol.format.OWS.readServiceProvider_),
       'OperationsMetadata': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readOperationsMetadata_,
-          'operationsMetadata')
+          ol.format.OWS.readOperationsMetadata_)
     });
 
 
@@ -327,17 +300,14 @@ ol.format.OWS.PARSERS_ = ol.xml.makeParsersNS(
 ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
       'DeliveryPoint': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'deliveryPoint'),
-      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'city'),
+          ol.format.XSD.readString),
+      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
       'AdministrativeArea': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'administrativeArea'),
-      'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'postalCode'),
-      'Country': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'country'),
+          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, 'electronicMailAddress')
+          ol.format.XSD.readString)
     });
 
 
@@ -348,7 +318,7 @@ ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeParsersNS(
  */
 ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'Value': ol.format.OWS.readValue_
+      'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_)
     });
 
 
@@ -360,8 +330,7 @@ ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeParsersNS(
 ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
       'AllowedValues': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readAllowedValues_, 'allowedValues'
-      )
+          ol.format.OWS.readAllowedValues_)
     });
 
 
@@ -372,10 +341,8 @@ ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeParsersNS(
  */
 ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'Phone': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readPhone_, 'phone'),
-      'Address': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readAddress_, 'address')
+      'Phone': ol.xml.makeObjectPropertySetter(ol.format.OWS.readPhone_),
+      'Address': ol.xml.makeObjectPropertySetter(ol.format.OWS.readAddress_)
     });
 
 
@@ -386,8 +353,7 @@ ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeParsersNS(
  */
 ol.format.OWS.DCP_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readHttp_, 'http')
+      'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_)
     });
 
 
@@ -398,7 +364,7 @@ ol.format.OWS.DCP_PARSERS_ = ol.xml.makeParsersNS(
  */
 ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'Get': ol.format.OWS.readGet_,
+      'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_),
       'Post': undefined // TODO
     });
 
@@ -410,8 +376,7 @@ ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeParsersNS(
  */
 ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'DCP': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readDcp_, 'dcp')
+      'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_)
     });
 
 
@@ -433,10 +398,8 @@ ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeParsersNS(
  */
 ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'voice'),
-      'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'facsimile')
+      'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
     });
 
 
@@ -447,7 +410,8 @@ ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeParsersNS(
  */
 ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'Constraint': ol.format.OWS.readConstraint_
+      'Constraint': ol.xml.makeObjectPropertyPusher(
+          ol.format.OWS.readConstraint_)
     });
 
 
@@ -460,11 +424,10 @@ ol.format.OWS.SERVICE_CONTACT_PARSERS_ =
     ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
       'IndividualName': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'individualName'),
-      'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'positionName'),
+          ol.format.XSD.readString),
+      'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
       'ContactInfo': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readContactInfo_, 'contactInfo')
+          ol.format.OWS.readContactInfo_)
     });
 
 
@@ -476,12 +439,10 @@ ol.format.OWS.SERVICE_CONTACT_PARSERS_ =
 ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
     ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'title'),
+      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
       'ServiceTypeVersion': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'serviceTypeVersion'),
-      'ServiceType': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString, 'serviceType')
+          ol.format.XSD.readString),
+      'ServiceType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
     });
 
 
@@ -493,10 +454,8 @@ ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
 ol.format.OWS.SERVICE_PROVIDER_PARSERS_ =
     ol.xml.makeParsersNS(
     ol.format.OWS.NAMESPACE_URIS_, {
-      'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString,
-          'providerName'),
-      'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref,
-          'providerSite'),
+      'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
+      'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref),
       'ServiceContact': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceContact_, 'serviceContact')
+          ol.format.OWS.readServiceContact_)
     });
diff --git a/src/ol/format/polylineformat.js b/src/ol/format/polylineformat.js
index cab6513..d5196d5 100644
--- a/src/ol/format/polylineformat.js
+++ b/src/ol/format/polylineformat.js
@@ -343,14 +343,6 @@ ol.format.Polyline.prototype.readProjection;
 /**
  * @inheritDoc
  */
-ol.format.Polyline.prototype.readProjectionFromText = function(text) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * @inheritDoc
- */
 ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) {
   var geometry = feature.getGeometry();
   if (goog.isDefAndNotNull(geometry)) {
diff --git a/src/ol/format/textfeatureformat.js b/src/ol/format/textfeatureformat.js
index c838c1a..bdaeaf7 100644
--- a/src/ol/format/textfeatureformat.js
+++ b/src/ol/format/textfeatureformat.js
@@ -111,7 +111,9 @@ ol.format.TextFeature.prototype.readProjection = function(source) {
  * @protected
  * @return {ol.proj.Projection} Projection.
  */
-ol.format.TextFeature.prototype.readProjectionFromText = goog.abstractMethod;
+ol.format.TextFeature.prototype.readProjectionFromText = function(text) {
+  return this.defaultDataProjection;
+};
 
 
 /**
diff --git a/src/ol/format/topojsonformat.js b/src/ol/format/topojsonformat.js
index 2773f2e..03bc9c5 100644
--- a/src/ol/format/topojsonformat.js
+++ b/src/ol/format/topojsonformat.js
@@ -397,7 +397,7 @@ ol.format.TopoJSON.prototype.readProjection = function(object) {
 /**
  * @const
  * @private
- * @type {Object.<string, function(TopoJSONGeometry, Array, ...[Array]): ol.geom.Geometry>}
+ * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>}
  */
 ol.format.TopoJSON.GEOMETRY_READERS_ = {
   'Point': ol.format.TopoJSON.readPointGeometry_,
diff --git a/src/ol/format/wfsformat.js b/src/ol/format/wfsformat.js
index f007bed..540d139 100644
--- a/src/ol/format/wfsformat.js
+++ b/src/ol/format/wfsformat.js
@@ -125,6 +125,9 @@ ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) {
   goog.object.extend(context, this.getReadOptions(node,
       goog.isDef(opt_options) ? opt_options : {}));
   var objectStack = [context];
+  this.gmlFormat_.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][
+      'featureMember'] =
+      ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal);
   var features = ol.xml.pushParseAndPop([],
       this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
       objectStack, this.gmlFormat_);
@@ -220,7 +223,7 @@ ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) {
   var result = {};
   var value = ol.format.XSD.readNonNegativeIntegerString(
       node.getAttribute('numberOfFeatures'));
-  goog.object.set(result, 'numberOfFeatures', value);
+  result['numberOfFeatures'] = value;
   return ol.xml.pushParseAndPop(
       /** @type {ol.format.WFS.FeatureCollectionMetadata} */ (result),
       ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_);
@@ -367,8 +370,8 @@ ol.format.WFS.QUERY_SERIALIZERS_ = {
 ol.format.WFS.writeFeature_ = function(node, feature, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featureNS = goog.object.get(context, 'featureNS');
+  var featureType = context['featureType'];
+  var featureNS = context['featureNS'];
   var child = ol.xml.createElementNS(featureNS, featureType);
   node.appendChild(child);
   ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack);
@@ -399,11 +402,11 @@ ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) {
 ol.format.WFS.writeDelete_ = function(node, feature, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featurePrefix = goog.object.get(context, 'featurePrefix');
+  var featureType = context['featureType'];
+  var featurePrefix = context['featurePrefix'];
   featurePrefix = goog.isDef(featurePrefix) ? featurePrefix :
       ol.format.WFS.FEATURE_PREFIX;
-  var featureNS = goog.object.get(context, 'featureNS');
+  var featureNS = context['featureNS'];
   node.setAttribute('typeName', featurePrefix + ':' + featureType);
   ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
       featureNS);
@@ -423,11 +426,11 @@ ol.format.WFS.writeDelete_ = function(node, feature, objectStack) {
 ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var featureType = goog.object.get(context, 'featureType');
-  var featurePrefix = goog.object.get(context, 'featurePrefix');
+  var featureType = context['featureType'];
+  var featurePrefix = context['featurePrefix'];
   featurePrefix = goog.isDef(featurePrefix) ? featurePrefix :
       ol.format.WFS.FEATURE_PREFIX;
-  var featureNS = goog.object.get(context, 'featureNS');
+  var featureNS = context['featureNS'];
   node.setAttribute('typeName', featurePrefix + ':' + featureType);
   ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
       featureNS);
@@ -442,7 +445,7 @@ ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
       }
     }
     ol.xml.pushSerializeAndPop({node: node, srsName:
-          goog.object.get(context, 'srsName')},
+          context['srsName']},
     ol.format.WFS.TRANSACTION_SERIALIZERS_,
     ol.xml.makeSimpleNodeFactory('Property'), values,
     objectStack);
@@ -518,10 +521,10 @@ ol.format.WFS.TRANSACTION_SERIALIZERS_ = {
 ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var featurePrefix = goog.object.get(context, 'featurePrefix');
-  var featureNS = goog.object.get(context, 'featureNS');
-  var propertyNames = goog.object.get(context, 'propertyNames');
-  var srsName = goog.object.get(context, 'srsName');
+  var featurePrefix = context['featurePrefix'];
+  var featureNS = context['featureNS'];
+  var propertyNames = context['propertyNames'];
+  var srsName = context['srsName'];
   var prefix = goog.isDef(featurePrefix) ? featurePrefix + ':' : '';
   node.setAttribute('typeName', prefix + featureType);
   if (goog.isDef(srsName)) {
@@ -537,7 +540,7 @@ ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) {
       ol.format.WFS.QUERY_SERIALIZERS_,
       ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames,
       objectStack);
-  var bbox = goog.object.get(context, 'bbox');
+  var bbox = context['bbox'];
   if (goog.isDef(bbox)) {
     var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
     ol.format.WFS.writeOgcBBOX_(child, bbox, objectStack);
@@ -569,7 +572,7 @@ ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) {
 ol.format.WFS.writeOgcBBOX_ = function(node, bbox, objectStack) {
   var context = objectStack[objectStack.length - 1];
   goog.asserts.assert(goog.isObject(context));
-  var geometryName = goog.object.get(context, 'geometryName');
+  var geometryName = context['geometryName'];
   var bboxNode = ol.xml.createElementNS('http://www.opengis.net/ogc', 'BBOX');
   node.appendChild(bboxNode);
   ol.format.WFS.writeOgcPropertyName_(bboxNode, geometryName, objectStack);
@@ -747,8 +750,10 @@ ol.format.WFS.prototype.readProjectionFromDocument = function(doc) {
 ol.format.WFS.prototype.readProjectionFromNode = function(node) {
   goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
   goog.asserts.assert(node.localName == 'FeatureCollection');
-  node = node.firstElementChild.firstElementChild;
-  if (goog.isDefAndNotNull(node)) {
+
+  if (goog.isDefAndNotNull(node.firstElementChild) &&
+      goog.isDefAndNotNull(node.firstElementChild.firstElementChild)) {
+    node = node.firstElementChild.firstElementChild;
     for (var n = node.firstElementChild; !goog.isNull(n);
         n = n.nextElementSibling) {
       if (!(n.childNodes.length === 0 ||
@@ -760,5 +765,6 @@ ol.format.WFS.prototype.readProjectionFromNode = function(node) {
       }
     }
   }
+
   return null;
 };
diff --git a/src/ol/format/wktformat.js b/src/ol/format/wktformat.js
index 40fc276..97505f0 100644
--- a/src/ol/format/wktformat.js
+++ b/src/ol/format/wktformat.js
@@ -292,14 +292,6 @@ ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) {
 
 
 /**
- * @inheritDoc
- */
-ol.format.WKT.prototype.readProjectionFromText = function(text) {
-  return null;
-};
-
-
-/**
  * Encode a feature as a WKT string.
  *
  * @function
diff --git a/src/ol/format/wmscapabilitiesformat.js b/src/ol/format/wmscapabilitiesformat.js
index a3d9be5..b5062b3 100644
--- a/src/ol/format/wmscapabilitiesformat.js
+++ b/src/ol/format/wmscapabilitiesformat.js
@@ -136,14 +136,14 @@ ol.format.WMSCapabilities.readEXGeographicBoundingBox_ =
   if (!goog.isDef(geographicBoundingBox)) {
     return undefined;
   }
-  var westBoundLongitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'westBoundLongitude'));
-  var southBoundLatitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'southBoundLatitude'));
-  var eastBoundLongitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'eastBoundLongitude'));
-  var northBoundLatitude = /** @type {number|undefined} */ (goog.object.get(
-      geographicBoundingBox, 'northBoundLatitude'));
+  var westBoundLongitude = /** @type {number|undefined} */
+      (geographicBoundingBox['westBoundLongitude']);
+  var southBoundLatitude = /** @type {number|undefined} */
+      (geographicBoundingBox['southBoundLatitude']);
+  var eastBoundLongitude = /** @type {number|undefined} */
+      (geographicBoundingBox['eastBoundLongitude']);
+  var northBoundLatitude = /** @type {number|undefined} */
+      (geographicBoundingBox['northBoundLatitude']);
   if (!goog.isDef(westBoundLongitude) || !goog.isDef(southBoundLatitude) ||
       !goog.isDef(eastBoundLongitude) || !goog.isDef(northBoundLatitude)) {
     return undefined;
@@ -280,64 +280,62 @@ ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) {
   var queryable =
       ol.format.XSD.readBooleanString(node.getAttribute('queryable'));
   if (!goog.isDef(queryable)) {
-    queryable = goog.object.get(parentLayerObject, 'queryable');
+    queryable = parentLayerObject['queryable'];
   }
-  goog.object.set(
-      layerObject, 'queryable', goog.isDef(queryable) ? queryable : false);
+  layerObject['queryable'] = goog.isDef(queryable) ? queryable : false;
 
   var cascaded = ol.format.XSD.readNonNegativeIntegerString(
       node.getAttribute('cascaded'));
   if (!goog.isDef(cascaded)) {
-    cascaded = goog.object.get(parentLayerObject, 'cascaded');
+    cascaded = parentLayerObject['cascaded'];
   }
-  goog.object.set(layerObject, 'cascaded', cascaded);
+  layerObject['cascaded'] = cascaded;
 
   var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
   if (!goog.isDef(opaque)) {
-    opaque = goog.object.get(parentLayerObject, 'opaque');
+    opaque = parentLayerObject['opaque'];
   }
-  goog.object.set(layerObject, 'opaque', goog.isDef(opaque) ? opaque : false);
+  layerObject['opaque'] = goog.isDef(opaque) ? opaque : false;
 
   var noSubsets =
       ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
   if (!goog.isDef(noSubsets)) {
-    noSubsets = goog.object.get(parentLayerObject, 'noSubsets');
+    noSubsets = parentLayerObject['noSubsets'];
   }
-  goog.object.set(
-      layerObject, 'noSubsets', goog.isDef(noSubsets) ? noSubsets : false);
+  layerObject['noSubsets'] = goog.isDef(noSubsets) ? noSubsets : false;
 
   var fixedWidth =
       ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
   if (!goog.isDef(fixedWidth)) {
-    fixedWidth = goog.object.get(parentLayerObject, 'fixedWidth');
+    fixedWidth = parentLayerObject['fixedWidth'];
   }
-  goog.object.set(layerObject, 'fixedWidth', fixedWidth);
+  layerObject['fixedWidth'] = fixedWidth;
 
   var fixedHeight =
       ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
   if (!goog.isDef(fixedHeight)) {
-    fixedHeight = goog.object.get(parentLayerObject, 'fixedHeight');
+    fixedHeight = parentLayerObject['fixedHeight'];
   }
-  goog.object.set(layerObject, 'fixedHeight', fixedHeight);
+  layerObject['fixedHeight'] = fixedHeight;
 
   // See 7.2.4.8
   var addKeys = ['Style', 'CRS', 'AuthorityURL'];
   goog.array.forEach(addKeys, function(key) {
-    var parentValue = goog.object.get(parentLayerObject, key);
+    var parentValue = parentLayerObject[key];
     if (goog.isDef(parentValue)) {
       var childValue = goog.object.setIfUndefined(layerObject, key, []);
       childValue = childValue.concat(parentValue);
-      goog.object.set(layerObject, key, childValue);
+      layerObject[key] = childValue;
     }
   });
 
   var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
     'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'];
   goog.array.forEach(replaceKeys, function(key) {
-    var childValue = goog.object.get(layerObject, key);
+    var childValue = layerObject[key];
     if (!goog.isDef(childValue)) {
-      var parentValue = goog.object.get(parentLayerObject, key);
-      goog.object.set(layerObject, key, parentValue);
+      var parentValue = parentLayerObject[key];
+      layerObject[key] = parentValue;
     }
   });
 
@@ -456,7 +454,7 @@ ol.format.WMSCapabilities.readSizedFormatOnlineresource_ =
       ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')),
       ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height'))
     ];
-    goog.object.set(formatOnlineresource, 'size', size);
+    formatOnlineresource['size'] = size;
     return formatOnlineresource;
   }
   return undefined;
@@ -475,7 +473,7 @@ ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) {
   var authorityObject =
       ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
   if (goog.isDef(authorityObject)) {
-    goog.object.set(authorityObject, 'name', node.getAttribute('name'));
+    authorityObject['name'] = node.getAttribute('name');
     return authorityObject;
   }
   return undefined;
@@ -494,7 +492,7 @@ ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) {
   var metadataObject =
       ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
   if (goog.isDef(metadataObject)) {
-    goog.object.set(metadataObject, 'type', node.getAttribute('type'));
+    metadataObject['type'] = node.getAttribute('type');
     return metadataObject;
   }
   return undefined;
diff --git a/src/ol/format/wmsgetfeatureinfoformat.js b/src/ol/format/wmsgetfeatureinfoformat.js
index 0d92818..e0e51fe 100644
--- a/src/ol/format/wmsgetfeatureinfoformat.js
+++ b/src/ol/format/wmsgetfeatureinfoformat.js
@@ -2,7 +2,6 @@ goog.provide('ol.format.WMSGetFeatureInfo');
 
 goog.require('goog.array');
 goog.require('goog.asserts');
-goog.require('goog.dom');
 goog.require('goog.dom.NodeType');
 goog.require('goog.object');
 goog.require('goog.string');
@@ -90,14 +89,14 @@ ol.format.WMSGetFeatureInfo.prototype.readFeatures_ =
           ol.format.WMSGetFeatureInfo.layerIdentifier_) +
           ol.format.WMSGetFeatureInfo.featureIdentifier_;
 
-      goog.object.set(context, 'featureType', featureType);
-      goog.object.set(context, 'featureNS', this.featureNS_);
+      context['featureType'] = featureType;
+      context['featureNS'] = this.featureNS_;
 
       var parsers = {};
       parsers[featureType] = ol.xml.makeArrayPusher(
           this.gmlFormat_.readFeatureElement, this.gmlFormat_);
       var parsersNS = ol.xml.makeParsersNS(
-          [goog.object.get(context, 'featureNS'), null], parsers);
+          [context['featureNS'], null], parsers);
       layer.namespaceURI = this.featureNS_;
       var layerFeatures = ol.xml.pushParseAndPop(
           [], parsersNS, layer, objectStack, this.gmlFormat_);
diff --git a/src/ol/format/wmtscapabilitiesformat.js b/src/ol/format/wmtscapabilitiesformat.js
new file mode 100644
index 0000000..ccaf9d5
--- /dev/null
+++ b/src/ol/format/wmtscapabilitiesformat.js
@@ -0,0 +1,400 @@
+goog.provide('ol.format.WMTSCapabilities');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('ol.extent');
+goog.require('ol.format.OWS');
+goog.require('ol.format.XLink');
+goog.require('ol.format.XML');
+goog.require('ol.format.XSD');
+goog.require('ol.xml');
+
+
+
+/**
+ * @classdesc
+ * Format for reading WMTS capabilities data.
+ *
+ * @constructor
+ * @extends {ol.format.XML}
+ * @api
+ */
+ol.format.WMTSCapabilities = function() {
+  goog.base(this);
+
+  /**
+   * @type {ol.format.OWS}
+   * @private
+   */
+  this.owsParser_ = new ol.format.OWS();
+};
+goog.inherits(ol.format.WMTSCapabilities, ol.format.XML);
+
+
+/**
+ * Read a WMTS capabilities document.
+ *
+ * @function
+ * @param {Document|Node|string} source The XML source.
+ * @return {Object} An object representing the WMTS capabilities.
+ * @api
+ */
+ol.format.WMTSCapabilities.prototype.read;
+
+
+/**
+ * @param {Document} doc Document.
+ * @return {Object} WMTS Capability object.
+ */
+ol.format.WMTSCapabilities.prototype.readFromDocument = function(doc) {
+  goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT);
+  for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) {
+    if (n.nodeType == goog.dom.NodeType.ELEMENT) {
+      return this.readFromNode(n);
+    }
+  }
+  return null;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @return {Object} WMTS Capability object.
+ */
+ol.format.WMTSCapabilities.prototype.readFromNode = function(node) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Capabilities');
+  this.version = goog.string.trim(node.getAttribute('version'));
+  goog.asserts.assertString(this.version);
+  var WMTSCapabilityObject = this.owsParser_.readFromNode(node);
+  if (!goog.isDef(WMTSCapabilityObject)) {
+    return null;
+  }
+  goog.object.set(WMTSCapabilityObject, 'version', this.version);
+  WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject,
+      ol.format.WMTSCapabilities.PARSERS_, node, []);
+  return goog.isDef(WMTSCapabilityObject) ? WMTSCapabilityObject : null;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Attribution object.
+ */
+ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Contents');
+
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Layers object.
+ */
+ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) {
+  goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+  goog.asserts.assert(node.localName == 'Layer');
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set object.
+ */
+ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Style object.
+ */
+ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) {
+  var style = ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack);
+  if (!goog.isDef(style)) {
+    return undefined;
+  }
+  var isDefault = node.getAttribute('isDefault') === 'true';
+  style['isDefault'] = isDefault;
+  return style;
+
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Tile Matrix Set Link object.
+ */
+ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
+    objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} 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 (goog.isDef(format)) {
+    resource['format'] = format;
+  }
+  if (goog.isDef(template)) {
+    resource['template'] = template;
+  }
+  if (goog.isDef(resourceType)) {
+    resource['resourceType'] = resourceType;
+  }
+  return resource;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} WGS84 BBox object.
+ */
+ol.format.WMTSCapabilities.readWgs84BoundingBox_ = function(node, objectStack) {
+  var coordinates = ol.xml.pushParseAndPop([],
+      ol.format.WMTSCapabilities.WGS84_BBOX_READERS_, node, objectStack);
+  if (coordinates.length != 2) {
+    return undefined;
+  }
+  return ol.extent.boundingExtent(coordinates);
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Legend object.
+ */
+ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
+  var legend = {};
+  legend['format'] = node.getAttribute('format');
+  legend['href'] = ol.format.XLink.readHref(node);
+  return legend;
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} Coordinates object.
+ */
+ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) {
+  var coordinates = ol.format.XSD.readString(node).split(' ');
+  if (!goog.isDef(coordinates) || coordinates.length != 2) {
+    return undefined;
+  }
+  var x = +coordinates[0];
+  var y = +coordinates[1];
+  if (isNaN(x) || isNaN(y)) {
+    return undefined;
+  }
+  return [x, y];
+};
+
+
+/**
+ * @private
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @return {Object|undefined} TileMatrix object.
+ */
+ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
+  return ol.xml.pushParseAndPop({},
+      ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/wmts/1.0'
+];
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<string>}
+ */
+ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
+  null,
+  'http://www.opengis.net/ows/1.1'
+];
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'Contents': ol.xml.makeObjectPropertySetter(
+          ol.format.WMTSCapabilities.readContents_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.CONTENTS_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'Layer': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMTSCapabilities.readLayer_),
+      'TileMatrixSet': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMTSCapabilities.readTileMatrixSet_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'Style': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMTSCapabilities.readStyle_),
+      'Format': ol.xml.makeObjectPropertyPusher(
+          ol.format.XSD.readString),
+      'TileMatrixSetLink': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMTSCapabilities.readTileMatrixSetLink_),
+      'ResourceURL': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMTSCapabilities.readResourceUrl_)
+    }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+      'Title': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'Abstract': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'WGS84BoundingBox': ol.xml.makeObjectPropertySetter(
+          ol.format.WMTSCapabilities.readWgs84BoundingBox_),
+      'Identifier': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.STYLE_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'LegendURL': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMTSCapabilities.readLegendUrl_)
+    }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+      'Title': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'Identifier': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'TileMatrixSet': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.WGS84_BBOX_READERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+      'LowerCorner': ol.xml.makeArrayPusher(
+          ol.format.WMTSCapabilities.readCoordinates_),
+      'UpperCorner': ol.xml.makeArrayPusher(
+          ol.format.WMTSCapabilities.readCoordinates_)
+    });
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.TMS_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'WellKnownScaleSet': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'TileMatrix': ol.xml.makeObjectPropertyPusher(
+          ol.format.WMTSCapabilities.readTileMatrix_)
+    }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+      'SupportedCRS': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString),
+      'Identifier': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    }));
+
+
+/**
+ * @const
+ * @type {Object.<string, Object.<string, ol.xml.Parser>>}
+ * @private
+ */
+ol.format.WMTSCapabilities.TM_PARSERS_ = ol.xml.makeParsersNS(
+    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
+      'TopLeftCorner': ol.xml.makeObjectPropertySetter(
+          ol.format.WMTSCapabilities.readCoordinates_),
+      'ScaleDenominator': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readDecimal),
+      'TileWidth': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'TileHeight': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'MatrixWidth': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger),
+      'MatrixHeight': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readNonNegativeInteger)
+    }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
+      'Identifier': ol.xml.makeObjectPropertySetter(
+          ol.format.XSD.readString)
+    }));
diff --git a/src/ol/format/xmlfeatureformat.js b/src/ol/format/xmlfeatureformat.js
index 5ac7ba1..48a08db 100644
--- a/src/ol/format/xmlfeatureformat.js
+++ b/src/ol/format/xmlfeatureformat.js
@@ -185,7 +185,9 @@ ol.format.XMLFeature.prototype.readProjection = function(source) {
  * @protected
  * @return {ol.proj.Projection} Projection.
  */
-ol.format.XMLFeature.prototype.readProjectionFromDocument = goog.abstractMethod;
+ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) {
+  return this.defaultDataProjection;
+};
 
 
 /**
@@ -193,7 +195,9 @@ ol.format.XMLFeature.prototype.readProjectionFromDocument = goog.abstractMethod;
  * @protected
  * @return {ol.proj.Projection} Projection.
  */
-ol.format.XMLFeature.prototype.readProjectionFromNode = goog.abstractMethod;
+ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) {
+  return this.defaultDataProjection;
+};
 
 
 /**
diff --git a/src/ol/geom/circle.js b/src/ol/geom/circle.js
index cc32031..e654472 100644
--- a/src/ol/geom/circle.js
+++ b/src/ol/geom/circle.js
@@ -94,20 +94,14 @@ ol.geom.Circle.prototype.getCenter = function() {
 
 /**
  * @inheritDoc
- * @api
  */
-ol.geom.Circle.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    var flatCoordinates = this.flatCoordinates;
-    var radius = flatCoordinates[this.stride] - flatCoordinates[0];
-    this.extent = ol.extent.createOrUpdate(
-        flatCoordinates[0] - radius, flatCoordinates[1] - radius,
-        flatCoordinates[0] + radius, flatCoordinates[1] + radius,
-        this.extent);
-    this.extentRevision = this.getRevision();
-  }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
+ol.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);
 };
 
 
diff --git a/src/ol/geom/flat/geodesicflatgeom.js b/src/ol/geom/flat/geodesicflatgeom.js
index 53d3aa8..48403f9 100644
--- a/src/ol/geom/flat/geodesicflatgeom.js
+++ b/src/ol/geom/flat/geodesicflatgeom.js
@@ -37,7 +37,7 @@ ol.geom.flat.geodesic.line_ =
   /** @type {Array.<number>} */
   var fractionStack = [1, 0];
 
-  /** @type {Object.<number, boolean>} */
+  /** @type {Object.<string, boolean>} */
   var fractions = {};
 
   var maxIterations = 1e5;
@@ -52,7 +52,7 @@ ol.geom.flat.geodesic.line_ =
     key = fracA.toString();
     if (!goog.object.containsKey(fractions, key)) {
       flatCoordinates.push(a[0], a[1]);
-      goog.object.set(fractions, key, true);
+      fractions[key] = true;
     }
     // Pop the b coordinate off the stack
     fracB = fractionStack.pop();
@@ -70,7 +70,7 @@ ol.geom.flat.geodesic.line_ =
       flatCoordinates.push(b[0], b[1]);
       key = fracB.toString();
       goog.asserts.assert(!goog.object.containsKey(fractions, key));
-      goog.object.set(fractions, key, true);
+      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.
diff --git a/src/ol/geom/geometry.js b/src/ol/geom/geometry.js
index f2c9ebd..c1ff3da 100644
--- a/src/ol/geom/geometry.js
+++ b/src/ol/geom/geometry.js
@@ -1,9 +1,9 @@
 goog.provide('ol.geom.Geometry');
 goog.provide('ol.geom.GeometryType');
 
-goog.require('goog.asserts');
 goog.require('goog.functions');
 goog.require('ol.Observable');
+goog.require('ol.extent');
 goog.require('ol.proj');
 
 
@@ -59,16 +59,16 @@ ol.geom.Geometry = function() {
   goog.base(this);
 
   /**
-   * @protected
-   * @type {ol.Extent|undefined}
+   * @private
+   * @type {ol.Extent}
    */
-  this.extent = undefined;
+  this.extent_ = ol.extent.createEmpty();
 
   /**
-   * @protected
+   * @private
    * @type {number}
    */
-  this.extentRevision = -1;
+  this.extentRevision_ = -1;
 
   /**
    * @protected
@@ -135,6 +135,14 @@ ol.geom.Geometry.prototype.containsCoordinate = function(coordinate) {
 
 
 /**
+ * @param {ol.Extent} extent Extent.
+ * @protected
+ * @return {ol.Extent} extent Extent.
+ */
+ol.geom.Geometry.prototype.computeExtent = goog.abstractMethod;
+
+
+/**
  * @param {number} x X.
  * @param {number} y Y.
  * @return {boolean} Contains (x, y).
@@ -144,12 +152,17 @@ ol.geom.Geometry.prototype.containsXY = goog.functions.FALSE;
 
 /**
  * Get the extent of the geometry.
- * @function
  * @param {ol.Extent=} opt_extent Extent.
  * @return {ol.Extent} extent Extent.
  * @api stable
  */
-ol.geom.Geometry.prototype.getExtent = goog.abstractMethod;
+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);
+};
 
 
 /**
diff --git a/src/ol/geom/geometrycollection.js b/src/ol/geom/geometrycollection.js
index ca5cb08..6cbe1c2 100644
--- a/src/ol/geom/geometrycollection.js
+++ b/src/ol/geom/geometrycollection.js
@@ -1,7 +1,6 @@
 goog.provide('ol.geom.GeometryCollection');
 
 goog.require('goog.array');
-goog.require('goog.asserts');
 goog.require('goog.events');
 goog.require('goog.events.EventType');
 goog.require('goog.object');
@@ -130,21 +129,14 @@ ol.geom.GeometryCollection.prototype.containsXY = function(x, y) {
 
 /**
  * @inheritDoc
- * @api stable
  */
-ol.geom.GeometryCollection.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    var extent = ol.extent.createOrUpdateEmpty(this.extent);
-    var geometries = this.geometries_;
-    var i, ii;
-    for (i = 0, ii = geometries.length; i < ii; ++i) {
-      ol.extent.extend(extent, geometries[i].getExtent());
-    }
-    this.extent = extent;
-    this.extentRevision = this.getRevision();
+ol.geom.GeometryCollection.prototype.computeExtent = function(extent) {
+  ol.extent.createOrUpdateEmpty(extent);
+  var geometries = this.geometries_;
+  for (var i = 0, ii = geometries.length; i < ii; ++i) {
+    ol.extent.extend(extent, geometries[i].getExtent());
   }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
+  return extent;
 };
 
 
diff --git a/src/ol/geom/linestring.js b/src/ol/geom/linestring.js
index 0d80bca..3477285 100644
--- a/src/ol/geom/linestring.js
+++ b/src/ol/geom/linestring.js
@@ -117,7 +117,7 @@ ol.geom.LineString.prototype.closestPointXY =
  * return the last coordinate.
  *
  * @param {number} m M.
- * @param {boolean=} opt_extrapolate Extrapolate.
+ * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
  * @return {ol.Coordinate} Coordinate.
  * @api stable
  */
diff --git a/src/ol/geom/multilinestring.js b/src/ol/geom/multilinestring.js
index 751636a..b419475 100644
--- a/src/ol/geom/multilinestring.js
+++ b/src/ol/geom/multilinestring.js
@@ -121,8 +121,8 @@ ol.geom.MultiLineString.prototype.closestPointXY =
  * LineStrings.
  *
  * @param {number} m M.
- * @param {boolean=} opt_extrapolate Extrapolate.
- * @param {boolean=} opt_interpolate Interpolate.
+ * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
+ * @param {boolean=} opt_interpolate Interpolate. Default is `false`.
  * @return {ol.Coordinate} Coordinate.
  * @api stable
  */
diff --git a/src/ol/geom/point.js b/src/ol/geom/point.js
index 07ba09a..cbd895a 100644
--- a/src/ol/geom/point.js
+++ b/src/ol/geom/point.js
@@ -1,6 +1,5 @@
 goog.provide('ol.geom.Point');
 
-goog.require('goog.asserts');
 goog.require('ol.extent');
 goog.require('ol.geom.GeometryType');
 goog.require('ol.geom.SimpleGeometry');
@@ -73,14 +72,8 @@ ol.geom.Point.prototype.getCoordinates = function() {
 /**
  * @inheritDoc
  */
-ol.geom.Point.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    this.extent = ol.extent.createOrUpdateFromCoordinate(
-        this.flatCoordinates, this.extent);
-    this.extentRevision = this.getRevision();
-  }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
+ol.geom.Point.prototype.computeExtent = function(extent) {
+  return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
 };
 
 
diff --git a/src/ol/geom/simplegeometry.js b/src/ol/geom/simplegeometry.js
index 7ead9c8..ccd82ea 100644
--- a/src/ol/geom/simplegeometry.js
+++ b/src/ol/geom/simplegeometry.js
@@ -90,17 +90,11 @@ ol.geom.SimpleGeometry.prototype.containsXY = goog.functions.FALSE;
 
 /**
  * @inheritDoc
- * @api stable
  */
-ol.geom.SimpleGeometry.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision != this.getRevision()) {
-    this.extent = ol.extent.createOrUpdateFromFlatCoordinates(
-        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-        this.extent);
-    this.extentRevision = this.getRevision();
-  }
-  goog.asserts.assert(goog.isDef(this.extent));
-  return ol.extent.returnOrUpdate(this.extent, opt_extent);
+ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
+  return ol.extent.createOrUpdateFromFlatCoordinates(
+      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
+      extent);
 };
 
 
diff --git a/src/ol/interaction/dragrotateandzoominteraction.js b/src/ol/interaction/dragrotateandzoominteraction.js
index a39d5e0..4c31e8f 100644
--- a/src/ol/interaction/dragrotateandzoominteraction.js
+++ b/src/ol/interaction/dragrotateandzoominteraction.js
@@ -1,6 +1,5 @@
 goog.provide('ol.interaction.DragRotateAndZoom');
 
-goog.require('goog.asserts');
 goog.require('goog.math.Vec2');
 goog.require('ol');
 goog.require('ol.ViewHint');
@@ -62,8 +61,7 @@ ol.interaction.DragRotateAndZoom = function(opt_options) {
   this.lastScaleDelta_ = 0;
 
 };
-goog.inherits(ol.interaction.DragRotateAndZoom,
-    ol.interaction.Pointer);
+goog.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer);
 
 
 /**
diff --git a/src/ol/interaction/dragrotateinteraction.js b/src/ol/interaction/dragrotateinteraction.js
index d7e3a73..877aa22 100644
--- a/src/ol/interaction/dragrotateinteraction.js
+++ b/src/ol/interaction/dragrotateinteraction.js
@@ -1,6 +1,5 @@
 goog.provide('ol.interaction.DragRotate');
 
-goog.require('goog.asserts');
 goog.require('ol');
 goog.require('ol.ViewHint');
 goog.require('ol.events.ConditionType');
@@ -68,10 +67,10 @@ ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) {
   if (goog.isDef(this.lastAngle_)) {
     var delta = theta - this.lastAngle_;
     var view = map.getView();
-    var viewState = view.getState();
+    var rotation = view.getRotation();
     map.render();
     ol.interaction.Interaction.rotateWithoutConstraints(
-        map, view, viewState.rotation - delta);
+        map, view, rotation - delta);
   }
   this.lastAngle_ = theta;
 };
@@ -91,8 +90,8 @@ ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) {
   var map = mapBrowserEvent.map;
   var view = map.getView();
   view.setHint(ol.ViewHint.INTERACTING, -1);
-  var viewState = view.getState();
-  ol.interaction.Interaction.rotate(map, view, viewState.rotation,
+  var rotation = view.getRotation();
+  ol.interaction.Interaction.rotate(map, view, rotation,
       undefined, ol.DRAGROTATE_ANIMATION_DURATION);
   return false;
 };
diff --git a/src/ol/interaction/interaction.js b/src/ol/interaction/interaction.js
index 3db0a9c..dae4ef1 100644
--- a/src/ol/interaction/interaction.js
+++ b/src/ol/interaction/interaction.js
@@ -3,7 +3,6 @@
 goog.provide('ol.interaction.Interaction');
 goog.provide('ol.interaction.InteractionProperty');
 
-goog.require('goog.asserts');
 goog.require('ol.MapBrowserEvent');
 goog.require('ol.Object');
 goog.require('ol.animation');
diff --git a/src/ol/interaction/pinchrotateinteraction.js b/src/ol/interaction/pinchrotateinteraction.js
index e1aa2fc..08911fc 100644
--- a/src/ol/interaction/pinchrotateinteraction.js
+++ b/src/ol/interaction/pinchrotateinteraction.js
@@ -1,6 +1,7 @@
 goog.provide('ol.interaction.PinchRotate');
 
 goog.require('goog.asserts');
+goog.require('goog.functions');
 goog.require('goog.style');
 goog.require('ol');
 goog.require('ol.Coordinate');
@@ -107,10 +108,10 @@ ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) {
   // rotate
   if (this.rotating_) {
     var view = map.getView();
-    var viewState = view.getState();
+    var rotation = view.getRotation();
     map.render();
     ol.interaction.Interaction.rotateWithoutConstraints(map, view,
-        viewState.rotation + rotationDelta, this.anchor_);
+        rotation + rotationDelta, this.anchor_);
   }
 };
 
@@ -127,10 +128,9 @@ ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) {
     var view = map.getView();
     view.setHint(ol.ViewHint.INTERACTING, -1);
     if (this.rotating_) {
-      var viewState = view.getState();
+      var rotation = view.getRotation();
       ol.interaction.Interaction.rotate(
-          map, view, viewState.rotation, this.anchor_,
-          ol.ROTATE_ANIMATION_DURATION);
+          map, view, rotation, this.anchor_, ol.ROTATE_ANIMATION_DURATION);
     }
     return false;
   } else {
diff --git a/src/ol/interaction/pinchzoominteraction.js b/src/ol/interaction/pinchzoominteraction.js
index dbe9fff..7d200ba 100644
--- a/src/ol/interaction/pinchzoominteraction.js
+++ b/src/ol/interaction/pinchzoominteraction.js
@@ -1,6 +1,7 @@
 goog.provide('ol.interaction.PinchZoom');
 
 goog.require('goog.asserts');
+goog.require('goog.functions');
 goog.require('goog.style');
 goog.require('ol.Coordinate');
 goog.require('ol.ViewHint');
@@ -84,7 +85,7 @@ ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) {
 
   var map = mapBrowserEvent.map;
   var view = map.getView();
-  var viewState = view.getState();
+  var resolution = view.getResolution();
 
   // scale anchor point.
   var viewportPosition = goog.style.getClientPosition(map.getViewport());
@@ -97,7 +98,7 @@ ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) {
   // scale, bypass the resolution constraint
   map.render();
   ol.interaction.Interaction.zoomWithoutConstraints(
-      map, view, viewState.resolution * scaleDelta, this.anchor_);
+      map, view, resolution * scaleDelta, this.anchor_);
 
 };
 
@@ -108,18 +109,17 @@ ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) {
  * @this {ol.interaction.PinchZoom}
  * @private
  */
-ol.interaction.PinchZoom.handleUpEvent_ =
-    function(mapBrowserEvent) {
+ol.interaction.PinchZoom.handleUpEvent_ = function(mapBrowserEvent) {
   if (this.targetPointers.length < 2) {
     var map = mapBrowserEvent.map;
     var view = map.getView();
     view.setHint(ol.ViewHint.INTERACTING, -1);
-    var viewState = view.getState();
+    var resolution = view.getResolution();
     // Zoom to final resolution, with an animation, and provide a
     // direction not to zoom out/in if user was pinching in/out.
     // Direction is > 0 if pinching out, and < 0 if pinching in.
     var direction = this.lastScaleDelta_ - 1;
-    ol.interaction.Interaction.zoom(map, view, viewState.resolution,
+    ol.interaction.Interaction.zoom(map, view, resolution,
         this.anchor_, this.duration_, direction);
     return false;
   } else {
@@ -134,8 +134,7 @@ ol.interaction.PinchZoom.handleUpEvent_ =
  * @this {ol.interaction.PinchZoom}
  * @private
  */
-ol.interaction.PinchZoom.handleDownEvent_ =
-    function(mapBrowserEvent) {
+ol.interaction.PinchZoom.handleDownEvent_ = function(mapBrowserEvent) {
   if (this.targetPointers.length >= 2) {
     var map = mapBrowserEvent.map;
     this.anchor_ = null;
diff --git a/src/ol/interaction/pointerinteraction.js b/src/ol/interaction/pointerinteraction.js
index ba730b2..8df39d5 100644
--- a/src/ol/interaction/pointerinteraction.js
+++ b/src/ol/interaction/pointerinteraction.js
@@ -1,6 +1,5 @@
 goog.provide('ol.interaction.Pointer');
 
-goog.require('goog.asserts');
 goog.require('goog.functions');
 goog.require('goog.object');
 goog.require('ol.MapBrowserEvent.EventType');
diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js
index 2da26fd..9a995a0 100644
--- a/src/ol/interaction/selectinteraction.js
+++ b/src/ol/interaction/selectinteraction.js
@@ -62,6 +62,12 @@ ol.interaction.Select = function(opt_options) {
   this.toggleCondition_ = goog.isDef(options.toggleCondition) ?
       options.toggleCondition : ol.events.condition.shiftKeyOnly;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.multi_ = goog.isDef(options.multi) ? options.multi : false;
+
   var layerFilter;
   if (goog.isDef(options.layers)) {
     if (goog.isFunction(options.layers)) {
@@ -132,34 +138,36 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
   var set = !add && !remove && !toggle;
   var map = mapBrowserEvent.map;
   var features = this.featureOverlay_.getFeatures();
+  var /** @type {Array.<ol.Feature>} */ deselected = [];
+  var /** @type {Array.<ol.Feature>} */ selected = [];
   if (set) {
-    // Replace the currently selected feature(s) with the feature at the pixel,
-    // or clear the selected feature(s) if there is no feature at the pixel.
-    /** @type {ol.Feature|undefined} */
-    var feature = map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
+    // Replace the currently selected feature(s) with the feature(s) at the
+    // pixel, or clear the selected feature(s) if there is no feature at
+    // the pixel.
+    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
         /**
          * @param {ol.Feature} feature Feature.
          * @param {ol.layer.Layer} layer Layer.
          */
         function(feature, layer) {
-          return feature;
+          selected.push(feature);
         }, undefined, this.layerFilter_);
-    if (goog.isDef(feature) &&
+    if (selected.length > 0 &&
         features.getLength() == 1 &&
-        features.item(0) == feature) {
+        features.item(0) == selected[selected.length - 1]) {
       // No change
     } else {
       if (features.getLength() !== 0) {
         features.clear();
       }
-      if (goog.isDef(feature)) {
-        features.push(feature);
+      if (this.multi_) {
+        features.extend(selected);
+      } else if (selected.length > 0) {
+        features.push(selected[selected.length - 1]);
       }
     }
   } else {
     // Modify the currently selected feature(s).
-    var /** @type {Array.<ol.Feature>} */ deselected = [];
-    var /** @type {Array.<ol.Feature>} */ selected = [];
     map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
         /**
          * @param {ol.Feature} feature Feature.
diff --git a/src/ol/layer/heatmaplayer.js b/src/ol/layer/heatmaplayer.js
index a5970a0..e14c9d1 100644
--- a/src/ol/layer/heatmaplayer.js
+++ b/src/ol/layer/heatmaplayer.js
@@ -3,6 +3,7 @@ goog.provide('ol.layer.Heatmap');
 goog.require('goog.asserts');
 goog.require('goog.events');
 goog.require('goog.math');
+goog.require('goog.object');
 goog.require('ol.Object');
 goog.require('ol.dom');
 goog.require('ol.layer.Vector');
@@ -36,7 +37,14 @@ ol.layer.HeatmapLayerProperty = {
 ol.layer.Heatmap = function(opt_options) {
   var options = goog.isDef(opt_options) ? opt_options : {};
 
-  goog.base(this, /** @type {olx.layer.VectorOptions} */ (options));
+  var baseOptions = goog.object.clone(options);
+
+  delete baseOptions.gradient;
+  delete baseOptions.radius;
+  delete baseOptions.blur;
+  delete baseOptions.shadow;
+  delete baseOptions.weight;
+  goog.base(this, /** @type {olx.layer.VectorOptions} */ (baseOptions));
 
   /**
    * @private
diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js
index 6ad78fa..b50d219 100644
--- a/src/ol/layer/layer.js
+++ b/src/ol/layer/layer.js
@@ -1,6 +1,5 @@
 goog.provide('ol.layer.Layer');
 
-goog.require('goog.asserts');
 goog.require('goog.events');
 goog.require('goog.events.EventType');
 goog.require('goog.object');
diff --git a/src/ol/layer/layerbase.js b/src/ol/layer/layerbase.js
index a7722e8..c1aca6f 100644
--- a/src/ol/layer/layerbase.js
+++ b/src/ol/layer/layerbase.js
@@ -87,13 +87,12 @@ goog.inherits(ol.layer.Base, ol.Object);
 
 
 /**
- * @return {number|undefined} The brightness of the layer.
+ * @return {number} The brightness of the layer.
  * @observable
  * @api
  */
 ol.layer.Base.prototype.getBrightness = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.BRIGHTNESS));
+  return /** @type {number} */ (this.get(ol.layer.LayerProperty.BRIGHTNESS));
 };
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -102,13 +101,12 @@ goog.exportProperty(
 
 
 /**
- * @return {number|undefined} The contrast of the layer.
+ * @return {number} The contrast of the layer.
  * @observable
  * @api
  */
 ol.layer.Base.prototype.getContrast = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.CONTRAST));
+  return /** @type {number} */ (this.get(ol.layer.LayerProperty.CONTRAST));
 };
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -117,12 +115,12 @@ goog.exportProperty(
 
 
 /**
- * @return {number|undefined} The hue of the layer.
+ * @return {number} The hue of the layer.
  * @observable
  * @api
  */
 ol.layer.Base.prototype.getHue = function() {
-  return /** @type {number|undefined} */ (this.get(ol.layer.LayerProperty.HUE));
+  return /** @type {number} */ (this.get(ol.layer.LayerProperty.HUE));
 };
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -146,16 +144,16 @@ ol.layer.Base.prototype.getLayerState = function() {
   var minResolution = this.getMinResolution();
   return {
     layer: /** @type {ol.layer.Layer} */ (this),
-    brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0,
-    contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1,
-    hue: goog.isDef(hue) ? hue : 0,
-    opacity: goog.isDef(opacity) ? goog.math.clamp(opacity, 0, 1) : 1,
-    saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1,
+    brightness: goog.math.clamp(brightness, -1, 1),
+    contrast: Math.max(contrast, 0),
+    hue: hue,
+    opacity: goog.math.clamp(opacity, 0, 1),
+    saturation: Math.max(saturation, 0),
     sourceState: sourceState,
-    visible: goog.isDef(visible) ? !!visible : true,
+    visible: visible,
     extent: extent,
-    maxResolution: goog.isDef(maxResolution) ? maxResolution : Infinity,
-    minResolution: goog.isDef(minResolution) ? Math.max(minResolution, 0) : 0
+    maxResolution: maxResolution,
+    minResolution: Math.max(minResolution, 0)
   };
 };
 
@@ -192,12 +190,12 @@ goog.exportProperty(
 
 
 /**
- * @return {number|undefined} The maximum resolution of the layer.
+ * @return {number} The maximum resolution of the layer.
  * @observable
  * @api stable
  */
 ol.layer.Base.prototype.getMaxResolution = function() {
-  return /** @type {number|undefined} */ (
+  return /** @type {number} */ (
       this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
 };
 goog.exportProperty(
@@ -207,12 +205,12 @@ goog.exportProperty(
 
 
 /**
- * @return {number|undefined} The minimum resolution of the layer.
+ * @return {number} The minimum resolution of the layer.
  * @observable
  * @api stable
  */
 ol.layer.Base.prototype.getMinResolution = function() {
-  return /** @type {number|undefined} */ (
+  return /** @type {number} */ (
       this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
 };
 goog.exportProperty(
@@ -222,13 +220,12 @@ goog.exportProperty(
 
 
 /**
- * @return {number|undefined} The opacity of the layer.
+ * @return {number} The opacity of the layer.
  * @observable
  * @api stable
  */
 ol.layer.Base.prototype.getOpacity = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.OPACITY));
+  return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY));
 };
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -237,13 +234,12 @@ goog.exportProperty(
 
 
 /**
- * @return {number|undefined} The saturation of the layer.
+ * @return {number} The saturation of the layer.
  * @observable
  * @api
  */
 ol.layer.Base.prototype.getSaturation = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.LayerProperty.SATURATION));
+  return /** @type {number} */ (this.get(ol.layer.LayerProperty.SATURATION));
 };
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -258,13 +254,12 @@ ol.layer.Base.prototype.getSourceState = goog.abstractMethod;
 
 
 /**
- * @return {boolean|undefined} The visiblity of the layer.
+ * @return {boolean} The visibility of the layer.
  * @observable
  * @api stable
  */
 ol.layer.Base.prototype.getVisible = function() {
-  return /** @type {boolean|undefined} */ (
-      this.get(ol.layer.LayerProperty.VISIBLE));
+  return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
 };
 goog.exportProperty(
     ol.layer.Base.prototype,
@@ -290,7 +285,7 @@ goog.exportProperty(
  * [2] https://github.com/WebKit/webkit/commit/8f4765e569
  * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647
  *
- * @param {number|undefined} brightness The brightness of the layer.
+ * @param {number} brightness The brightness of the layer.
  * @observable
  * @api
  */
@@ -308,7 +303,7 @@ goog.exportProperty(
  * grey.  A value of 1 will leave the contrast unchanged.  Other values are
  * linear multipliers on the effect (and values over 1 are permitted).
  *
- * @param {number|undefined} contrast The contrast of the layer.
+ * @param {number} contrast The contrast of the layer.
  * @observable
  * @api
  */
@@ -324,7 +319,7 @@ goog.exportProperty(
 /**
  * Apply a hue-rotation to the layer.  A value of 0 will leave the hue
  * unchanged.  Other values are radians around the color circle.
- * @param {number|undefined} hue The hue of the layer.
+ * @param {number} hue The hue of the layer.
  * @observable
  * @api
  */
@@ -354,7 +349,7 @@ goog.exportProperty(
 
 
 /**
- * @param {number|undefined} maxResolution The maximum resolution of the layer.
+ * @param {number} maxResolution The maximum resolution of the layer.
  * @observable
  * @api stable
  */
@@ -368,7 +363,7 @@ goog.exportProperty(
 
 
 /**
- * @param {number|undefined} minResolution The minimum resolution of the layer.
+ * @param {number} minResolution The minimum resolution of the layer.
  * @observable
  * @api stable
  */
@@ -382,7 +377,7 @@ goog.exportProperty(
 
 
 /**
- * @param {number|undefined} opacity The opacity of the layer.
+ * @param {number} opacity The opacity of the layer.
  * @observable
  * @api stable
  */
@@ -401,7 +396,7 @@ goog.exportProperty(
  * values are linear multipliers of the effect (and values over 1 are
  * permitted).
  *
- * @param {number|undefined} saturation The saturation of the layer.
+ * @param {number} saturation The saturation of the layer.
  * @observable
  * @api
  */
@@ -415,7 +410,7 @@ goog.exportProperty(
 
 
 /**
- * @param {boolean|undefined} visible The visiblity of the layer.
+ * @param {boolean} visible The visibility of the layer.
  * @observable
  * @api stable
  */
diff --git a/src/ol/layer/layergroup.js b/src/ol/layer/layergroup.js
index dcb420f..b973710 100644
--- a/src/ol/layer/layergroup.js
+++ b/src/ol/layer/layergroup.js
@@ -58,7 +58,7 @@ ol.layer.Group = function(opt_options) {
 
   if (goog.isDefAndNotNull(layers)) {
     if (goog.isArray(layers)) {
-      layers = new ol.Collection(goog.array.clone(layers));
+      layers = new ol.Collection(layers.slice());
     } else {
       goog.asserts.assertInstanceof(layers, ol.Collection);
       layers = layers;
diff --git a/src/ol/layer/tilelayer.js b/src/ol/layer/tilelayer.js
index 9aa70cf..394fd1a 100644
--- a/src/ol/layer/tilelayer.js
+++ b/src/ol/layer/tilelayer.js
@@ -1,5 +1,6 @@
 goog.provide('ol.layer.Tile');
 
+goog.require('goog.object');
 goog.require('ol.layer.Layer');
 
 
@@ -29,19 +30,27 @@ ol.layer.TileProperty = {
  */
 ol.layer.Tile = function(opt_options) {
   var options = goog.isDef(opt_options) ? opt_options : {};
-  goog.base(this,  /** @type {olx.layer.LayerOptions} */ (options));
+
+  var baseOptions = goog.object.clone(options);
+
+  delete baseOptions.preload;
+  delete baseOptions.useInterimTilesOnError;
+  goog.base(this,  /** @type {olx.layer.LayerOptions} */ (baseOptions));
+
+  this.setPreload(goog.isDef(options.preload) ? options.preload : 0);
+  this.setUseInterimTilesOnError(goog.isDef(options.useInterimTilesOnError) ?
+      options.useInterimTilesOnError : true);
 };
 goog.inherits(ol.layer.Tile, ol.layer.Layer);
 
 
 /**
- * @return {number|undefined} The level to preload tiles up to.
+ * @return {number} The level to preload tiles up to.
  * @observable
  * @api
  */
 ol.layer.Tile.prototype.getPreload = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.layer.TileProperty.PRELOAD));
+  return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
 };
 goog.exportProperty(
     ol.layer.Tile.prototype,
@@ -72,12 +81,12 @@ goog.exportProperty(
 
 
 /**
- * @return {boolean|undefined} Use interim tiles on error.
+ * @return {boolean} Use interim tiles on error.
  * @observable
  * @api
  */
 ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
-  return /** @type {boolean|undefined} */ (
+  return /** @type {boolean} */ (
       this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
 };
 goog.exportProperty(
@@ -87,7 +96,7 @@ goog.exportProperty(
 
 
 /**
- * @param {boolean|undefined} useInterimTilesOnError Use interim tiles on error.
+ * @param {boolean} useInterimTilesOnError Use interim tiles on error.
  * @observable
  * @api
  */
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index 025f76e..90d5e86 100644
--- a/src/ol/layer/vectorlayer.js
+++ b/src/ol/layer/vectorlayer.js
@@ -35,6 +35,8 @@ ol.layer.Vector = function(opt_options) {
   var baseOptions = goog.object.clone(options);
 
   delete baseOptions.style;
+  delete baseOptions.renderBuffer;
+  delete baseOptions.updateWhileAnimating;
   goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
 
   /**
@@ -60,6 +62,13 @@ ol.layer.Vector = function(opt_options) {
 
   this.setStyle(options.style);
 
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.updateWhileAnimating_ = goog.isDef(options.updateWhileAnimating) ?
+      options.updateWhileAnimating : false;
+
 };
 goog.inherits(ol.layer.Vector, ol.layer.Layer);
 
@@ -113,6 +122,15 @@ ol.layer.Vector.prototype.getStyleFunction = function() {
 
 
 /**
+ * @return {boolean} Whether the rendered layer should be updated while
+ *     animating.
+ */
+ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
+  return this.updateWhileAnimating_;
+};
+
+
+/**
  * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
  *     Render order.
  */
diff --git a/src/ol/map.js b/src/ol/map.js
index 19d25f7..ec1e7e3 100644
--- a/src/ol/map.js
+++ b/src/ol/map.js
@@ -5,7 +5,6 @@
 goog.provide('ol.Map');
 goog.provide('ol.MapProperty');
 
-goog.require('goog.Uri.QueryData');
 goog.require('goog.array');
 goog.require('goog.asserts');
 goog.require('goog.async.AnimationDelay');
@@ -22,6 +21,7 @@ goog.require('goog.events.KeyHandler');
 goog.require('goog.events.KeyHandler.EventType');
 goog.require('goog.events.MouseWheelHandler');
 goog.require('goog.events.MouseWheelHandler.EventType');
+goog.require('goog.functions');
 goog.require('goog.log');
 goog.require('goog.log.Level');
 goog.require('goog.object');
@@ -166,6 +166,21 @@ ol.Map = function(options) {
   var optionsInternal = ol.Map.createOptionsInternal(options);
 
   /**
+   * @type {boolean}
+   * @private
+   */
+  this.loadTilesWhileAnimating_ = goog.isDef(options.loadTilesWhileAnimating) ?
+      options.loadTilesWhileAnimating : false;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.loadTilesWhileInteracting_ =
+      goog.isDef(options.loadTilesWhileInteracting) ?
+          options.loadTilesWhileInteracting : false;
+
+  /**
    * @private
    * @type {number}
    */
@@ -303,12 +318,6 @@ ol.Map = function(options) {
   this.controls_ = optionsInternal.controls;
 
   /**
-   * @type {olx.DeviceOptions}
-   * @private
-   */
-  this.deviceOptions_ = optionsInternal.deviceOptions;
-
-  /**
    * @type {ol.Collection.<ol.interaction.Interaction>}
    * @private
    */
@@ -574,13 +583,78 @@ ol.Map.prototype.forEachFeatureAtPixel =
   var layerFilter = goog.isDef(opt_layerFilter) ?
       opt_layerFilter : goog.functions.TRUE;
   var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null;
-  return this.renderer_.forEachFeatureAtPixel(
+  return this.renderer_.forEachFeatureAtCoordinate(
       coordinate, this.frameState_, callback, thisArg,
       layerFilter, thisArg2);
 };
 
 
 /**
+ * Detect layers that have a color value at a pixel on the viewport, and
+ * execute a callback with each matching layer. Layers included in the
+ * detection can be configured through `opt_layerFilter`. Feature overlays will
+ * always be included in the detection.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {function(this: S, ol.layer.Layer): T} callback Layer
+ *     callback. If the detected feature is not on a layer, but on a
+ *     {@link ol.FeatureOverlay}, then the argument to this function will
+ *     be `null`. To stop detection, callback functions can return a truthy
+ *     value.
+ * @param {S=} opt_this Value to use as `this` when executing `callback`.
+ * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
+ *     filter function, only layers which are visible and for which this
+ *     function returns `true` will be tested for features. By default, all
+ *     visible layers will be tested. Feature overlays will always be tested.
+ * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
+ * @return {T|undefined} Callback result, i.e. the return value of last
+ * callback execution, or the first truthy callback return value.
+ * @template S,T,U
+ * @api stable
+ */
+ol.Map.prototype.forEachLayerAtPixel =
+    function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
+  if (goog.isNull(this.frameState_)) {
+    return;
+  }
+  var thisArg = goog.isDef(opt_this) ? opt_this : null;
+  var layerFilter = goog.isDef(opt_layerFilter) ?
+      opt_layerFilter : goog.functions.TRUE;
+  var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null;
+  return this.renderer_.forEachLayerAtPixel(
+      pixel, this.frameState_, callback, thisArg,
+      layerFilter, thisArg2);
+};
+
+
+/**
+ * Detect if features intersect a pixel on the viewport. Layers included in the
+ * detection can be configured through `opt_layerFilter`. Feature overlays will
+ * always be included in the detection.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
+ *     filter function, only layers which are visible and for which this
+ *     function returns `true` will be tested for features. By default, all
+ *     visible layers will be tested. Feature overlays will always be tested.
+ * @param {U=} opt_this Value to use as `this` when executing `layerFilter`.
+ * @return {boolean} Is there a feature at the given pixel?
+ * @template U
+ * @api
+ */
+ol.Map.prototype.hasFeatureAtPixel =
+    function(pixel, opt_layerFilter, opt_this) {
+  if (goog.isNull(this.frameState_)) {
+    return false;
+  }
+  var coordinate = this.getCoordinateFromPixel(pixel);
+  var layerFilter = goog.isDef(opt_layerFilter) ?
+      opt_layerFilter : goog.functions.TRUE;
+  var thisArg = goog.isDef(opt_this) ? opt_this : null;
+  return this.renderer_.hasFeatureAtCoordinate(
+      coordinate, this.frameState_, layerFilter, thisArg);
+};
+
+
+/**
  * Returns the geographical coordinate for a browser event.
  * @param {Event} event Event.
  * @return {ol.Coordinate} Coordinate.
@@ -598,19 +672,12 @@ ol.Map.prototype.getEventCoordinate = function(event) {
  * @api stable
  */
 ol.Map.prototype.getEventPixel = function(event) {
-  // Use the offsetX and offsetY values if available.
-  // See http://www.w3.org/TR/cssom-view/#dom-mouseevent-offsetx and
-  // http://www.w3.org/TR/cssom-view/#dom-mouseevent-offsety
-  if (goog.isDef(event.offsetX) && goog.isDef(event.offsetY)) {
-    return [event.offsetX, event.offsetY];
-  } else if (goog.isDef(event.changedTouches)) {
-    // offsetX and offsetY are not defined for Touch Event
-    //
-    // goog.style.getRelativePosition is based on event.targetTouches,
-    // but touchend and touchcancel events have no targetTouches when
-    // the last finger is removed from the screen.
-    // So we ourselves compute the position of touch events.
-    // See https://github.com/google/closure-library/pull/323
+  // goog.style.getRelativePosition is based on event.targetTouches,
+  // but touchend and touchcancel events have no targetTouches when
+  // the last finger is removed from the screen.
+  // So we ourselves compute the position of touch events.
+  // See https://github.com/google/closure-library/pull/323
+  if (goog.isDef(event.changedTouches)) {
     var touch = event.changedTouches[0];
     var viewportPosition = goog.style.getClientPosition(this.viewport_);
     return [
@@ -618,8 +685,6 @@ ol.Map.prototype.getEventPixel = function(event) {
       touch.clientY - viewportPosition.y
     ];
   } else {
-    // Compute offsetX and offsetY values for browsers that don't implement
-    // cssom-view specification
     var eventPosition = goog.style.getRelativePosition(event, this.viewport_);
     return [eventPosition.x, eventPosition.y];
   }
@@ -915,15 +980,12 @@ ol.Map.prototype.handlePostRender = function() {
     var tileSourceCount = 0;
     if (!goog.isNull(frameState)) {
       var hints = frameState.viewHints;
-      var deviceOptions = this.deviceOptions_;
       if (hints[ol.ViewHint.ANIMATING]) {
-        maxTotalLoading = deviceOptions.loadTilesWhileAnimating === false ?
-            0 : 8;
+        maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0;
         maxNewLoads = 2;
       }
       if (hints[ol.ViewHint.INTERACTING]) {
-        maxTotalLoading = deviceOptions.loadTilesWhileInteracting === false ?
-            0 : 8;
+        maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0;
         maxNewLoads = 2;
       }
       tileSourceCount = goog.object.getCount(frameState.wantedTiles);
@@ -1397,7 +1459,6 @@ ol.Map.prototype.unskipFeature = function(feature) {
 
 /**
  * @typedef {{controls: ol.Collection.<ol.control.Control>,
- *            deviceOptions: olx.DeviceOptions,
  *            interactions: ol.Collection.<ol.interaction.Interaction>,
  *            keyboardEventTarget: (Element|Document),
  *            logos: Object,
@@ -1502,7 +1563,7 @@ ol.Map.createOptionsInternal = function(options) {
   var controls;
   if (goog.isDef(options.controls)) {
     if (goog.isArray(options.controls)) {
-      controls = new ol.Collection(goog.array.clone(options.controls));
+      controls = new ol.Collection(options.controls.slice());
     } else {
       goog.asserts.assertInstanceof(options.controls, ol.Collection);
       controls = options.controls;
@@ -1511,13 +1572,10 @@ ol.Map.createOptionsInternal = function(options) {
     controls = ol.control.defaults();
   }
 
-  var deviceOptions = goog.isDef(options.deviceOptions) ?
-      options.deviceOptions : /** @type {olx.DeviceOptions} */ ({});
-
   var interactions;
   if (goog.isDef(options.interactions)) {
     if (goog.isArray(options.interactions)) {
-      interactions = new ol.Collection(goog.array.clone(options.interactions));
+      interactions = new ol.Collection(options.interactions.slice());
     } else {
       goog.asserts.assertInstanceof(options.interactions, ol.Collection);
       interactions = options.interactions;
@@ -1529,7 +1587,7 @@ ol.Map.createOptionsInternal = function(options) {
   var overlays;
   if (goog.isDef(options.overlays)) {
     if (goog.isArray(options.overlays)) {
-      overlays = new ol.Collection(goog.array.clone(options.overlays));
+      overlays = new ol.Collection(options.overlays.slice());
     } else {
       goog.asserts.assertInstanceof(options.overlays, ol.Collection);
       overlays = options.overlays;
@@ -1540,7 +1598,6 @@ ol.Map.createOptionsInternal = function(options) {
 
   return {
     controls: controls,
-    deviceOptions: deviceOptions,
     interactions: interactions,
     keyboardEventTarget: keyboardEventTarget,
     logos: logos,
diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js
index 199b2fa..56b508c 100644
--- a/src/ol/mapbrowserevent.js
+++ b/src/ol/mapbrowserevent.js
@@ -30,9 +30,11 @@ goog.require('ol.pointer.PointerEventHandler');
  * @param {string} type Event type.
  * @param {ol.Map} map Map.
  * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @param {boolean=} opt_dragging Is the map currently being dragged?
  * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) {
+ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging,
+    opt_frameState) {
 
   goog.base(this, type, map, opt_frameState);
 
@@ -61,6 +63,15 @@ ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) {
    */
   this.coordinate = map.getCoordinateFromPixel(this.pixel);
 
+  /**
+   * Indicates if the map is currently being dragged. Only set for
+   * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`.
+   *
+   * @type {boolean}
+   * @api stable
+   */
+  this.dragging = goog.isDef(opt_dragging) ? opt_dragging : false;
+
 };
 goog.inherits(ol.MapBrowserEvent, ol.MapEvent);
 
@@ -96,11 +107,14 @@ ol.MapBrowserEvent.prototype.stopPropagation = function() {
  * @param {string} type Event type.
  * @param {ol.Map} map Map.
  * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @param {boolean=} opt_dragging Is the map currently being dragged?
  * @param {?olx.FrameState=} opt_frameState Frame state.
  */
-ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_frameState) {
+ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging,
+    opt_frameState) {
 
-  goog.base(this, type, map, pointerEvent.browserEvent, opt_frameState);
+  goog.base(this, type, map, pointerEvent.browserEvent, opt_dragging,
+      opt_frameState);
 
   /**
    * @const
@@ -139,7 +153,7 @@ ol.MapBrowserEventHandler = function(map) {
    * @type {boolean}
    * @private
    */
-  this.dragged_ = false;
+  this.dragging_ = false;
 
   /**
    * @type {Array.<number>}
@@ -162,6 +176,8 @@ ol.MapBrowserEventHandler = function(map) {
   }
 
   /**
+   * The most recent "down" type event (or null if none have occurred).
+   * Set on pointerdown.
    * @type {ol.pointer.PointerEvent}
    * @private
    */
@@ -223,16 +239,6 @@ goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);
 
 
 /**
- * Get the last "down" type event. This will be set on pointerdown.
- * @return {ol.pointer.PointerEvent} The most recent "down" type event (or null
- * if none have occurred).
- */
-ol.MapBrowserEventHandler.prototype.getDown = function() {
-  return this.down_;
-};
-
-
-/**
  * @param {goog.events.BrowserEvent} browserEvent Pointer event.
  * @private
  */
@@ -304,22 +310,24 @@ ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
       ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent);
   this.dispatchEvent(newEvent);
 
+  // We emulate click events on left mouse button click, touch contact, and pen
+  // contact. isMouseActionButton returns true in these cases (evt.button is set
+  // to 0).
+  // See http://www.w3.org/TR/pointerevents/#button-states
+  if (!this.dragging_ && this.isMouseActionButton_(pointerEvent)) {
+    goog.asserts.assert(!goog.isNull(this.down_));
+    this.emulateClick_(this.down_);
+  }
+
   goog.asserts.assert(this.activePointers_ >= 0);
   if (this.activePointers_ === 0) {
     goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
     this.dragListenerKeys_ = null;
+    this.dragging_ = false;
+    this.down_ = null;
     goog.dispose(this.documentPointerEventHandler_);
     this.documentPointerEventHandler_ = null;
   }
-
-  // We emulate click event on left mouse button click, touch contact, and pen
-  // contact. isMouseActionButton returns true in these cases (evt.button is set
-  // to 0).
-  // See http://www.w3.org/TR/pointerevents/#button-states
-  if (!this.dragged_ && this.isMouseActionButton_(pointerEvent)) {
-    goog.asserts.assert(!goog.isNull(this.down_));
-    this.emulateClick_(this.down_);
-  }
 };
 
 
@@ -350,7 +358,6 @@ ol.MapBrowserEventHandler.prototype.handlePointerDown_ =
   this.dispatchEvent(newEvent);
 
   this.down_ = pointerEvent;
-  this.dragged_ = false;
 
   if (goog.isNull(this.dragListenerKeys_)) {
     /* Set up a pointer event handler on the `document`,
@@ -399,11 +406,11 @@ ol.MapBrowserEventHandler.prototype.handlePointerMove_ =
   // the exact same coordinates of the pointerdown event. To avoid a
   // 'false' touchmove event to be dispatched , we test if the pointer
   // effectively moved.
-  if (pointerEvent.clientX != this.down_.clientX ||
-      pointerEvent.clientY != this.down_.clientY) {
-    this.dragged_ = true;
+  if (this.isMoving_(pointerEvent)) {
+    this.dragging_ = true;
     var newEvent = new ol.MapBrowserPointerEvent(
-        ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent);
+        ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent,
+        this.dragging_);
     this.dispatchEvent(newEvent);
   }
 
@@ -422,8 +429,20 @@ ol.MapBrowserEventHandler.prototype.handlePointerMove_ =
  * @private
  */
 ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
+  var dragging = !goog.isNull(this.down_) && this.isMoving_(pointerEvent);
   this.dispatchEvent(new ol.MapBrowserPointerEvent(
-      pointerEvent.type, this.map_, pointerEvent));
+      pointerEvent.type, this.map_, pointerEvent, dragging));
+};
+
+
+/**
+ * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
+ * @return {boolean}
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) {
+  return pointerEvent.clientX != this.down_.clientX ||
+      pointerEvent.clientY != this.down_.clientY;
 };
 
 
diff --git a/src/ol/object.js b/src/ol/object.js
index fe5a613..0e2f7ab 100644
--- a/src/ol/object.js
+++ b/src/ol/object.js
@@ -268,7 +268,7 @@ ol.Object.getSetterName = function(key) {
 ol.Object.getKeyValue_ = function(obj, key) {
   var getterName = ol.Object.getGetterName(key);
   var getter = /** @type {function(): *|undefined} */
-      (goog.object.get(obj, getterName));
+      (/** @type {Object} */ (obj)[getterName]);
   return goog.isDef(getter) ? getter.call(obj) : obj.get(key);
 };
 
@@ -284,7 +284,7 @@ ol.Object.getKeyValue_ = function(obj, key) {
 ol.Object.setKeyValue_ = function(obj, key, value) {
   var setterName = ol.Object.getSetterName(key);
   var setter = /** @type {function(*)|undefined} */
-      (goog.object.get(obj, setterName));
+      (/** @type {Object} */ (obj)[setterName]);
   if (goog.isDef(setter)) {
     setter.call(obj, value);
   } else {
diff --git a/src/ol/overlay.js b/src/ol/overlay.js
index cec759b..97026f8 100644
--- a/src/ol/overlay.js
+++ b/src/ol/overlay.js
@@ -348,7 +348,8 @@ goog.exportProperty(
 
 
 /**
- * Set the position for this overlay.
+ * 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
diff --git a/src/ol/pointer/mousesource.js b/src/ol/pointer/mousesource.js
index 9aa1ffa..3e007cf 100644
--- a/src/ol/pointer/mousesource.js
+++ b/src/ol/pointer/mousesource.js
@@ -169,8 +169,7 @@ ol.pointer.MouseSource.prototype.mousedown = function(inEvent) {
       this.cancel(inEvent);
     }
     var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-    goog.object.set(this.pointerMap,
-        ol.pointer.MouseSource.POINTER_ID.toString(), inEvent);
+    this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent;
     this.dispatcher.down(e, inEvent);
   }
 };
@@ -196,8 +195,7 @@ ol.pointer.MouseSource.prototype.mousemove = function(inEvent) {
  */
 ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
   if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var p = goog.object.get(this.pointerMap,
-        ol.pointer.MouseSource.POINTER_ID.toString());
+    var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
 
     if (p && p.button === inEvent.button) {
       var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
diff --git a/src/ol/pointer/mssource.js b/src/ol/pointer/mssource.js
index 4df40d3..bbf17e9 100644
--- a/src/ol/pointer/mssource.js
+++ b/src/ol/pointer/mssource.js
@@ -108,8 +108,7 @@ ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
  * @param {goog.events.BrowserEvent} inEvent
  */
 ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
-  goog.object.set(this.pointerMap,
-      inEvent.getBrowserEvent().pointerId, inEvent);
+  this.pointerMap[inEvent.getBrowserEvent().pointerId] = inEvent;
   var e = this.prepareEvent_(inEvent);
   this.dispatcher.down(e, inEvent);
 };
diff --git a/src/ol/pointer/touchsource.js b/src/ol/pointer/touchsource.js
index 7e3d4c0..575a1e6 100644
--- a/src/ol/pointer/touchsource.js
+++ b/src/ol/pointer/touchsource.js
@@ -312,11 +312,11 @@ ol.pointer.TouchSource.prototype.touchstart = function(inEvent) {
  * @param {Object} inPointer
  */
 ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) {
-  goog.object.set(this.pointerMap, inPointer.pointerId, {
+  this.pointerMap[inPointer.pointerId] = {
     target: inPointer.target,
     out: inPointer,
     outTarget: inPointer.target
-  });
+  };
   this.dispatcher.over(inPointer, browserEvent);
   this.dispatcher.enter(inPointer, browserEvent);
   this.dispatcher.down(inPointer, browserEvent);
@@ -342,7 +342,7 @@ ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
 ol.pointer.TouchSource.prototype.moveOverOut_ =
     function(browserEvent, inPointer) {
   var event = inPointer;
-  var pointer = goog.object.get(this.pointerMap, event.pointerId);
+  var pointer = this.pointerMap[event.pointerId];
   // a finger drifted off the screen, ignore it
   if (!pointer) {
     return;
diff --git a/src/ol/proj/proj.js b/src/ol/proj/proj.js
index 5ceccdf..8ae5f85 100644
--- a/src/ol/proj/proj.js
+++ b/src/ol/proj/proj.js
@@ -196,7 +196,7 @@ ol.proj.Projection.prototype.getAxisOrientation = function() {
 
 /**
  * Is this projection a global projection which spans the whole world?
- * @return {boolean} Wether the projection is global.
+ * @return {boolean} Whether the projection is global.
  * @api stable
  */
 ol.proj.Projection.prototype.isGlobal = function() {
diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js
index 92127da..d8ef6cd 100644
--- a/src/ol/render/canvas/canvasreplay.js
+++ b/src/ol/render/canvas/canvasreplay.js
@@ -62,11 +62,18 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) {
 
   /**
    * @protected
+   * @const
    * @type {ol.Extent}
    */
   this.maxExtent = maxExtent;
 
   /**
+   * @private
+   * @type {ol.Extent}
+   */
+  this.bufferedMaxExtent_ = null;
+
+  /**
    * @protected
    * @type {number}
    */
@@ -74,6 +81,7 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) {
 
   /**
    * @protected
+   * @const
    * @type {number}
    */
   this.resolution = resolution;
@@ -210,12 +218,14 @@ ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) {
  * @param {Object} skippedFeaturesHash Ids of features to skip.
  * @param {Array.<*>} instructions Instructions array.
  * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
+ *     extent.
  * @return {T|undefined} Callback result.
  * @template T
  */
 ol.render.canvas.Replay.prototype.replay_ = function(
     context, pixelRatio, transform, viewRotation, skippedFeaturesHash,
-    instructions, featureCallback) {
+    instructions, featureCallback, opt_hitExtent) {
   /** @type {Array.<number>} */
   var pixelCoordinates;
   if (ol.vec.Mat4.equals2D(transform, this.renderedTransform_)) {
@@ -240,10 +250,13 @@ ol.render.canvas.Replay.prototype.replay_ = function(
       case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
         feature = /** @type {ol.Feature} */ (instruction[1]);
         var featureUid = goog.getUid(feature).toString();
-        if (!goog.isDef(goog.object.get(skippedFeaturesHash, featureUid))) {
-          ++i;
-        } else {
+        if (goog.isDef(skippedFeaturesHash[featureUid])) {
           i = /** @type {number} */ (instruction[2]);
+        } else if (goog.isDef(opt_hitExtent) && !ol.extent.intersects(
+            opt_hitExtent, feature.getGeometry().getExtent())) {
+          i = /** @type {number} */ (instruction[2]);
+        } else {
+          ++i;
         }
         break;
       case ol.render.canvas.Instruction.BEGIN_PATH:
@@ -467,15 +480,17 @@ ol.render.canvas.Replay.prototype.replay = function(
  * @param {number} viewRotation View rotation.
  * @param {Object} skippedFeaturesHash Ids of features to skip
  * @param {function(ol.Feature): T=} opt_featureCallback Feature callback.
+ * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
+ *     extent.
  * @return {T|undefined} Callback result.
  * @template T
  */
 ol.render.canvas.Replay.prototype.replayHitDetection = function(
     context, transform, viewRotation, skippedFeaturesHash,
-    opt_featureCallback) {
+    opt_featureCallback, opt_hitExtent) {
   var instructions = this.hitDetectionInstructions;
   return this.replay_(context, 1, transform, viewRotation,
-      skippedFeaturesHash, instructions, opt_featureCallback);
+      skippedFeaturesHash, instructions, opt_featureCallback, opt_hitExtent);
 };
 
 
@@ -957,12 +972,14 @@ ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ =
  * @inheritDoc
  */
 ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() {
-  var extent = this.maxExtent;
-  if (this.maxLineWidth) {
-    extent = ol.extent.buffer(
-        extent, this.resolution * (this.maxLineWidth + 1) / 2);
+  if (goog.isNull(this.bufferedMaxExtent_)) {
+    this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent);
+    if (this.maxLineWidth > 0) {
+      var width = this.resolution * (this.maxLineWidth + 1) / 2;
+      ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
+    }
   }
-  return extent;
+  return this.bufferedMaxExtent_;
 };
 
 
@@ -1109,7 +1126,12 @@ ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle =
   var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
   this.state_.miterLimit = goog.isDef(strokeStyleMiterLimit) ?
       strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-  this.maxLineWidth = Math.max(this.maxLineWidth, this.state_.lineWidth);
+
+  if (this.state_.lineWidth > this.maxLineWidth) {
+    this.maxLineWidth = this.state_.lineWidth;
+    // invalidate the buffered max extent cache
+    this.bufferedMaxExtent_ = null;
+  }
 };
 
 
@@ -1362,12 +1384,14 @@ ol.render.canvas.PolygonReplay.prototype.finish = function() {
  * @inheritDoc
  */
 ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() {
-  var extent = this.maxExtent;
-  if (this.maxLineWidth) {
-    extent = ol.extent.buffer(
-        extent, this.resolution * (this.maxLineWidth + 1) / 2);
+  if (goog.isNull(this.bufferedMaxExtent_)) {
+    this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent);
+    if (this.maxLineWidth > 0) {
+      var width = this.resolution * (this.maxLineWidth + 1) / 2;
+      ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
+    }
   }
-  return extent;
+  return this.bufferedMaxExtent_;
 };
 
 
@@ -1405,7 +1429,12 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle =
     var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
     state.miterLimit = goog.isDef(strokeStyleMiterLimit) ?
         strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-    this.maxLineWidth = Math.max(this.maxLineWidth, state.lineWidth);
+
+    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;
@@ -1783,9 +1812,11 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
  * @param {number} tolerance Tolerance.
  * @param {ol.Extent} maxExtent Max extent.
  * @param {number} resolution Resolution.
+ * @param {number=} opt_renderBuffer Optional rendering buffer.
  * @struct
  */
-ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution) {
+ol.render.canvas.ReplayGroup = function(
+    tolerance, maxExtent, resolution, opt_renderBuffer) {
 
   /**
    * @private
@@ -1807,6 +1838,12 @@ ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution) {
 
   /**
    * @private
+   * @type {number|undefined}
+   */
+  this.renderBuffer_ = opt_renderBuffer;
+
+  /**
+   * @private
    * @type {Object.<string,
    *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
    */
@@ -1843,16 +1880,16 @@ ol.render.canvas.ReplayGroup.prototype.finish = function() {
 
 
 /**
+ * @param {ol.Coordinate} coordinate Coordinate.
  * @param {number} resolution Resolution.
  * @param {number} rotation Rotation.
- * @param {ol.Coordinate} coordinate Coordinate.
  * @param {Object} skippedFeaturesHash Ids of features to skip
  * @param {function(ol.Feature): T} callback Feature callback.
  * @return {T|undefined} Callback result.
  * @template T
  */
-ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function(
-    resolution, rotation, coordinate, skippedFeaturesHash, callback) {
+ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
+    coordinate, resolution, rotation, skippedFeaturesHash, callback) {
 
   var transform = this.hitDetectionTransform_;
   ol.vec.Mat4.makeTransform2D(transform, 0.5, 0.5,
@@ -1862,6 +1899,16 @@ ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function(
   var context = this.hitDetectionContext_;
   context.clearRect(0, 0, 1, 1);
 
+  /**
+   * @type {ol.Extent}
+   */
+  var hitExtent;
+  if (goog.isDef(this.renderBuffer_)) {
+    hitExtent = ol.extent.createEmpty();
+    ol.extent.extendCoordinate(hitExtent, coordinate);
+    ol.extent.buffer(hitExtent, resolution * this.renderBuffer_, hitExtent);
+  }
+
   return this.replayHitDetection_(context, transform, rotation,
       skippedFeaturesHash,
       /**
@@ -1877,7 +1924,7 @@ ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function(
           }
           context.clearRect(0, 0, 1, 1);
         }
-      });
+      }, hitExtent);
 };
 
 
@@ -1926,6 +1973,8 @@ ol.render.canvas.ReplayGroup.prototype.replay = function(
   var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number);
   goog.array.sort(zs);
 
+  // setup clipping so that the parts of over-simplified geometries are not
+  // visible outside the current extent when panning
   var maxExtent = this.maxExtent_;
   var minX = maxExtent[0];
   var minY = maxExtent[1];
@@ -1966,12 +2015,14 @@ ol.render.canvas.ReplayGroup.prototype.replay = function(
  * @param {number} viewRotation View rotation.
  * @param {Object} skippedFeaturesHash Ids of features to skip
  * @param {function(ol.Feature): T} featureCallback Feature callback.
+ * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
+ *     extent.
  * @return {T|undefined} Callback result.
  * @template T
  */
 ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
     context, transform, viewRotation, skippedFeaturesHash,
-    featureCallback) {
+    featureCallback, opt_hitExtent) {
   /** @type {Array.<number>} */
   var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number);
   goog.array.sort(zs, function(a, b) { return b - a; });
@@ -1983,7 +2034,7 @@ ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
       replay = replays[ol.render.REPLAY_ORDER[j]];
       if (goog.isDef(replay)) {
         result = replay.replayHitDetection(context, transform, viewRotation,
-            skippedFeaturesHash, featureCallback);
+            skippedFeaturesHash, featureCallback, opt_hitExtent);
         if (result) {
           return result;
         }
diff --git a/src/ol/render/ireplay.js b/src/ol/render/ireplay.js
index 78e2faf..701b391 100644
--- a/src/ol/render/ireplay.js
+++ b/src/ol/render/ireplay.js
@@ -1,6 +1,5 @@
 goog.provide('ol.render.IReplayGroup');
 
-goog.require('goog.functions');
 goog.require('ol.render.IVectorContext');
 
 
diff --git a/src/ol/render/vector.js b/src/ol/render/vector.js
index e02c7ad..4adafac 100644
--- a/src/ol/render/vector.js
+++ b/src/ol/render/vector.js
@@ -88,18 +88,11 @@ ol.renderer.vector.renderFeature = function(
   var loading = false;
   var imageStyle, imageState;
   imageStyle = style.getImage();
-  if (goog.isNull(imageStyle)) {
-    ol.renderer.vector.renderFeature_(
-        replayGroup, feature, style, squaredTolerance);
-  } else {
+  if (!goog.isNull(imageStyle)) {
     imageState = imageStyle.getImageState();
     if (imageState == ol.style.ImageState.LOADED ||
         imageState == ol.style.ImageState.ERROR) {
       imageStyle.unlistenImageChange(listener, thisArg);
-      if (imageState == ol.style.ImageState.LOADED) {
-        ol.renderer.vector.renderFeature_(
-            replayGroup, feature, style, squaredTolerance);
-      }
     } else {
       if (imageState == ol.style.ImageState.IDLE) {
         imageStyle.load();
@@ -110,6 +103,8 @@ ol.renderer.vector.renderFeature = function(
       loading = true;
     }
   }
+  ol.renderer.vector.renderFeature_(replayGroup, feature, style,
+      squaredTolerance);
   return loading;
 };
 
@@ -254,6 +249,9 @@ ol.renderer.vector.renderPointGeometry_ =
   goog.asserts.assertInstanceof(geometry, ol.geom.Point);
   var imageStyle = style.getImage();
   if (!goog.isNull(imageStyle)) {
+    if (imageStyle.getImageState() != ol.style.ImageState.LOADED) {
+      return;
+    }
     var imageReplay = replayGroup.getReplay(
         style.getZIndex(), ol.render.ReplayType.IMAGE);
     imageReplay.setImageStyle(imageStyle);
@@ -281,6 +279,9 @@ ol.renderer.vector.renderMultiPointGeometry_ =
   goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint);
   var imageStyle = style.getImage();
   if (!goog.isNull(imageStyle)) {
+    if (imageStyle.getImageState() != ol.style.ImageState.LOADED) {
+      return;
+    }
     var imageReplay = replayGroup.getReplay(
         style.getZIndex(), ol.render.ReplayType.IMAGE);
     imageReplay.setImageStyle(imageStyle);
diff --git a/src/ol/render/webgl/webglimmediate.js b/src/ol/render/webgl/webglimmediate.js
index 4c07223..ca3d481 100644
--- a/src/ol/render/webgl/webglimmediate.js
+++ b/src/ol/render/webgl/webglimmediate.js
@@ -2,6 +2,7 @@ goog.provide('ol.render.webgl.Immediate');
 goog.require('goog.array');
 goog.require('goog.object');
 goog.require('ol.extent');
+goog.require('ol.render.IVectorContext');
 goog.require('ol.render.webgl.ReplayGroup');
 
 
diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js
index 6878500..3d1f743 100644
--- a/src/ol/render/webgl/webglreplay.js
+++ b/src/ol/render/webgl/webglreplay.js
@@ -9,10 +9,12 @@ goog.require('goog.vec.Mat4');
 goog.require('ol.color.Matrix');
 goog.require('ol.extent');
 goog.require('ol.render.IReplayGroup');
+goog.require('ol.render.IVectorContext');
 goog.require('ol.render.webgl.imagereplay.shader.Color');
 goog.require('ol.render.webgl.imagereplay.shader.Default');
 goog.require('ol.vec.Mat4');
 goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.Context');
 
 
 
@@ -61,6 +63,12 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
   this.groupIndices_ = [];
 
   /**
+   * @type {Array.<number>}
+   * @private
+   */
+  this.hitDetectionGroupIndices_ = [];
+
+  /**
    * @type {number|undefined}
    * @private
    */
@@ -73,6 +81,12 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
   this.images_ = [];
 
   /**
+   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
+   * @private
+   */
+  this.hitDetectionImages_ = [];
+
+  /**
    * @type {number|undefined}
    * @private
    */
@@ -169,6 +183,12 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
   this.textures_ = [];
 
   /**
+   * @type {Array.<WebGLTexture>}
+   * @private
+   */
+  this.hitDetectionTextures_ = [];
+
+  /**
    * @type {Array.<number>}
    * @private
    */
@@ -181,6 +201,20 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
   this.verticesBuffer_ = null;
 
   /**
+   * Start index per feature (the index).
+   * @type {Array.<number>}
+   * @private
+   */
+  this.startIndices_ = [];
+
+  /**
+   * Start index per feature (the feature).
+   * @type {Array.<ol.Feature>}
+   * @private
+   */
+  this.startIndicesFeature_ = [];
+
+  /**
    * @type {number|undefined}
    * @private
    */
@@ -204,6 +238,7 @@ ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction =
   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()) {
@@ -211,6 +246,9 @@ ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction =
       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);
@@ -377,6 +415,8 @@ ol.render.webgl.ImageReplay.prototype.drawMultiLineStringGeometry =
  */
 ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry =
     function(multiPointGeometry, feature) {
+  this.startIndices_.push(this.indices_.length);
+  this.startIndicesFeature_.push(feature);
   var flatCoordinates = multiPointGeometry.getFlatCoordinates();
   var stride = multiPointGeometry.getStride();
   this.drawCoordinates_(
@@ -396,6 +436,8 @@ ol.render.webgl.ImageReplay.prototype.drawMultiPolygonGeometry =
  */
 ol.render.webgl.ImageReplay.prototype.drawPointGeometry =
     function(pointGeometry, feature) {
+  this.startIndices_.push(this.indices_.length);
+  this.startIndicesFeature_.push(feature);
   var flatCoordinates = pointGeometry.getFlatCoordinates();
   var stride = pointGeometry.getStride();
   this.drawCoordinates_(
@@ -422,7 +464,10 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) {
   var gl = context.getGL();
 
   this.groupIndices_.push(this.indices_.length);
-  goog.asserts.assert(this.images_.length == this.groupIndices_.length);
+  goog.asserts.assert(this.images_.length === this.groupIndices_.length);
+  this.hitDetectionGroupIndices_.push(this.indices_.length);
+  goog.asserts.assert(this.hitDetectionImages_.length ===
+      this.hitDetectionGroupIndices_.length);
 
   // create, bind, and populate the vertices buffer
   this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_);
@@ -438,44 +483,23 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) {
   this.indicesBuffer_ = new ol.webgl.Buffer(indices);
   context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
 
-  goog.asserts.assert(this.textures_.length === 0);
-
   // create textures
-  var texture, image, uid;
   /** @type {Object.<string, WebGLTexture>} */
   var texturePerImage = {};
-  var i;
-  var ii = this.images_.length;
-  for (i = 0; i < ii; ++i) {
-    image = this.images_[i];
 
-    uid = goog.getUid(image).toString();
-    if (goog.object.containsKey(texturePerImage, uid)) {
-      texture = goog.object.get(texturePerImage, uid);
-    } else {
-      texture = gl.createTexture();
-      gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
-      gl.texParameteri(goog.webgl.TEXTURE_2D,
-          goog.webgl.TEXTURE_WRAP_S, goog.webgl.CLAMP_TO_EDGE);
-      gl.texParameteri(goog.webgl.TEXTURE_2D,
-          goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE);
-      gl.texParameteri(goog.webgl.TEXTURE_2D,
-          goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR);
-      gl.texParameteri(goog.webgl.TEXTURE_2D,
-          goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR);
-      gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA,
-          goog.webgl.UNSIGNED_BYTE, image);
-      goog.object.set(texturePerImage, uid, texture);
-    }
-    this.textures_[i] = texture;
-  }
+  this.createTextures_(this.textures_, this.images_, texturePerImage, gl);
+  goog.asserts.assert(this.textures_.length === this.groupIndices_.length);
 
-  goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
+  this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_,
+      texturePerImage, gl);
+  goog.asserts.assert(this.hitDetectionTextures_.length ===
+      this.hitDetectionGroupIndices_.length);
 
   this.anchorX_ = undefined;
   this.anchorY_ = undefined;
   this.height_ = undefined;
   this.images_ = null;
+  this.hitDetectionImages_ = null;
   this.imageHeight_ = undefined;
   this.imageWidth_ = undefined;
   this.indices_ = null;
@@ -491,6 +515,36 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) {
 
 
 /**
+ * @private
+ * @param {Array.<WebGLTexture>} textures Textures.
+ * @param {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} images
+ *    Images.
+ * @param {Object.<string, WebGLTexture>} texturePerImage Texture cache.
+ * @param {WebGLRenderingContext} gl Gl.
+ */
+ol.render.webgl.ImageReplay.prototype.createTextures_ =
+    function(textures, images, texturePerImage, gl) {
+  goog.asserts.assert(textures.length === 0);
+
+  var texture, image, uid, i;
+  var ii = images.length;
+  for (i = 0; i < ii; ++i) {
+    image = images[i];
+
+    uid = goog.getUid(image).toString();
+    if (goog.object.containsKey(texturePerImage, uid)) {
+      texture = texturePerImage[uid];
+    } else {
+      texture = ol.webgl.Context.createTexture(
+          gl, image, goog.webgl.CLAMP_TO_EDGE, goog.webgl.CLAMP_TO_EDGE);
+      texturePerImage[uid] = texture;
+    }
+    textures[i] = texture;
+  }
+};
+
+
+/**
  * @param {ol.webgl.Context} context Context.
  * @param {ol.Coordinate} center Center.
  * @param {number} resolution Resolution.
@@ -503,12 +557,17 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) {
  * @param {number} hue Global hue.
  * @param {number} saturation Global saturation.
  * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
+ * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
+ *  this extent are checked.
  * @return {T|undefined} Callback result.
  * @template T
  */
 ol.render.webgl.ImageReplay.prototype.replay = function(context,
     center, resolution, rotation, size, pixelRatio,
-    opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
+    opacity, brightness, contrast, hue, saturation, skippedFeaturesHash,
+    featureCallback, oneByOne, opt_hitExtent) {
   var gl = context.getGL();
 
   // bind the vertices buffer
@@ -609,17 +668,14 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context,
   }
 
   // draw!
-  goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
-  var i, ii, start;
-  for (i = 0, ii = this.textures_.length, start = 0; i < ii; ++i) {
-    gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]);
-    var end = this.groupIndices_[i];
-    var numItems = end - start;
-    var offsetInBytes = start * (context.hasOESElementIndexUint ? 4 : 2);
-    var elementType = context.hasOESElementIndexUint ?
-        goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT;
-    gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
-    start = end;
+  var result;
+  if (!goog.isDef(featureCallback)) {
+    this.drawReplay_(gl, context, skippedFeaturesHash,
+        this.textures_, this.groupIndices_);
+  } else {
+    // draw feature by feature for the hit-detection
+    result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash,
+        featureCallback, oneByOne, opt_hitExtent);
   }
 
   // disable the vertex attrib arrays
@@ -628,6 +684,231 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context,
   gl.disableVertexAttribArray(locations.a_texCoord);
   gl.disableVertexAttribArray(locations.a_opacity);
   gl.disableVertexAttribArray(locations.a_rotateWithView);
+
+  return result;
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {Array.<WebGLTexture>} textures Textures.
+ * @param {Array.<number>} groupIndices Texture group indices.
+ */
+ol.render.webgl.ImageReplay.prototype.drawReplay_ =
+    function(gl, context, skippedFeaturesHash, textures, groupIndices) {
+  goog.asserts.assert(textures.length === groupIndices.length);
+  var elementType = context.hasOESElementIndexUint ?
+      goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT;
+  var elementSize = context.hasOESElementIndexUint ? 4 : 2;
+
+  if (!goog.object.isEmpty(skippedFeaturesHash)) {
+    this.drawReplaySkipping_(
+        gl, skippedFeaturesHash, textures, groupIndices,
+        elementType, elementSize);
+  } else {
+    var i, ii, start;
+    for (i = 0, ii = textures.length, start = 0; i < ii; ++i) {
+      gl.bindTexture(goog.webgl.TEXTURE_2D, textures[i]);
+      var end = groupIndices[i];
+      this.drawElements_(gl, start, end, elementType, elementSize);
+      start = end;
+    }
+  }
+};
+
+
+/**
+ * Draw the replay while paying attention to skipped features.
+ *
+ * This functions creates groups of features that can be drawn to together,
+ * so that the number of `drawElements` calls is minimized.
+ *
+ * For example given the following texture groups:
+ *
+ *    Group 1: A B C
+ *    Group 2: D [E] F G
+ *
+ * If feature E should be skipped, the following `drawElements` calls will be
+ * made:
+ *
+ *    drawElements with feature A, B and C
+ *    drawElements with feature D
+ *    drawElements with feature F and G
+ *
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {Array.<WebGLTexture>} textures Textures.
+ * @param {Array.<number>} groupIndices Texture group indices.
+ * @param {number} elementType Element type.
+ * @param {number} elementSize Element Size.
+ */
+ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ =
+    function(gl, skippedFeaturesHash, textures, groupIndices,
+    elementType, elementSize) {
+  var featureIndex = 0;
+
+  var i, ii;
+  for (i = 0, ii = textures.length; i < ii; ++i) {
+    gl.bindTexture(goog.webgl.TEXTURE_2D, textures[i]);
+    var groupStart = (i > 0) ? groupIndices[i - 1] : 0;
+    var groupEnd = groupIndices[i];
+
+    var start = groupStart;
+    var end = groupStart;
+    while (featureIndex < this.startIndices_.length &&
+        this.startIndices_[featureIndex] <= groupEnd) {
+      var feature = this.startIndicesFeature_[featureIndex];
+
+      var featureUid = goog.getUid(feature).toString();
+      if (goog.isDef(skippedFeaturesHash[featureUid])) {
+        // feature should be skipped
+        if (start !== end) {
+          // draw the features so far
+          this.drawElements_(gl, start, end, elementType, elementSize);
+        }
+        // continue with the next feature
+        start = (featureIndex === this.startIndices_.length - 1) ?
+            groupEnd : this.startIndices_[featureIndex + 1];
+        end = start;
+      } else {
+        // the feature is not skipped, augment the end index
+        end = (featureIndex === this.startIndices_.length - 1) ?
+            groupEnd : this.startIndices_[featureIndex + 1];
+      }
+      featureIndex++;
+    }
+
+    if (start !== end) {
+      // draw the remaining features (in case there was no skipped feature
+      // in this texture group, all features of a group are drawn together)
+      this.drawElements_(gl, start, end, elementType, elementSize);
+    }
+  }
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {number} start Start index.
+ * @param {number} end End index.
+ * @param {number} elementType Element type.
+ * @param {number} elementSize Element Size.
+ */
+ol.render.webgl.ImageReplay.prototype.drawElements_ = function(
+    gl, start, end, elementType, elementSize) {
+  var numItems = end - start;
+  var offsetInBytes = start * elementSize;
+  gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
+ * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
+ *  this extent are checked.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ =
+    function(gl, context, skippedFeaturesHash, featureCallback, oneByOne,
+    opt_hitExtent) {
+  if (!oneByOne) {
+    // draw all hit-detection features in "once" (by texture group)
+    return this.drawHitDetectionReplayAll_(gl, context,
+        skippedFeaturesHash, featureCallback);
+  } else {
+    // draw hit-detection features one by one
+    return this.drawHitDetectionReplayOneByOne_(gl, context,
+        skippedFeaturesHash, featureCallback, opt_hitExtent);
+  }
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ =
+    function(gl, context, skippedFeaturesHash, featureCallback) {
+  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+  this.drawReplay_(gl, context, skippedFeaturesHash,
+      this.hitDetectionTextures_, this.hitDetectionGroupIndices_);
+
+  var result = featureCallback(null);
+  if (result) {
+    return result;
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @private
+ * @param {WebGLRenderingContext} gl gl.
+ * @param {ol.webgl.Context} context Context.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
+ *  this extent are checked.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ =
+    function(gl, context, skippedFeaturesHash, featureCallback,
+    opt_hitExtent) {
+  goog.asserts.assert(this.hitDetectionTextures_.length ===
+      this.hitDetectionGroupIndices_.length);
+  var elementType = context.hasOESElementIndexUint ?
+      goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT;
+  var elementSize = context.hasOESElementIndexUint ? 4 : 2;
+
+  var i, groupStart, start, end, feature, featureUid;
+  var featureIndex = this.startIndices_.length - 1;
+  for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) {
+    gl.bindTexture(goog.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]);
+    groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0;
+    end = this.hitDetectionGroupIndices_[i];
+
+    // draw all features for this texture group
+    while (featureIndex >= 0 &&
+        this.startIndices_[featureIndex] >= groupStart) {
+      start = this.startIndices_[featureIndex];
+      feature = this.startIndicesFeature_[featureIndex];
+      featureUid = goog.getUid(feature).toString();
+
+      if (!goog.isDef(skippedFeaturesHash[featureUid]) &&
+          (!goog.isDef(opt_hitExtent) || ol.extent.intersects(
+              opt_hitExtent, feature.getGeometry().getExtent()))) {
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        this.drawElements_(gl, start, end, elementType, elementSize);
+
+        var result = featureCallback(feature);
+        if (result) {
+          return result;
+        }
+      }
+
+      end = start;
+      featureIndex--;
+    }
+  }
+  return undefined;
 };
 
 
@@ -647,6 +928,10 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) {
   goog.asserts.assert(!goog.isNull(image));
   var imageSize = imageStyle.getImageSize();
   goog.asserts.assert(!goog.isNull(imageSize));
+  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
+  goog.asserts.assert(!goog.isNull(hitDetectionImage));
+  var hitDetectionImageSize = imageStyle.getHitDetectionImageSize();
+  goog.asserts.assert(!goog.isNull(hitDetectionImageSize));
   var opacity = imageStyle.getOpacity();
   goog.asserts.assert(goog.isDef(opacity));
   var origin = imageStyle.getOrigin();
@@ -660,17 +945,31 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) {
   var scale = imageStyle.getScale();
   goog.asserts.assert(goog.isDef(scale));
 
+  var currentImage;
   if (this.images_.length === 0) {
     this.images_.push(image);
   } else {
-    var currentImage = this.images_[this.images_.length - 1];
+    currentImage = this.images_[this.images_.length - 1];
     if (goog.getUid(currentImage) != goog.getUid(image)) {
       this.groupIndices_.push(this.indices_.length);
-      goog.asserts.assert(this.groupIndices_.length == this.images_.length);
+      goog.asserts.assert(this.groupIndices_.length === this.images_.length);
       this.images_.push(image);
     }
   }
 
+  if (this.hitDetectionImages_.length === 0) {
+    this.hitDetectionImages_.push(hitDetectionImage);
+  } else {
+    currentImage =
+        this.hitDetectionImages_[this.hitDetectionImages_.length - 1];
+    if (goog.getUid(currentImage) != goog.getUid(hitDetectionImage)) {
+      this.hitDetectionGroupIndices_.push(this.indices_.length);
+      goog.asserts.assert(this.hitDetectionGroupIndices_.length ===
+          this.hitDetectionImages_.length);
+      this.hitDetectionImages_.push(hitDetectionImage);
+    }
+  }
+
   this.anchorX_ = anchor[0];
   this.anchorY_ = anchor[1];
   this.height_ = size[1];
@@ -698,9 +997,11 @@ ol.render.webgl.ImageReplay.prototype.setTextStyle = goog.abstractMethod;
  * @implements {ol.render.IReplayGroup}
  * @param {number} tolerance Tolerance.
  * @param {ol.Extent} maxExtent Max extent.
+ * @param {number=} opt_renderBuffer Render buffer.
  * @struct
  */
-ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) {
+ol.render.webgl.ReplayGroup = function(
+    tolerance, maxExtent, opt_renderBuffer) {
 
   /**
    * @type {ol.Extent}
@@ -715,6 +1016,12 @@ ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) {
   this.tolerance_ = tolerance;
 
   /**
+   * @type {number|undefined}
+   * @private
+   */
+  this.renderBuffer_ = opt_renderBuffer;
+
+  /**
    * ImageReplay only is supported at this point.
    * @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>}
    * @private
@@ -788,8 +1095,6 @@ ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
  * @param {number} hue Global hue.
  * @param {number} saturation Global saturation.
  * @param {Object} skippedFeaturesHash Ids of features to skip.
- * @return {T|undefined} Callback result.
- * @template T
  */
 ol.render.webgl.ReplayGroup.prototype.replay = function(context,
     center, resolution, rotation, size, pixelRatio,
@@ -798,9 +1103,48 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context,
   for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) {
     replay = this.replays_[ol.render.REPLAY_ORDER[i]];
     if (goog.isDef(replay)) {
+      replay.replay(context,
+          center, resolution, rotation, size, pixelRatio,
+          opacity, brightness, contrast, hue, saturation, skippedFeaturesHash,
+          undefined, false);
+    }
+  }
+};
+
+
+/**
+ * @private
+ * @param {ol.webgl.Context} context Context.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Size} size Size.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {number} opacity Global opacity.
+ * @param {number} brightness Global brightness.
+ * @param {number} contrast Global contrast.
+ * @param {number} hue Global hue.
+ * @param {number} saturation Global saturation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {function(ol.Feature): T|undefined} featureCallback Feature callback.
+ * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
+ * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
+ *  this extent are checked.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context,
+    center, resolution, rotation, size, pixelRatio,
+    opacity, brightness, contrast, hue, saturation, skippedFeaturesHash,
+    featureCallback, oneByOne, opt_hitExtent) {
+  var i, replay, result;
+  for (i = ol.render.REPLAY_ORDER.length - 1; i >= 0; --i) {
+    replay = this.replays_[ol.render.REPLAY_ORDER[i]];
+    if (goog.isDef(replay)) {
       result = replay.replay(context,
           center, resolution, rotation, size, pixelRatio,
-          opacity, brightness, contrast, hue, saturation, skippedFeaturesHash);
+          opacity, brightness, contrast, hue, saturation,
+          skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent);
       if (result) {
         return result;
       }
@@ -811,6 +1155,108 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context,
 
 
 /**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.webgl.Context} context Context.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Size} size Size.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {number} opacity Global opacity.
+ * @param {number} brightness Global brightness.
+ * @param {number} contrast Global contrast.
+ * @param {number} hue Global hue.
+ * @param {number} saturation Global saturation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @param {function(ol.Feature): T|undefined} callback Feature callback.
+ * @return {T|undefined} Callback result.
+ * @template T
+ */
+ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
+    coordinate, context, center, resolution, rotation, size, pixelRatio,
+    opacity, brightness, contrast, hue, saturation, skippedFeaturesHash,
+    callback) {
+  var gl = context.getGL();
+  gl.bindFramebuffer(
+      gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());
+
+
+  /**
+   * @type {ol.Extent}
+   */
+  var hitExtent;
+  if (goog.isDef(this.renderBuffer_)) {
+    // build an extent around the coordinate, so that only features that
+    // intersect this extent are checked
+    hitExtent = ol.extent.buffer(
+        ol.extent.createOrUpdateFromCoordinate(coordinate),
+        resolution * this.renderBuffer_);
+  }
+
+  return this.replayHitDetection_(context,
+      coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_,
+      pixelRatio, opacity, brightness, contrast, hue, saturation,
+      skippedFeaturesHash,
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {?} Callback result.
+       */
+      function(feature) {
+        var imageData = new Uint8Array(4);
+        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);
+
+        if (imageData[3] > 0) {
+          var result = callback(feature);
+          if (result) {
+            return result;
+          }
+        }
+      }, true, hitExtent);
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {ol.webgl.Context} context Context.
+ * @param {ol.Coordinate} center Center.
+ * @param {number} resolution Resolution.
+ * @param {number} rotation Rotation.
+ * @param {ol.Size} size Size.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {number} opacity Global opacity.
+ * @param {number} brightness Global brightness.
+ * @param {number} contrast Global contrast.
+ * @param {number} hue Global hue.
+ * @param {number} saturation Global saturation.
+ * @param {Object} skippedFeaturesHash Ids of features to skip.
+ * @return {boolean} Is there a feature at the given coordinate?
+ */
+ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function(
+    coordinate, context, center, resolution, rotation, size, pixelRatio,
+    opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
+  var gl = context.getGL();
+  gl.bindFramebuffer(
+      gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());
+
+  var hasFeature = this.replayHitDetection_(context,
+      coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_,
+      pixelRatio, opacity, brightness, contrast, hue, saturation,
+      skippedFeaturesHash,
+      /**
+       * @param {ol.Feature} feature Feature.
+       * @return {boolean} Is there a feature?
+       */
+      function(feature) {
+        var imageData = new Uint8Array(4);
+        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);
+        return imageData[3] > 0;
+      }, false);
+
+  return goog.isDef(hasFeature);
+};
+
+
+/**
  * @const
  * @private
  * @type {Object.<ol.render.ReplayType,
@@ -820,3 +1266,11 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context,
 ol.render.webgl.BATCH_CONSTRUCTORS_ = {
   'Image': ol.render.webgl.ImageReplay
 };
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.<number>}
+ */
+ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1];
diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js
index 6fa2e36..201806b 100644
--- a/src/ol/renderer/canvas/canvasimagelayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js
@@ -1,17 +1,17 @@
 goog.provide('ol.renderer.canvas.ImageLayer');
 
 goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
+goog.require('goog.functions');
 goog.require('goog.vec.Mat4');
 goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
 goog.require('ol.ViewHint');
+goog.require('ol.dom');
 goog.require('ol.extent');
 goog.require('ol.layer.Image');
 goog.require('ol.proj');
 goog.require('ol.renderer.Map');
 goog.require('ol.renderer.canvas.Layer');
+goog.require('ol.source.ImageVector');
 goog.require('ol.vec.Mat4');
 
 
@@ -38,6 +38,18 @@ ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) {
    */
   this.imageTransform_ = goog.vec.Mat4.createNumber();
 
+  /**
+   * @private
+   * @type {?goog.vec.Mat4.Number}
+   */
+  this.imageTransformInv_ = null;
+
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.hitCanvasContext_ = null;
+
 };
 goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer);
 
@@ -45,15 +57,15 @@ goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer);
 /**
  * @inheritDoc
  */
-ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtPixel =
+ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtCoordinate =
     function(coordinate, frameState, callback, thisArg) {
   var layer = this.getLayer();
   var source = layer.getSource();
   var resolution = frameState.viewState.resolution;
   var rotation = frameState.viewState.rotation;
   var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtPixel(
-      resolution, rotation, coordinate, skippedFeatureUids,
+  return source.forEachFeatureAtCoordinate(
+      coordinate, resolution, rotation, skippedFeatureUids,
       /**
        * @param {ol.Feature} feature Feature.
        * @return {?} Callback result.
@@ -67,6 +79,55 @@ ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtPixel =
 /**
  * @inheritDoc
  */
+ol.renderer.canvas.ImageLayer.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg) {
+  if (goog.isNull(this.getImage())) {
+    return undefined;
+  }
+
+  if (this.getLayer().getSource() instanceof ol.source.ImageVector) {
+    // for ImageVector sources use the original hit-detection logic,
+    // so that for example also transparent polygons are detected
+    var coordinate = this.getMap().getCoordinateFromPixel(pixel);
+    var hasFeature = this.forEachFeatureAtCoordinate(
+        coordinate, frameState, goog.functions.TRUE, this);
+
+    if (hasFeature) {
+      return callback.call(thisArg, this.getLayer());
+    } else {
+      return undefined;
+    }
+  } else {
+    // for all other image sources directly check the image
+    if (goog.isNull(this.imageTransformInv_)) {
+      this.imageTransformInv_ = goog.vec.Mat4.createNumber();
+      goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_);
+    }
+
+    var pixelOnCanvas =
+        this.getPixelOnCanvas(pixel, this.imageTransformInv_);
+
+    if (goog.isNull(this.hitCanvasContext_)) {
+      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
+    }
+
+    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
+    this.hitCanvasContext_.drawImage(
+        this.getImage(), pixelOnCanvas[0], pixelOnCanvas[1], 1, 1, 0, 0, 1, 1);
+
+    var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
+    if (imageData[3] > 0) {
+      return callback.call(thisArg, this.getLayer());
+    } else {
+      return undefined;
+    }
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
 ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
   return goog.isNull(this.image_) ?
       null : this.image_.getImage();
@@ -117,12 +178,8 @@ ol.renderer.canvas.ImageLayer.prototype.prepareFrame =
     image = imageSource.getImage(
         renderedExtent, viewResolution, pixelRatio, projection);
     if (!goog.isNull(image)) {
-      var imageState = image.getState();
-      if (imageState == ol.ImageState.IDLE) {
-        goog.events.listenOnce(image, goog.events.EventType.CHANGE,
-            this.handleImageChange, false, this);
-        image.load();
-      } else if (imageState == ol.ImageState.LOADED) {
+      var loaded = this.loadImage(image);
+      if (loaded) {
         this.image_ = image;
       }
     }
@@ -142,6 +199,7 @@ ol.renderer.canvas.ImageLayer.prototype.prepareFrame =
         viewRotation,
         imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
         imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
+    this.imageTransformInv_ = null;
     this.updateAttributions(frameState.attributions, image.getAttributions());
     this.updateLogos(frameState, imageSource);
   }
diff --git a/src/ol/renderer/canvas/canvaslayerrenderer.js b/src/ol/renderer/canvas/canvaslayerrenderer.js
index 74bca84..7e8dc1c 100644
--- a/src/ol/renderer/canvas/canvaslayerrenderer.js
+++ b/src/ol/renderer/canvas/canvaslayerrenderer.js
@@ -52,6 +52,7 @@ ol.renderer.canvas.Layer.prototype.composeFrame =
     var clipped = goog.isDef(extent);
     if (clipped) {
       goog.asserts.assert(goog.isDef(extent));
+      var pixelRatio = frameState.pixelRatio;
       var topLeft = ol.extent.getTopLeft(extent);
       var topRight = ol.extent.getTopRight(extent);
       var bottomRight = ol.extent.getBottomRight(extent);
@@ -68,10 +69,10 @@ ol.renderer.canvas.Layer.prototype.composeFrame =
 
       context.save();
       context.beginPath();
-      context.moveTo(topLeft[0], topLeft[1]);
-      context.lineTo(topRight[0], topRight[1]);
-      context.lineTo(bottomRight[0], bottomRight[1]);
-      context.lineTo(bottomLeft[0], bottomLeft[1]);
+      context.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();
     }
 
@@ -216,6 +217,21 @@ ol.renderer.canvas.Layer.prototype.prepareFrame = goog.abstractMethod;
 
 
 /**
+ * @param {ol.Pixel} pixelOnMap Pixel.
+ * @param {goog.vec.Mat4.Number} imageTransformInv The transformation matrix
+ *        to convert from a map pixel to a canvas pixel.
+ * @return {ol.Pixel}
+ * @protected
+ */
+ol.renderer.canvas.Layer.prototype.getPixelOnCanvas =
+    function(pixelOnMap, imageTransformInv) {
+  var pixelOnCanvas = [0, 0];
+  ol.vec.Mat4.multVec2(imageTransformInv, pixelOnMap, pixelOnCanvas);
+  return pixelOnCanvas;
+};
+
+
+/**
  * @param {ol.Size} size Size.
  * @return {boolean} True when the canvas with the current size does not exceed
  *     the maximum dimensions.
diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js
index 978aa41..f583b6a 100644
--- a/src/ol/renderer/canvas/canvastilelayerrenderer.js
+++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js
@@ -62,6 +62,12 @@ ol.renderer.canvas.TileLayer = function(mapRenderer, tileLayer) {
 
   /**
    * @private
+   * @type {?goog.vec.Mat4.Number}
+   */
+  this.imageTransformInv_ = null;
+
+  /**
+   * @private
    * @type {number}
    */
   this.renderedCanvasZ_ = NaN;
@@ -281,9 +287,6 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame =
       tilesToDrawByZ, getTileIfLoaded);
 
   var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-  if (!goog.isDef(useInterimTilesOnError)) {
-    useInterimTilesOnError = true;
-  }
 
   var tmpExtent = ol.extent.createEmpty();
   var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
@@ -411,6 +414,35 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame =
       viewState.rotation,
       (origin[0] - center[0]) / tilePixelResolution,
       (center[1] - origin[1]) / tilePixelResolution);
+  this.imageTransformInv_ = null;
 
   return true;
 };
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg) {
+  if (goog.isNull(this.context_)) {
+    return undefined;
+  }
+
+  if (goog.isNull(this.imageTransformInv_)) {
+    this.imageTransformInv_ = goog.vec.Mat4.createNumber();
+    goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_);
+  }
+
+  var pixelOnCanvas =
+      this.getPixelOnCanvas(pixel, this.imageTransformInv_);
+
+  var imageData = this.context_.getImageData(
+      pixelOnCanvas[0], pixelOnCanvas[1], 1, 1).data;
+
+  if (imageData[3] > 0) {
+    return callback.call(thisArg, this.getLayer());
+  } else {
+    return undefined;
+  }
+};
diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
index 3fe8912..219288c 100644
--- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
@@ -116,7 +116,7 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame =
 /**
  * @inheritDoc
  */
-ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel =
+ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate =
     function(coordinate, frameState, callback, thisArg) {
   if (goog.isNull(this.replayGroup_)) {
     return undefined;
@@ -126,8 +126,8 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel =
     var layer = this.getLayer();
     /** @type {Object.<string, boolean>} */
     var features = {};
-    return this.replayGroup_.forEachGeometryAtPixel(resolution,
-        rotation, coordinate, frameState.skippedFeatureUids,
+    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate,
+        resolution, rotation, frameState.skippedFeatureUids,
         /**
          * @param {ol.Feature} feature Feature.
          * @return {?} Callback result.
@@ -149,7 +149,7 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel =
  * @param {goog.events.Event} event Image style change event.
  * @private
  */
-ol.renderer.canvas.VectorLayer.prototype.handleImageChange_ =
+ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ =
     function(event) {
   this.renderIfReadyAndVisible();
 };
@@ -169,7 +169,8 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame =
       frameState.attributions, vectorSource.getAttributions());
   this.updateLogos(frameState, vectorSource);
 
-  if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
+  if (!this.dirty_ && (!vectorLayer.getUpdateWhileAnimating() &&
+      frameState.viewHints[ol.ViewHint.ANIMATING] ||
       frameState.viewHints[ol.ViewHint.INTERACTING])) {
     return true;
   }
@@ -207,7 +208,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame =
   var replayGroup =
       new ol.render.canvas.ReplayGroup(
           ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
-          resolution);
+          resolution, vectorLayer.getRenderBuffer());
   vectorSource.loadFeatures(extent, resolution, projection);
   var renderFeature =
       /**
@@ -273,7 +274,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFeature =
     loading = ol.renderer.vector.renderFeature(
         replayGroup, feature, styles[i],
         ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleImageChange_, this) || loading;
+        this.handleStyleImageChange_, this) || loading;
   }
   return loading;
 };
diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js
index f41162b..cce9db4 100644
--- a/src/ol/renderer/dom/domimagelayerrenderer.js
+++ b/src/ol/renderer/dom/domimagelayerrenderer.js
@@ -3,11 +3,8 @@ goog.provide('ol.renderer.dom.ImageLayer');
 goog.require('goog.asserts');
 goog.require('goog.dom');
 goog.require('goog.dom.TagName');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
 goog.require('goog.vec.Mat4');
 goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
 goog.require('ol.ViewHint');
 goog.require('ol.dom');
 goog.require('ol.extent');
@@ -50,15 +47,15 @@ goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer);
 /**
  * @inheritDoc
  */
-ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtPixel =
+ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtCoordinate =
     function(coordinate, frameState, callback, thisArg) {
   var layer = this.getLayer();
   var source = layer.getSource();
   var resolution = frameState.viewState.resolution;
   var rotation = frameState.viewState.rotation;
   var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtPixel(
-      resolution, rotation, coordinate, skippedFeatureUids,
+  return source.forEachFeatureAtCoordinate(
+      coordinate, resolution, rotation, skippedFeatureUids,
       /**
        * @param {ol.Feature} feature Feature.
        * @return {?} Callback result.
@@ -113,12 +110,8 @@ ol.renderer.dom.ImageLayer.prototype.prepareFrame =
     var image_ = imageSource.getImage(renderedExtent, viewResolution,
         frameState.pixelRatio, projection);
     if (!goog.isNull(image_)) {
-      var imageState = image_.getState();
-      if (imageState == ol.ImageState.IDLE) {
-        goog.events.listenOnce(image_, goog.events.EventType.CHANGE,
-            this.handleImageChange, false, this);
-        image_.load();
-      } else if (imageState == ol.ImageState.LOADED) {
+      var loaded = this.loadImage(image_);
+      if (loaded) {
         image = image_;
       }
     }
diff --git a/src/ol/renderer/dom/domlayerrenderer.js b/src/ol/renderer/dom/domlayerrenderer.js
index 072e324..6425ba5 100644
--- a/src/ol/renderer/dom/domlayerrenderer.js
+++ b/src/ol/renderer/dom/domlayerrenderer.js
@@ -1,6 +1,5 @@
 goog.provide('ol.renderer.dom.Layer');
 
-goog.require('goog.dom');
 goog.require('ol.layer.Layer');
 goog.require('ol.renderer.Layer');
 
diff --git a/src/ol/renderer/dom/dommaprenderer.js b/src/ol/renderer/dom/dommaprenderer.js
index 97831d3..0eeda85 100644
--- a/src/ol/renderer/dom/dommaprenderer.js
+++ b/src/ol/renderer/dom/dommaprenderer.js
@@ -224,8 +224,9 @@ ol.renderer.dom.Map.prototype.renderFrame = function(frameState) {
       (map.hasListener(ol.render.EventType.PRECOMPOSE) ||
       map.hasListener(ol.render.EventType.POSTCOMPOSE))) {
     var canvas = this.context_.canvas;
-    canvas.width = frameState.size[0];
-    canvas.height = frameState.size[1];
+    var pixelRatio = frameState.pixelRatio;
+    canvas.width = frameState.size[0] * pixelRatio;
+    canvas.height = frameState.size[1] * pixelRatio;
   }
 
   this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js
index a2fdf6a..ca4b91d 100644
--- a/src/ol/renderer/dom/domtilelayerrenderer.js
+++ b/src/ol/renderer/dom/domtilelayerrenderer.js
@@ -136,9 +136,6 @@ ol.renderer.dom.TileLayer.prototype.prepareFrame =
       tilesToDrawByZ, getTileIfLoaded);
 
   var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-  if (!goog.isDef(useInterimTilesOnError)) {
-    useInterimTilesOnError = true;
-  }
 
   var tmpExtent = ol.extent.createEmpty();
   var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js
index 5d2d802..bcb5ec6 100644
--- a/src/ol/renderer/dom/domvectorlayerrenderer.js
+++ b/src/ol/renderer/dom/domvectorlayerrenderer.js
@@ -83,6 +83,12 @@ ol.renderer.dom.VectorLayer = function(mapRenderer, vectorLayer) {
    */
   this.transform_ = goog.vec.Mat4.createNumber();
 
+  /**
+   * @private
+   * @type {goog.vec.Mat4.Number}
+   */
+  this.elementTransform_ = goog.vec.Mat4.createNumber();
+
 };
 goog.inherits(ol.renderer.dom.VectorLayer, ol.renderer.dom.Layer);
 
@@ -97,22 +103,36 @@ ol.renderer.dom.VectorLayer.prototype.composeFrame =
   goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
 
   var viewState = frameState.viewState;
+  var viewCenter = viewState.center;
   var viewRotation = viewState.rotation;
+  var viewResolution = viewState.resolution;
   var pixelRatio = frameState.pixelRatio;
+  var viewWidth = frameState.size[0];
+  var viewHeight = frameState.size[1];
+  var imageWidth = viewWidth * pixelRatio;
+  var imageHeight = viewHeight * pixelRatio;
 
   var transform = ol.vec.Mat4.makeTransform2D(this.transform_,
-      pixelRatio * frameState.size[0] / 2,
-      pixelRatio * frameState.size[1] / 2,
-      pixelRatio / viewState.resolution,
-      -pixelRatio / viewState.resolution,
-      -viewState.rotation,
-      -viewState.center[0], -viewState.center[1]);
+      pixelRatio * viewWidth / 2,
+      pixelRatio * viewHeight / 2,
+      pixelRatio / viewResolution,
+      -pixelRatio / viewResolution,
+      -viewRotation,
+      -viewCenter[0], -viewCenter[1]);
 
   var context = this.context_;
 
   // Clear the canvas and set the correct size
-  context.canvas.width = frameState.size[0];
-  context.canvas.height = frameState.size[1];
+  context.canvas.width = imageWidth;
+  context.canvas.height = imageHeight;
+
+  var elementTransform = ol.vec.Mat4.makeTransform2D(this.elementTransform_,
+      0, 0,
+      1 / pixelRatio, 1 / pixelRatio,
+      0,
+      -(imageWidth - viewWidth) / 2 * pixelRatio,
+      -(imageHeight - viewHeight) / 2 * pixelRatio);
+  ol.dom.transformElement2D(context.canvas, elementTransform, 6);
 
   this.dispatchEvent_(ol.render.EventType.PRECOMPOSE, frameState, transform);
 
@@ -156,7 +176,7 @@ ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ =
 /**
  * @inheritDoc
  */
-ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel =
+ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtCoordinate =
     function(coordinate, frameState, callback, thisArg) {
   if (goog.isNull(this.replayGroup_)) {
     return undefined;
@@ -166,8 +186,8 @@ ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel =
     var layer = this.getLayer();
     /** @type {Object.<string, boolean>} */
     var features = {};
-    return this.replayGroup_.forEachGeometryAtPixel(resolution,
-        rotation, coordinate, frameState.skippedFeatureUids,
+    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate,
+        resolution, rotation, frameState.skippedFeatureUids,
         /**
          * @param {ol.Feature} feature Feature.
          * @return {?} Callback result.
@@ -189,7 +209,7 @@ ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel =
  * @param {goog.events.Event} event Image style change event.
  * @private
  */
-ol.renderer.dom.VectorLayer.prototype.handleImageChange_ =
+ol.renderer.dom.VectorLayer.prototype.handleStyleImageChange_ =
     function(event) {
   this.renderIfReadyAndVisible();
 };
@@ -209,7 +229,8 @@ ol.renderer.dom.VectorLayer.prototype.prepareFrame =
       frameState.attributions, vectorSource.getAttributions());
   this.updateLogos(frameState, vectorSource);
 
-  if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
+  if (!this.dirty_ && (!vectorLayer.getUpdateWhileAnimating() &&
+      frameState.viewHints[ol.ViewHint.ANIMATING] ||
       frameState.viewHints[ol.ViewHint.INTERACTING])) {
     return true;
   }
@@ -247,7 +268,7 @@ ol.renderer.dom.VectorLayer.prototype.prepareFrame =
   var replayGroup =
       new ol.render.canvas.ReplayGroup(
           ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
-          resolution);
+          resolution, vectorLayer.getRenderBuffer());
   vectorSource.loadFeatures(extent, resolution, projection);
   var renderFeature =
       /**
@@ -313,7 +334,7 @@ ol.renderer.dom.VectorLayer.prototype.renderFeature =
     loading = ol.renderer.vector.renderFeature(
         replayGroup, feature, styles[i],
         ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleImageChange_, this) || loading;
+        this.handleStyleImageChange_, this) || loading;
   }
   return loading;
 };
diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js
index 356561b..98df08d 100644
--- a/src/ol/renderer/layerrenderer.js
+++ b/src/ol/renderer/layerrenderer.js
@@ -2,6 +2,9 @@ goog.provide('ol.renderer.Layer');
 
 goog.require('goog.Disposable');
 goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.functions');
 goog.require('ol.ImageState');
 goog.require('ol.TileRange');
 goog.require('ol.TileState');
@@ -51,7 +54,37 @@ goog.inherits(ol.renderer.Layer, goog.Disposable);
  * @return {T|undefined} Callback result.
  * @template S,T
  */
-ol.renderer.Layer.prototype.forEachFeatureAtPixel = goog.nullFunction;
+ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = goog.nullFunction;
+
+
+/**
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {olx.FrameState} frameState Frame state.
+ * @param {function(this: S, ol.layer.Layer): T} callback Layer callback.
+ * @param {S} thisArg Value to use as `this` when executing `callback`.
+ * @return {T|undefined} Callback result.
+ * @template S,T
+ */
+ol.renderer.Layer.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg) {
+  var coordinate = this.getMap().getCoordinateFromPixel(pixel);
+  var hasFeature = this.forEachFeatureAtCoordinate(
+      coordinate, frameState, goog.functions.TRUE, this);
+
+  if (hasFeature) {
+    return callback.call(thisArg, this.layer_);
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState Frame state.
+ * @return {boolean} Is there a feature at the given coordinate?
+ */
+ol.renderer.Layer.prototype.hasFeatureAtCoordinate = goog.functions.FALSE;
 
 
 /**
@@ -84,9 +117,9 @@ ol.renderer.Layer.prototype.getMapRenderer = function() {
 /**
  * Handle changes in image state.
  * @param {goog.events.Event} event Image change event.
- * @protected
+ * @private
  */
-ol.renderer.Layer.prototype.handleImageChange = function(event) {
+ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
   var image = /** @type {ol.Image} */ (event.target);
   if (image.getState() === ol.ImageState.LOADED) {
     this.renderIfReadyAndVisible();
@@ -95,6 +128,34 @@ ol.renderer.Layer.prototype.handleImageChange = function(event) {
 
 
 /**
+ * Load the image if not already loaded, and register the image change
+ * listener if needed.
+ * @param {ol.ImageBase} image Image.
+ * @return {boolean} `true` if the image is already loaded, `false`
+ *     otherwise.
+ * @protected
+ */
+ol.renderer.Layer.prototype.loadImage = function(image) {
+  var imageState = image.getState();
+  if (imageState != ol.ImageState.LOADED &&
+      imageState != ol.ImageState.ERROR) {
+    // the image is either "idle" or "loading", register the change
+    // listener (a noop if the listener was already registered)
+    goog.asserts.assert(imageState == ol.ImageState.IDLE ||
+        imageState == ol.ImageState.LOADING);
+    goog.events.listenOnce(image, goog.events.EventType.CHANGE,
+        this.handleImageChange_, false, this);
+  }
+  if (imageState == ol.ImageState.IDLE) {
+    image.load();
+    imageState = image.getState();
+    goog.asserts.assert(imageState == ol.ImageState.LOADING);
+  }
+  return imageState == ol.ImageState.LOADED;
+};
+
+
+/**
  * @protected
  */
 ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
@@ -246,8 +307,7 @@ ol.renderer.Layer.prototype.snapCenterToPixel =
  * @param {ol.proj.Projection} projection Projection.
  * @param {ol.Extent} extent Extent.
  * @param {number} currentZ Current Z.
- * @param {number|undefined} preload Load low resolution tiles up to 'preload'
- *     levels.
+ * @param {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
@@ -264,9 +324,6 @@ ol.renderer.Layer.prototype.manageTilePyramid = function(
   var tileQueue = frameState.tileQueue;
   var minZoom = tileGrid.getMinZoom();
   var tile, tileRange, tileResolution, x, y, z;
-  if (!goog.isDef(preload)) {
-    preload = 0;
-  }
   for (z = currentZ; z >= minZoom; --z) {
     tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
     tileResolution = tileGrid.getResolution(z);
diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js
index 7dd9bc0..5397f6a 100644
--- a/src/ol/renderer/maprenderer.js
+++ b/src/ol/renderer/maprenderer.js
@@ -4,6 +4,7 @@ goog.provide('ol.renderer.Map');
 goog.require('goog.Disposable');
 goog.require('goog.asserts');
 goog.require('goog.dispose');
+goog.require('goog.functions');
 goog.require('goog.object');
 goog.require('goog.vec.Mat4');
 goog.require('ol.layer.Layer');
@@ -121,7 +122,7 @@ ol.renderer.Map.expireIconCache_ = function(map, frameState) {
  * @return {T|undefined} Callback result.
  * @template S,T,U
  */
-ol.renderer.Map.prototype.forEachFeatureAtPixel =
+ol.renderer.Map.prototype.forEachFeatureAtCoordinate =
     function(coordinate, frameState, callback, thisArg,
         layerFilter, thisArg2) {
   var result;
@@ -131,8 +132,8 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel =
   if (!goog.isNull(this.replayGroup)) {
     /** @type {Object.<string, boolean>} */
     var features = {};
-    result = this.replayGroup.forEachGeometryAtPixel(viewResolution,
-        viewRotation, coordinate, {},
+    result = this.replayGroup.forEachFeatureAtCoordinate(coordinate,
+        viewResolution, viewRotation, {},
         /**
          * @param {ol.Feature} feature Feature.
          * @return {?} Callback result.
@@ -149,7 +150,7 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel =
       return result;
     }
   }
-  var layerStates = this.map_.getLayerGroup().getLayerStatesArray();
+  var layerStates = frameState.layerStatesArray;
   var numLayers = layerStates.length;
   var i;
   for (i = numLayers - 1; i >= 0; --i) {
@@ -158,7 +159,7 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel =
     if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
         layerFilter.call(thisArg2, layer)) {
       var layerRenderer = this.getLayerRenderer(layer);
-      result = layerRenderer.forEachFeatureAtPixel(
+      result = layerRenderer.forEachFeatureAtCoordinate(
           coordinate, frameState, callback, thisArg);
       if (result) {
         return result;
@@ -170,6 +171,80 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel =
 
 
 /**
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {function(this: S, ol.layer.Layer): T} callback Layer
+ *     callback.
+ * @param {S} thisArg Value to use as `this` when executing `callback`.
+ * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
+ *     function, only layers which are visible and for which this function
+ *     returns `true` will be tested for features.  By default, all visible
+ *     layers will be tested.
+ * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`.
+ * @return {T|undefined} Callback result.
+ * @template S,T,U
+ */
+ol.renderer.Map.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg,
+        layerFilter, thisArg2) {
+  var result;
+  var viewState = frameState.viewState;
+  var viewResolution = viewState.resolution;
+  var viewRotation = viewState.rotation;
+
+  if (!goog.isNull(this.replayGroup)) {
+    var coordinate = this.getMap().getCoordinateFromPixel(pixel);
+    var hasFeature = this.replayGroup.forEachFeatureAtCoordinate(coordinate,
+        viewResolution, viewRotation, {}, goog.functions.TRUE);
+
+    if (hasFeature) {
+      result = callback.call(thisArg, null);
+      if (result) {
+        return 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, viewResolution) &&
+        layerFilter.call(thisArg2, layer)) {
+      var layerRenderer = this.getLayerRenderer(layer);
+      result = layerRenderer.forEachLayerAtPixel(
+          pixel, frameState, callback, thisArg);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return undefined;
+};
+
+
+/**
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @param {olx.FrameState} frameState FrameState.
+ * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
+ *     function, only layers which are visible and for which this function
+ *     returns `true` will be tested for features.  By default, all visible
+ *     layers will be tested.
+ * @param {U} thisArg Value to use as `this` when executing `layerFilter`.
+ * @return {boolean} Is there a feature at the given coordinate?
+ * @template U
+ */
+ol.renderer.Map.prototype.hasFeatureAtCoordinate =
+    function(coordinate, frameState, layerFilter, thisArg) {
+  var hasFeature = this.forEachFeatureAtCoordinate(
+      coordinate, frameState, goog.functions.TRUE, this, layerFilter, thisArg);
+
+  return goog.isDef(hasFeature);
+};
+
+
+/**
  * @param {ol.layer.Layer} layer Layer.
  * @protected
  * @return {ol.renderer.Layer} Layer renderer.
diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js
index 61dd3f6..e4d001e 100644
--- a/src/ol/renderer/webgl/webglimagelayerrenderer.js
+++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js
@@ -1,19 +1,21 @@
 goog.provide('ol.renderer.webgl.ImageLayer');
 
 goog.require('goog.asserts');
-goog.require('goog.events');
-goog.require('goog.events.EventType');
+goog.require('goog.functions');
 goog.require('goog.vec.Mat4');
 goog.require('goog.webgl');
 goog.require('ol.Coordinate');
 goog.require('ol.Extent');
 goog.require('ol.ImageBase');
-goog.require('ol.ImageState');
 goog.require('ol.ViewHint');
+goog.require('ol.dom');
 goog.require('ol.extent');
 goog.require('ol.layer.Image');
 goog.require('ol.proj');
 goog.require('ol.renderer.webgl.Layer');
+goog.require('ol.source.ImageVector');
+goog.require('ol.vec.Mat4');
+goog.require('ol.webgl.Context');
 
 
 
@@ -34,6 +36,18 @@ ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) {
    */
   this.image_ = null;
 
+  /**
+   * @private
+   * @type {CanvasRenderingContext2D}
+   */
+  this.hitCanvasContext_ = null;
+
+  /**
+   * @private
+   * @type {?goog.vec.Mat4.Number}
+   */
+  this.hitTransformationMatrix_ = null;
+
 };
 goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);
 
@@ -52,39 +66,23 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
   var imageElement = image.getImage();
   var gl = this.getWebGLMapRenderer().getGL();
 
-  var texture = gl.createTexture();
-
-  gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
-  gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
-      goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, imageElement);
-
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S,
-      goog.webgl.CLAMP_TO_EDGE);
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T,
-      goog.webgl.CLAMP_TO_EDGE);
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR);
-  gl.texParameteri(
-      goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR);
-
-  return texture;
+  return ol.webgl.Context.createTexture(
+      gl, imageElement, goog.webgl.CLAMP_TO_EDGE, goog.webgl.CLAMP_TO_EDGE);
 };
 
 
 /**
  * @inheritDoc
  */
-ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtPixel =
+ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate =
     function(coordinate, frameState, callback, thisArg) {
   var layer = this.getLayer();
   var source = layer.getSource();
   var resolution = frameState.viewState.resolution;
   var rotation = frameState.viewState.rotation;
   var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtPixel(
-      resolution, rotation, coordinate, skippedFeatureUids,
+  return source.forEachFeatureAtCoordinate(
+      coordinate, resolution, rotation, skippedFeatureUids,
 
       /**
        * @param {ol.Feature} feature Feature.
@@ -133,12 +131,8 @@ ol.renderer.webgl.ImageLayer.prototype.prepareFrame =
     var image_ = imageSource.getImage(renderedExtent, viewResolution,
         frameState.pixelRatio, projection);
     if (!goog.isNull(image_)) {
-      var imageState = image_.getState();
-      if (imageState == ol.ImageState.IDLE) {
-        goog.events.listenOnce(image_, goog.events.EventType.CHANGE,
-            this.handleImageChange, false, this);
-        image_.load();
-      } else if (imageState == ol.ImageState.LOADED) {
+      var loaded = this.loadImage(image_);
+      if (loaded) {
         image = image_;
         texture = this.createTexture_(image_);
         if (!goog.isNull(this.texture)) {
@@ -165,6 +159,7 @@ ol.renderer.webgl.ImageLayer.prototype.prepareFrame =
 
     this.updateProjectionMatrix_(canvas.width, canvas.height,
         viewCenter, viewResolution, viewRotation, image.getExtent());
+    this.hitTransformationMatrix_ = null;
 
     // Translate and scale to flip the Y coord.
     var texCoordMatrix = this.texCoordMatrix;
@@ -215,3 +210,114 @@ ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ =
   goog.vec.Mat4.translate(projectionMatrix, 1, 1, 0);
 
 };
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate =
+    function(coordinate, frameState) {
+  var hasFeature = this.forEachFeatureAtCoordinate(
+      coordinate, frameState, goog.functions.TRUE, this);
+  return goog.isDef(hasFeature);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg) {
+  if (goog.isNull(this.image_) || goog.isNull(this.image_.getImage())) {
+    return undefined;
+  }
+
+  if (this.getLayer().getSource() instanceof ol.source.ImageVector) {
+    // for ImageVector sources use the original hit-detection logic,
+    // so that for example also transparent polygons are detected
+    var coordinate = this.getMap().getCoordinateFromPixel(pixel);
+    var hasFeature = this.forEachFeatureAtCoordinate(
+        coordinate, frameState, goog.functions.TRUE, this);
+
+    if (hasFeature) {
+      return callback.call(thisArg, this.getLayer());
+    } else {
+      return undefined;
+    }
+  } else {
+    var imageSize =
+        [this.image_.getImage().width, this.image_.getImage().height];
+
+    if (goog.isNull(this.hitTransformationMatrix_)) {
+      this.hitTransformationMatrix_ = this.getHitTransformationMatrix_(
+          frameState.size, imageSize);
+    }
+
+    var pixelOnFrameBuffer = [0, 0];
+    ol.vec.Mat4.multVec2(
+        this.hitTransformationMatrix_, pixel, pixelOnFrameBuffer);
+
+    if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] ||
+        pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) {
+      // outside the image, no need to check
+      return undefined;
+    }
+
+    if (goog.isNull(this.hitCanvasContext_)) {
+      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
+    }
+
+    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
+    this.hitCanvasContext_.drawImage(this.image_.getImage(),
+        pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1);
+
+    var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
+    if (imageData[3] > 0) {
+      return callback.call(thisArg, this.getLayer());
+    } else {
+      return undefined;
+    }
+  }
+};
+
+
+/**
+ * The transformation matrix to get the pixel on the image for a
+ * pixel on the map.
+ * @param {ol.Size} mapSize
+ * @param {ol.Size} imageSize
+ * @return {goog.vec.Mat4.Number}
+ * @private
+ */
+ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ =
+    function(mapSize, imageSize) {
+  // the first matrix takes a map pixel, flips the y-axis and scales to
+  // a range between -1 ... 1
+  var mapCoordMatrix = goog.vec.Mat4.createNumber();
+  goog.vec.Mat4.makeIdentity(mapCoordMatrix);
+  goog.vec.Mat4.translate(mapCoordMatrix, -1, -1, 0);
+  goog.vec.Mat4.scale(mapCoordMatrix, 2 / mapSize[0], 2 / mapSize[1], 1);
+  goog.vec.Mat4.translate(mapCoordMatrix, 0, mapSize[1], 0);
+  goog.vec.Mat4.scale(mapCoordMatrix, 1, -1, 1);
+
+  // the second matrix is the inverse of the projection matrix used in the
+  // shader for drawing
+  var projectionMatrixInv = goog.vec.Mat4.createNumber();
+  goog.vec.Mat4.invert(this.projectionMatrix, projectionMatrixInv);
+
+  // the third matrix scales to the image dimensions and flips the y-axis again
+  var imageCoordMatrix = goog.vec.Mat4.createNumber();
+  goog.vec.Mat4.makeIdentity(imageCoordMatrix);
+  goog.vec.Mat4.translate(imageCoordMatrix, 0, imageSize[1], 0);
+  goog.vec.Mat4.scale(imageCoordMatrix, 1, -1, 1);
+  goog.vec.Mat4.scale(imageCoordMatrix, imageSize[0] / 2, imageSize[1] / 2, 1);
+  goog.vec.Mat4.translate(imageCoordMatrix, 1, 1, 0);
+
+  var transformMatrix = goog.vec.Mat4.createNumber();
+  goog.vec.Mat4.multMat(
+      imageCoordMatrix, projectionMatrixInv, transformMatrix);
+  goog.vec.Mat4.multMat(
+      transformMatrix, mapCoordMatrix, transformMatrix);
+
+  return transformMatrix;
+};
diff --git a/src/ol/renderer/webgl/webgllayerrenderer.js b/src/ol/renderer/webgl/webgllayerrenderer.js
index 4f09f7b..26b9ea5 100644
--- a/src/ol/renderer/webgl/webgllayerrenderer.js
+++ b/src/ol/renderer/webgl/webgllayerrenderer.js
@@ -11,6 +11,7 @@ goog.require('ol.renderer.Layer');
 goog.require('ol.renderer.webgl.map.shader.Color');
 goog.require('ol.renderer.webgl.map.shader.Default');
 goog.require('ol.webgl.Buffer');
+goog.require('ol.webgl.Context');
 
 
 
@@ -115,15 +116,8 @@ ol.renderer.webgl.Layer.prototype.bindFramebuffer =
               }
             }, gl, this.framebuffer, this.texture));
 
-    var texture = gl.createTexture();
-    gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
-    gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
-        framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA,
-        goog.webgl.UNSIGNED_BYTE, null);
-    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER,
-        goog.webgl.LINEAR);
-    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER,
-        goog.webgl.LINEAR);
+    var texture = ol.webgl.Context.createEmptyTexture(
+        gl, framebufferDimension, framebufferDimension);
 
     var framebuffer = gl.createFramebuffer();
     gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer);
diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js
index b597950..7fd95f3 100644
--- a/src/ol/renderer/webgl/webglmaprenderer.js
+++ b/src/ol/renderer/webgl/webglmaprenderer.js
@@ -290,13 +290,10 @@ ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ =
     replayGroup.finish(context);
     if (!replayGroup.isEmpty()) {
       // use default color values
-      var opacity = 1;
-      var brightness = 0;
-      var contrast = 1;
-      var hue = 0;
-      var saturation = 1;
+      var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_;
       replayGroup.replay(context, center, resolution, rotation, size,
-          pixelRatio, opacity, brightness, contrast, hue, saturation, {});
+          pixelRatio, d.opacity, d.brightness, d.contrast,
+          d.hue, d.saturation, {});
     }
     replayGroup.getDeleteResourcesFunction(context)();
 
@@ -538,3 +535,177 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
   this.scheduleExpireIconCache(frameState);
 
 };
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate =
+    function(coordinate, frameState, callback, thisArg,
+        layerFilter, thisArg2) {
+  var result;
+
+  if (this.getGL().isContextLost()) {
+    return false;
+  }
+
+  var context = this.getContext();
+  var viewState = frameState.viewState;
+
+  // do the hit-detection for the overlays first
+  if (!goog.isNull(this.replayGroup)) {
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+
+    // use default color values
+    var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_;
+
+    result = this.replayGroup.forEachFeatureAtCoordinate(coordinate,
+        context, viewState.center, viewState.resolution, viewState.rotation,
+        frameState.size, frameState.pixelRatio,
+        d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {},
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          goog.asserts.assert(goog.isDef(feature));
+          var key = goog.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback.call(thisArg, feature, null);
+          }
+        });
+    if (result) {
+      return result;
+    }
+  }
+  var layerStates = frameState.layerStatesArray;
+  var numLayers = layerStates.length;
+  var i;
+  for (i = numLayers - 1; i >= 0; --i) {
+    var layerState = layerStates[i];
+    var layer = layerState.layer;
+    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
+        layerFilter.call(thisArg2, layer)) {
+      var layerRenderer = this.getLayerRenderer(layer);
+      result = layerRenderer.forEachFeatureAtCoordinate(
+          coordinate, frameState, callback, thisArg);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return undefined;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate =
+    function(coordinate, frameState, layerFilter, thisArg) {
+  var hasFeature = false;
+
+  if (this.getGL().isContextLost()) {
+    return false;
+  }
+
+  var context = this.getContext();
+  var viewState = frameState.viewState;
+
+  // do the hit-detection for the overlays first
+  if (!goog.isNull(this.replayGroup)) {
+    // use default color values
+    var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_;
+
+    hasFeature = this.replayGroup.hasFeatureAtCoordinate(coordinate,
+        context, viewState.center, viewState.resolution, viewState.rotation,
+        frameState.size, frameState.pixelRatio,
+        d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {});
+    if (hasFeature) {
+      return true;
+    }
+  }
+  var layerStates = frameState.layerStatesArray;
+  var numLayers = layerStates.length;
+  var i;
+  for (i = numLayers - 1; i >= 0; --i) {
+    var layerState = layerStates[i];
+    var layer = layerState.layer;
+    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
+        layerFilter.call(thisArg, layer)) {
+      var layerRenderer = this.getLayerRenderer(layer);
+      hasFeature =
+          layerRenderer.hasFeatureAtCoordinate(coordinate, frameState);
+      if (hasFeature) {
+        return true;
+      }
+    }
+  }
+  return hasFeature;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.Map.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg,
+        layerFilter, thisArg2) {
+  if (this.getGL().isContextLost()) {
+    return false;
+  }
+
+  var context = this.getContext();
+  var viewState = frameState.viewState;
+  var result;
+
+  // do the hit-detection for the overlays first
+  if (!goog.isNull(this.replayGroup)) {
+    // use default color values
+    var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_;
+    var coordinate = this.getMap().getCoordinateFromPixel(pixel);
+
+    var hasFeature = this.replayGroup.hasFeatureAtCoordinate(coordinate,
+        context, viewState.center, viewState.resolution, viewState.rotation,
+        frameState.size, frameState.pixelRatio,
+        d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {});
+    if (hasFeature) {
+      result = callback.call(thisArg, null);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  var layerStates = frameState.layerStatesArray;
+  var numLayers = layerStates.length;
+  var i;
+  for (i = numLayers - 1; i >= 0; --i) {
+    var layerState = layerStates[i];
+    var layer = layerState.layer;
+    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
+        layerFilter.call(thisArg, layer)) {
+      var layerRenderer = this.getLayerRenderer(layer);
+      result = layerRenderer.forEachLayerAtPixel(
+          pixel, frameState, callback, thisArg);
+      if (result) {
+        return result;
+      }
+    }
+  }
+  return undefined;
+};
+
+
+/**
+ * @private
+ * @const
+ */
+ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_ = {
+  opacity: 1,
+  brightness: 0,
+  contrast: 1,
+  hue: 0,
+  saturation: 1
+};
diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js
index 005eebc..9e79e8c 100644
--- a/src/ol/renderer/webgl/webgltilelayerrenderer.js
+++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js
@@ -17,6 +17,7 @@ goog.require('ol.math');
 goog.require('ol.renderer.webgl.Layer');
 goog.require('ol.renderer.webgl.tilelayer.shader');
 goog.require('ol.tilecoord');
+goog.require('ol.vec.Mat4');
 goog.require('ol.webgl.Buffer');
 
 
@@ -198,10 +199,6 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame =
         tilesToDrawByZ, getTileIfLoaded);
 
     var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-    if (!goog.isDef(useInterimTilesOnError)) {
-      useInterimTilesOnError = true;
-    }
-
     var allTilesLoaded = true;
     var tmpExtent = ol.extent.createEmpty();
     var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
@@ -330,3 +327,38 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame =
 
   return true;
 };
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg) {
+  if (goog.isNull(this.framebuffer)) {
+    return undefined;
+  }
+  var mapSize = this.getMap().getSize();
+
+  var pixelOnMapScaled = [
+    pixel[0] / mapSize[0],
+    (mapSize[1] - pixel[1]) / mapSize[1]];
+
+  var pixelOnFrameBufferScaled = [0, 0];
+  ol.vec.Mat4.multVec2(
+      this.texCoordMatrix, pixelOnMapScaled, pixelOnFrameBufferScaled);
+  var pixelOnFrameBuffer = [
+    pixelOnFrameBufferScaled[0] * this.framebufferDimension,
+    pixelOnFrameBufferScaled[1] * this.framebufferDimension];
+
+  var gl = this.getWebGLMapRenderer().getContext().getGL();
+  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
+  var imageData = new Uint8Array(4);
+  gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1,
+      gl.RGBA, gl.UNSIGNED_BYTE, imageData);
+
+  if (imageData[3] > 0) {
+    return callback.call(thisArg, this.getLayer());
+  } else {
+    return undefined;
+  }
+};
diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js
index e2a685a..7a3de9e 100644
--- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js
+++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js
@@ -58,6 +58,13 @@ ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
    */
   this.replayGroup_ = null;
 
+  /**
+   * The last layer state.
+   * @private
+   * @type {?ol.layer.LayerState}
+   */
+  this.layerState_ = null;
+
 };
 goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
 
@@ -67,6 +74,7 @@ goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
  */
 ol.renderer.webgl.VectorLayer.prototype.composeFrame =
     function(frameState, layerState, context) {
+  this.layerState_ = layerState;
   var viewState = frameState.viewState;
   var replayGroup = this.replayGroup_;
   if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) {
@@ -98,8 +106,73 @@ ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
 /**
  * @inheritDoc
  */
-ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel =
+ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate =
     function(coordinate, frameState, callback, thisArg) {
+  if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) {
+    return undefined;
+  } else {
+    var mapRenderer = this.getWebGLMapRenderer();
+    var context = mapRenderer.getContext();
+    var viewState = frameState.viewState;
+    var layer = this.getLayer();
+    var layerState = this.layerState_;
+    /** @type {Object.<string, boolean>} */
+    var features = {};
+    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate,
+        context, viewState.center, viewState.resolution, viewState.rotation,
+        frameState.size, frameState.pixelRatio,
+        layerState.opacity, layerState.brightness, layerState.contrast,
+        layerState.hue, layerState.saturation, frameState.skippedFeatureUids,
+        /**
+         * @param {ol.Feature} feature Feature.
+         * @return {?} Callback result.
+         */
+        function(feature) {
+          goog.asserts.assert(goog.isDef(feature));
+          var key = goog.getUid(feature).toString();
+          if (!(key in features)) {
+            features[key] = true;
+            return callback.call(thisArg, feature, layer);
+          }
+        });
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate =
+    function(coordinate, frameState) {
+  if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) {
+    return false;
+  } else {
+    var mapRenderer = this.getWebGLMapRenderer();
+    var context = mapRenderer.getContext();
+    var viewState = frameState.viewState;
+    var layerState = this.layerState_;
+    return this.replayGroup_.hasFeatureAtCoordinate(coordinate,
+        context, viewState.center, viewState.resolution, viewState.rotation,
+        frameState.size, frameState.pixelRatio,
+        layerState.opacity, layerState.brightness, layerState.contrast,
+        layerState.hue, layerState.saturation, frameState.skippedFeatureUids);
+  }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel =
+    function(pixel, frameState, callback, thisArg) {
+  var coordinate = this.getMap().getCoordinateFromPixel(pixel);
+  var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState);
+
+  if (hasFeature) {
+    return callback.call(thisArg, this.getLayer());
+  } else {
+    return undefined;
+  }
 };
 
 
@@ -108,7 +181,7 @@ ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel =
  * @param {goog.events.Event} event Image style change event.
  * @private
  */
-ol.renderer.webgl.VectorLayer.prototype.handleImageChange_ =
+ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ =
     function(event) {
   this.renderIfReadyAndVisible();
 };
@@ -128,7 +201,8 @@ ol.renderer.webgl.VectorLayer.prototype.prepareFrame =
       frameState.attributions, vectorSource.getAttributions());
   this.updateLogos(frameState, vectorSource);
 
-  if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
+  if (!this.dirty_ && (!vectorLayer.getUpdateWhileAnimating() &&
+      frameState.viewHints[ol.ViewHint.ANIMATING] ||
       frameState.viewHints[ol.ViewHint.INTERACTING])) {
     return true;
   }
@@ -166,7 +240,7 @@ ol.renderer.webgl.VectorLayer.prototype.prepareFrame =
 
   var replayGroup = new ol.render.webgl.ReplayGroup(
       ol.renderer.vector.getTolerance(resolution, pixelRatio),
-      extent);
+      extent, vectorLayer.getRenderBuffer());
   vectorSource.loadFeatures(extent, resolution, projection);
   var renderFeature =
       /**
@@ -232,7 +306,7 @@ ol.renderer.webgl.VectorLayer.prototype.renderFeature =
     loading = ol.renderer.vector.renderFeature(
         replayGroup, feature, styles[i],
         ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleImageChange_, this) || loading;
+        this.handleStyleImageChange_, this) || loading;
   }
   return loading;
 };
diff --git a/src/ol/source/bingmapssource.js b/src/ol/source/bingmapssource.js
index fdcb439..f025874 100644
--- a/src/ol/source/bingmapssource.js
+++ b/src/ol/source/bingmapssource.js
@@ -48,6 +48,12 @@ ol.source.BingMaps = function(options) {
    */
   this.maxZoom_ = goog.isDef(options.maxZoom) ? options.maxZoom : -1;
 
+  /**
+   * @private
+   * @type {boolean}
+   */
+  this.wrapX_ = goog.isDef(options.wrapX) ? options.wrapX : true;
+
   var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
   var uri = new goog.Uri(
       protocol + '//dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
@@ -108,7 +114,7 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse =
 
   var culture = this.culture_;
   this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
-      tileGrid.createTileCoordTransform(),
+      tileGrid.createTileCoordTransform({wrapX: this.wrapX_}),
       ol.TileUrlFunction.createFromTileUrlFunctions(
           goog.array.map(
               resource.imageUrlSubdomains,
diff --git a/src/ol/source/clustersource.js b/src/ol/source/clustersource.js
index cb5225c..3e412fa 100644
--- a/src/ol/source/clustersource.js
+++ b/src/ol/source/clustersource.js
@@ -93,7 +93,7 @@ ol.source.Cluster.prototype.cluster_ = function() {
   if (!goog.isDef(this.resolution_)) {
     return;
   }
-  goog.array.clear(this.features_);
+  this.features_.length = 0;
   var extent = ol.extent.createEmpty();
   var mapDistance = this.distance_ * this.resolution_;
   var features = this.source_.getFeatures();
@@ -117,7 +117,7 @@ ol.source.Cluster.prototype.cluster_ = function() {
       neighbors = goog.array.filter(neighbors, function(neighbor) {
         var uid = goog.getUid(neighbor).toString();
         if (!goog.object.containsKey(clustered, uid)) {
-          goog.object.set(clustered, uid, true);
+          clustered[uid] = true;
           return true;
         } else {
           return false;
diff --git a/src/ol/source/imagemapguidesource.js b/src/ol/source/imagemapguidesource.js
index 3d7c18f..de72827 100644
--- a/src/ol/source/imagemapguidesource.js
+++ b/src/ol/source/imagemapguidesource.js
@@ -161,6 +161,15 @@ ol.source.ImageMapGuide.prototype.getImage =
 
 
 /**
+ * @return {ol.ImageLoadFunctionType} The image load function.
+ * @api
+ */
+ol.source.ImageMapGuide.prototype.getImageLoadFunction = function() {
+  return this.imageLoadFunction_;
+};
+
+
+/**
  * @param {ol.Extent} extent The map extents.
  * @param {ol.Size} size the viewport size.
  * @param {number} metersPerUnit The meters-per-unit value.
@@ -221,3 +230,15 @@ ol.source.ImageMapGuide.prototype.getUrl =
   goog.object.extend(baseParams, params);
   return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams);
 };
+
+
+/**
+ * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
+ * @api
+ */
+ol.source.ImageMapGuide.prototype.setImageLoadFunction = function(
+    imageLoadFunction) {
+  this.image_ = null;
+  this.imageLoadFunction_ = imageLoadFunction;
+  this.changed();
+};
diff --git a/src/ol/source/imagevectorsource.js b/src/ol/source/imagevectorsource.js
index ee40500..521ad8e 100644
--- a/src/ol/source/imagevectorsource.js
+++ b/src/ol/source/imagevectorsource.js
@@ -152,15 +152,15 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ =
 /**
  * @inheritDoc
  */
-ol.source.ImageVector.prototype.forEachFeatureAtPixel = function(
-    resolution, rotation, coordinate, skippedFeatureUids, callback) {
+ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
+    coordinate, resolution, rotation, skippedFeatureUids, callback) {
   if (goog.isNull(this.replayGroup_)) {
     return undefined;
   } else {
     /** @type {Object.<string, boolean>} */
     var features = {};
-    return this.replayGroup_.forEachGeometryAtPixel(
-        resolution, 0, coordinate, skippedFeatureUids,
+    return this.replayGroup_.forEachFeatureAtCoordinate(
+        coordinate, resolution, 0, skippedFeatureUids,
         /**
          * @param {ol.Feature} feature Feature.
          * @return {?} Callback result.
diff --git a/src/ol/source/imagewmssource.js b/src/ol/source/imagewmssource.js
index 4ba801d..b058b82 100644
--- a/src/ol/source/imagewmssource.js
+++ b/src/ol/source/imagewmssource.js
@@ -152,14 +152,14 @@ ol.source.ImageWMS.prototype.getGetFeatureInfoUrl =
     'REQUEST': 'GetFeatureInfo',
     'FORMAT': 'image/png',
     'TRANSPARENT': true,
-    'QUERY_LAYERS': goog.object.get(this.params_, 'LAYERS')
+    'QUERY_LAYERS': this.params_['LAYERS']
   };
   goog.object.extend(baseParams, this.params_, params);
 
   var x = Math.floor((coordinate[0] - extent[0]) / resolution);
   var y = Math.floor((extent[3] - coordinate[1]) / resolution);
-  goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x);
-  goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y);
+  baseParams[this.v13_ ? 'I' : 'X'] = x;
+  baseParams[this.v13_ ? 'J' : 'Y'] = y;
 
   return this.getRequestUrl_(
       extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_,
@@ -253,6 +253,15 @@ ol.source.ImageWMS.prototype.getImage =
 
 
 /**
+ * @return {ol.ImageLoadFunctionType} The image load function.
+ * @api
+ */
+ol.source.ImageWMS.prototype.getImageLoadFunction = function() {
+  return this.imageLoadFunction_;
+};
+
+
+/**
  * @param {ol.Extent} extent Extent.
  * @param {ol.Size} size Size.
  * @param {number} pixelRatio Pixel ratio.
@@ -270,7 +279,7 @@ ol.source.ImageWMS.prototype.getRequestUrl_ =
 
   if (!('STYLES' in this.params_)) {
     /* jshint -W053 */
-    goog.object.set(params, 'STYLES', new String(''));
+    params['STYLES'] = new String('');
     /* jshint +W053 */
   }
 
@@ -278,14 +287,14 @@ ol.source.ImageWMS.prototype.getRequestUrl_ =
     switch (this.serverType_) {
       case ol.source.wms.ServerType.GEOSERVER:
         var dpi = (90 * pixelRatio + 0.5) | 0;
-        goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi);
+        params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
         break;
       case ol.source.wms.ServerType.MAPSERVER:
-        goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio);
+        params['MAP_RESOLUTION'] = 90 * pixelRatio;
         break;
       case ol.source.wms.ServerType.CARMENTA_SERVER:
       case ol.source.wms.ServerType.QGIS:
-        goog.object.set(params, 'DPI', 90 * pixelRatio);
+        params['DPI'] = 90 * pixelRatio;
         break;
       default:
         goog.asserts.fail();
@@ -293,8 +302,8 @@ ol.source.ImageWMS.prototype.getRequestUrl_ =
     }
   }
 
-  goog.object.set(params, 'WIDTH', size[0]);
-  goog.object.set(params, 'HEIGHT', size[1]);
+  params['WIDTH'] = size[0];
+  params['HEIGHT'] = size[1];
 
   var axisOrientation = projection.getAxisOrientation();
   var bbox;
@@ -303,7 +312,7 @@ ol.source.ImageWMS.prototype.getRequestUrl_ =
   } else {
     bbox = extent;
   }
-  goog.object.set(params, 'BBOX', bbox.join(','));
+  params['BBOX'] = bbox.join(',');
 
   return goog.uri.utils.appendParamsFromMap(this.url_, params);
 };
@@ -320,6 +329,18 @@ ol.source.ImageWMS.prototype.getUrl = function() {
 
 
 /**
+ * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
+ * @api
+ */
+ol.source.ImageWMS.prototype.setImageLoadFunction = function(
+    imageLoadFunction) {
+  this.image_ = null;
+  this.imageLoadFunction_ = imageLoadFunction;
+  this.changed();
+};
+
+
+/**
  * @param {string|undefined} url URL.
  * @api stable
  */
diff --git a/src/ol/source/mapquestsource.js b/src/ol/source/mapquestsource.js
index 743bddb..6ca9569 100644
--- a/src/ol/source/mapquestsource.js
+++ b/src/ol/source/mapquestsource.js
@@ -25,7 +25,8 @@ ol.source.MapQuest = function(opt_options) {
   var layerConfig = ol.source.MapQuestConfig[options.layer];
 
   var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
-  var url = protocol + '//otile{1-4}-s.mqcdn.com/tiles/1.0.0/' +
+  var url = goog.isDef(options.url) ? options.url :
+      protocol + '//otile{1-4}-s.mqcdn.com/tiles/1.0.0/' +
       options.layer + '/{z}/{x}/{y}.jpg';
 
   goog.base(this, {
diff --git a/src/ol/source/osmsource.js b/src/ol/source/osmsource.js
index b100bbb..7f992f1 100644
--- a/src/ol/source/osmsource.js
+++ b/src/ol/source/osmsource.js
@@ -39,7 +39,8 @@ ol.source.OSM = function(opt_options) {
     opaque: true,
     maxZoom: goog.isDef(options.maxZoom) ? options.maxZoom : 19,
     tileLoadFunction: options.tileLoadFunction,
-    url: url
+    url: url,
+    wrapX: options.wrapX
   });
 
 };
diff --git a/src/ol/source/servervectorsource.js b/src/ol/source/servervectorsource.js
index d9b988b..8d36a4c 100644
--- a/src/ol/source/servervectorsource.js
+++ b/src/ol/source/servervectorsource.js
@@ -82,11 +82,12 @@ ol.source.ServerVector.prototype.addFeaturesInternal = function(features) {
 
 /**
  * @inheritDoc
+ * @api stable
  */
-ol.source.ServerVector.prototype.clear = function() {
+ol.source.ServerVector.prototype.clear = function(opt_fast) {
   goog.object.clear(this.loadedFeatures_);
   this.loadedExtents_.clear();
-  goog.base(this, 'clear');
+  goog.base(this, 'clear', opt_fast);
 };
 
 
diff --git a/src/ol/source/source.js b/src/ol/source/source.js
index ea2c84c..a54d304 100644
--- a/src/ol/source/source.js
+++ b/src/ol/source/source.js
@@ -77,15 +77,15 @@ goog.inherits(ol.source.Source, ol.Observable);
 
 
 /**
+ * @param {ol.Coordinate} coordinate Coordinate.
  * @param {number} resolution Resolution.
  * @param {number} rotation Rotation.
- * @param {ol.Coordinate} coordinate Coordinate.
  * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
  * @param {function(ol.Feature): T} callback Feature callback.
  * @return {T|undefined} Callback result.
  * @template T
  */
-ol.source.Source.prototype.forEachFeatureAtPixel =
+ol.source.Source.prototype.forEachFeatureAtCoordinate =
     goog.nullFunction;
 
 
diff --git a/src/ol/source/tilewmssource.js b/src/ol/source/tilewmssource.js
index fd227e9..a1873ad 100644
--- a/src/ol/source/tilewmssource.js
+++ b/src/ol/source/tilewmssource.js
@@ -161,15 +161,15 @@ ol.source.TileWMS.prototype.getGetFeatureInfoUrl =
     'REQUEST': 'GetFeatureInfo',
     'FORMAT': 'image/png',
     'TRANSPARENT': true,
-    'QUERY_LAYERS': goog.object.get(this.params_, 'LAYERS')
+    'QUERY_LAYERS': this.params_['LAYERS']
   };
   goog.object.extend(baseParams, this.params_, params);
 
   var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution);
   var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution);
 
-  goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x);
-  goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y);
+  baseParams[this.v13_ ? 'I' : 'X'] = x;
+  baseParams[this.v13_ ? 'J' : 'Y'] = y;
 
   return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
       1, projectionObj, baseParams);
@@ -222,14 +222,14 @@ ol.source.TileWMS.prototype.getRequestUrl_ =
     return undefined;
   }
 
-  goog.object.set(params, 'WIDTH', tileSize);
-  goog.object.set(params, 'HEIGHT', tileSize);
+  params['WIDTH'] = tileSize;
+  params['HEIGHT'] = tileSize;
 
   params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
 
   if (!('STYLES' in this.params_)) {
     /* jshint -W053 */
-    goog.object.set(params, 'STYLES', new String(''));
+    params['STYLES'] = new String('');
     /* jshint +W053 */
   }
 
@@ -237,14 +237,14 @@ ol.source.TileWMS.prototype.getRequestUrl_ =
     switch (this.serverType_) {
       case ol.source.wms.ServerType.GEOSERVER:
         var dpi = (90 * pixelRatio + 0.5) | 0;
-        goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi);
+        params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
         break;
       case ol.source.wms.ServerType.MAPSERVER:
-        goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio);
+        params['MAP_RESOLUTION'] = 90 * pixelRatio;
         break;
       case ol.source.wms.ServerType.CARMENTA_SERVER:
       case ol.source.wms.ServerType.QGIS:
-        goog.object.set(params, 'DPI', 90 * pixelRatio);
+        params['DPI'] = 90 * pixelRatio;
         break;
       default:
         goog.asserts.fail();
@@ -263,7 +263,7 @@ ol.source.TileWMS.prototype.getRequestUrl_ =
     bbox[2] = tileExtent[3];
     bbox[3] = tmp;
   }
-  goog.object.set(params, 'BBOX', bbox.join(','));
+  params['BBOX'] = bbox.join(',');
 
   var url;
   if (urls.length == 1) {
@@ -369,8 +369,7 @@ ol.source.TileWMS.prototype.tileUrlFunction_ =
   }
 
   var tileResolution = tileGrid.getResolution(tileCoord[0]);
-  var tileExtent = tileGrid.getTileCoordExtent(
-      tileCoord, this.tmpExtent_);
+  var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_);
   var tileSize = tileGrid.getTileSize(tileCoord[0]);
 
   var gutter = this.gutter_;
diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js
index de1783e..ccfe2cb 100644
--- a/src/ol/source/vectorsource.js
+++ b/src/ol/source/vectorsource.js
@@ -295,7 +295,7 @@ ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) {
  * @return {S|undefined} The return value from the last call to the callback.
  * @template T,S
  */
-ol.source.Vector.prototype.forEachFeatureAtCoordinate =
+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) {
@@ -409,7 +409,7 @@ ol.source.Vector.prototype.getFeatures = function() {
  */
 ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
   var features = [];
-  this.forEachFeatureAtCoordinate(coordinate, function(feature) {
+  this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) {
     features.push(feature);
   });
   return features;
diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js
index a9c5cce..7efa537 100644
--- a/src/ol/structs/rbush.js
+++ b/src/ol/structs/rbush.js
@@ -4,6 +4,7 @@ goog.require('goog.array');
 goog.require('goog.asserts');
 goog.require('goog.object');
 goog.require('ol.ext.rbush');
+goog.require('ol.extent');
 
 
 
@@ -59,7 +60,9 @@ ol.structs.RBush.prototype.insert = function(extent, value) {
   ];
   this.rbush_.insert(item);
   // remember the object that was added to the internal rbush
-  goog.object.add(this.items_, goog.getUid(value).toString(), item);
+  goog.asserts.assert(
+      !goog.object.containsKey(this.items_, goog.getUid(value)));
+  this.items_[goog.getUid(value)] = item;
 };
 
 
@@ -87,7 +90,9 @@ ol.structs.RBush.prototype.load = function(extents, values) {
       value
     ];
     items[i] = item;
-    goog.object.add(this.items_, goog.getUid(value).toString(), item);
+    goog.asserts.assert(
+        !goog.object.containsKey(this.items_, goog.getUid(value)));
+    this.items_[goog.getUid(value)] = item;
   }
   this.rbush_.load(items);
 };
@@ -102,12 +107,12 @@ ol.structs.RBush.prototype.remove = function(value) {
   if (goog.DEBUG && this.readers_) {
     throw new Error('Can not remove value while reading');
   }
-  var uid = goog.getUid(value).toString();
+  var uid = goog.getUid(value);
   goog.asserts.assert(goog.object.containsKey(this.items_, uid));
 
   // get the object in which the value was wrapped when adding to the
   // internal rbush. then use that object to do the removal.
-  var item = goog.object.get(this.items_, uid);
+  var item = this.items_[uid];
   goog.object.remove(this.items_, uid);
   return this.rbush_.remove(item) !== null;
 };
@@ -119,8 +124,17 @@ ol.structs.RBush.prototype.remove = function(value) {
  * @param {T} value Value.
  */
 ol.structs.RBush.prototype.update = function(extent, value) {
-  this.remove(value);
-  this.insert(extent, value);
+  var uid = goog.getUid(value);
+  goog.asserts.assert(goog.object.containsKey(this.items_, uid));
+
+  var item = this.items_[uid];
+  if (!ol.extent.equals(item.slice(0, 4), extent)) {
+    if (goog.DEBUG && this.readers_) {
+      throw new Error('Can not update extent while reading');
+    }
+    this.remove(value);
+    this.insert(extent, value);
+  }
 };
 
 
diff --git a/src/ol/style/atlasmanager.js b/src/ol/style/atlasmanager.js
index a3cb260..8daea3b 100644
--- a/src/ol/style/atlasmanager.js
+++ b/src/ol/style/atlasmanager.js
@@ -4,6 +4,7 @@ goog.provide('ol.style.AtlasManager');
 goog.require('goog.asserts');
 goog.require('goog.dom');
 goog.require('goog.dom.TagName');
+goog.require('goog.functions');
 goog.require('goog.object');
 goog.require('ol');
 
@@ -11,12 +12,10 @@ goog.require('ol');
 /**
  * Provides information for an image inside an atlas manager.
  * `offsetX` and `offsetY` is the position of the image inside
- * the atlas image `image`.
- * `hitOffsetX` and `hitOffsetY` ist the position of the hit-detection image
- * inside the hit-detection atlas image `hitImage` (only when a hit-detection
- * image was created for this image).
+ * the atlas image `image` and the position of the hit-detection image
+ * inside the hit-detection atlas image `hitImage`.
  * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement,
- *    hitOffsetX: number, hitOffsetY: number, hitImage: HTMLCanvasElement}}
+ *    hitImage: HTMLCanvasElement}}
  */
 ol.style.AtlasManagerInfo;
 
@@ -103,6 +102,7 @@ ol.style.AtlasManager.prototype.getInfo = function(id) {
   }
   /** @type {?ol.style.AtlasInfo} */
   var hitInfo = this.getInfo_(this.hitAtlases_, id);
+  goog.asserts.assert(!goog.isNull(hitInfo));
 
   return this.mergeInfos_(info, hitInfo);
 };
@@ -131,19 +131,19 @@ ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
 /**
  * @private
  * @param {ol.style.AtlasInfo} info The info for the real image.
- * @param {?ol.style.AtlasInfo} hitInfo The info for the hit-detection
+ * @param {ol.style.AtlasInfo} hitInfo The info for the hit-detection
  *    image.
  * @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
  *    entry, or `null` if the entry is not part of the atlases.
  */
 ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
+  goog.asserts.assert(info.offsetX === hitInfo.offsetX);
+  goog.asserts.assert(info.offsetY === hitInfo.offsetY);
   return /** @type {ol.style.AtlasManagerInfo} */ ({
     offsetX: info.offsetX,
     offsetY: info.offsetY,
     image: info.image,
-    hitOffsetX: goog.isNull(hitInfo) ? undefined : hitInfo.offsetX,
-    hitOffsetY: goog.isNull(hitInfo) ? undefined : hitInfo.offsetY,
-    hitImage: goog.isNull(hitInfo) ? undefined : hitInfo.image
+    hitImage: hitInfo.image
   });
 };
 
@@ -185,12 +185,17 @@ ol.style.AtlasManager.prototype.add =
     return null;
   }
 
+  // even if no hit-detection entry is requested, we insert a fake entry into
+  // the hit-detection atlas, to make sure that the offset is the same for
+  // the original image and the hit-detection image.
+  var renderHitCallback = goog.isDef(opt_renderHitCallback) ?
+      opt_renderHitCallback : goog.functions.NULL;
+
   /** @type {?ol.style.AtlasInfo} */
-  var hitInfo = null;
-  if (goog.isDef(opt_renderHitCallback)) {
-    hitInfo = this.add_(true,
-        id, width, height, opt_renderHitCallback, opt_this);
-  }
+  var hitInfo = this.add_(true,
+      id, width, height, renderHitCallback, opt_this);
+  goog.asserts.assert(!goog.isNull(hitInfo));
+
   return this.mergeInfos_(info, hitInfo);
 };
 
diff --git a/src/ol/style/circlestyle.js b/src/ol/style/circlestyle.js
index 96632d0..d6b459b 100644
--- a/src/ol/style/circlestyle.js
+++ b/src/ol/style/circlestyle.js
@@ -74,12 +74,6 @@ ol.style.Circle = function(opt_options) {
    * @private
    * @type {Array.<number>}
    */
-  this.hitDetectionOrigin_ = [0, 0];
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
   this.anchor_ = null;
 
   /**
@@ -190,14 +184,6 @@ ol.style.Circle.prototype.getOrigin = function() {
 
 
 /**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getHitDetectionOrigin = function() {
-  return this.hitDetectionOrigin_;
-};
-
-
-/**
  * @return {number} Radius.
  * @api
  */
@@ -323,12 +309,10 @@ ol.style.Circle.prototype.render_ = function(atlasManager) {
 
     if (hasCustomHitDetectionImage) {
       this.hitDetectionCanvas_ = info.hitImage;
-      this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
       this.hitDetectionImageSize_ =
           [info.hitImage.width, info.hitImage.height];
     } else {
       this.hitDetectionCanvas_ = this.canvas_;
-      this.hitDetectionOrigin_ = this.origin_;
       this.hitDetectionImageSize_ = [imageSize, imageSize];
     }
   }
diff --git a/src/ol/style/iconstyle.js b/src/ol/style/iconstyle.js
index 9913c64..e9130b3 100644
--- a/src/ol/style/iconstyle.js
+++ b/src/ol/style/iconstyle.js
@@ -302,14 +302,6 @@ ol.style.Icon.prototype.getOrigin = function() {
 
 
 /**
- * @inheritDoc
- */
-ol.style.Icon.prototype.getHitDetectionOrigin = function() {
-  return this.getOrigin();
-};
-
-
-/**
  * @return {string|undefined} Image src.
  * @api
  */
diff --git a/src/ol/style/imagestyle.js b/src/ol/style/imagestyle.js
index d34d74c..1cb95b2 100644
--- a/src/ol/style/imagestyle.js
+++ b/src/ol/style/imagestyle.js
@@ -163,13 +163,6 @@ ol.style.Image.prototype.getOrigin = goog.abstractMethod;
 
 /**
  * @function
- * @return {Array.<number>} Origin for the hit-detection image.
- */
-ol.style.Image.prototype.getHitDetectionOrigin = goog.abstractMethod;
-
-
-/**
- * @function
  * @return {ol.Size} Size.
  */
 ol.style.Image.prototype.getSize = goog.abstractMethod;
diff --git a/src/ol/style/regularshapestyle.js b/src/ol/style/regularshapestyle.js
index cde61ff..a1fdd70 100644
--- a/src/ol/style/regularshapestyle.js
+++ b/src/ol/style/regularshapestyle.js
@@ -6,6 +6,7 @@ goog.require('goog.dom.TagName');
 goog.require('ol.color');
 goog.require('ol.has');
 goog.require('ol.render.canvas');
+goog.require('ol.structs.IHasChecksum');
 goog.require('ol.style.Fill');
 goog.require('ol.style.Image');
 goog.require('ol.style.ImageState');
@@ -62,12 +63,6 @@ ol.style.RegularShape = function(options) {
 
   /**
    * @private
-   * @type {Array.<number>}
-   */
-  this.hitDetectionOrigin_ = [0, 0];
-
-  /**
-   * @private
    * @type {number}
    */
   this.points_ = options.points;
@@ -220,14 +215,6 @@ ol.style.RegularShape.prototype.getOrigin = function() {
 
 
 /**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.getHitDetectionOrigin = function() {
-  return this.hitDetectionOrigin_;
-};
-
-
-/**
  * @return {number} Number of points for stars and regular polygons.
  * @api
  */
@@ -370,12 +357,10 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) {
 
     if (hasCustomHitDetectionImage) {
       this.hitDetectionCanvas_ = info.hitImage;
-      this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
       this.hitDetectionImageSize_ =
           [info.hitImage.width, info.hitImage.height];
     } else {
       this.hitDetectionCanvas_ = this.canvas_;
-      this.hitDetectionOrigin_ = this.origin_;
       this.hitDetectionImageSize_ = [imageSize, imageSize];
     }
   }
diff --git a/src/ol/tilecache.js b/src/ol/tilecache.js
index 931da13..e5af596 100644
--- a/src/ol/tilecache.js
+++ b/src/ol/tilecache.js
@@ -1,6 +1,5 @@
 goog.provide('ol.TileCache');
 
-goog.require('goog.asserts');
 goog.require('ol');
 goog.require('ol.TileRange');
 goog.require('ol.structs.LRUCache');
diff --git a/src/ol/view.js b/src/ol/view.js
index 2a02b6b..37bbdb3 100644
--- a/src/ol/view.js
+++ b/src/ol/view.js
@@ -115,6 +115,7 @@ ol.View = function(opt_options) {
 
   /**
    * @private
+   * @const
    * @type {ol.proj.Projection}
    */
   this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857');
@@ -260,7 +261,7 @@ goog.exportProperty(
  * @return {Array.<number>} Hint.
  */
 ol.View.prototype.getHints = function() {
-  return goog.array.clone(this.hints_);
+  return this.hints_.slice();
 };
 
 
@@ -350,12 +351,12 @@ ol.View.prototype.getResolutionForValueFunction = function(opt_power) {
 
 
 /**
- * @return {number|undefined} The rotation of the view.
+ * @return {number} The rotation of the view.
  * @observable
  * @api stable
  */
 ol.View.prototype.getRotation = function() {
-  return /** @type {number|undefined} */ (this.get(ol.ViewProperty.ROTATION));
+  return /** @type {number} */ (this.get(ol.ViewProperty.ROTATION));
 };
 goog.exportProperty(
     ol.View.prototype,
@@ -401,7 +402,7 @@ ol.View.prototype.getState = function() {
     center: center.slice(),
     projection: goog.isDef(projection) ? projection : null,
     resolution: resolution,
-    rotation: goog.isDef(rotation) ? rotation : 0
+    rotation: rotation
   });
 };
 
@@ -625,7 +626,7 @@ goog.exportProperty(
 
 /**
  * Set the rotation for this view.
- * @param {number|undefined} rotation The rotation of the view.
+ * @param {number} rotation The rotation of the view.
  * @observable
  * @api stable
  */
diff --git a/src/ol/webgl/context.js b/src/ol/webgl/context.js
index 6b91f6c..616a2d7 100644
--- a/src/ol/webgl/context.js
+++ b/src/ol/webgl/context.js
@@ -67,6 +67,24 @@ ol.webgl.Context = function(canvas, gl) {
   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 = goog.array.contains(
@@ -153,6 +171,10 @@ ol.webgl.Context.prototype.disposeInternal = function() {
     goog.object.forEach(this.shaderCache_, function(shader) {
       gl.deleteShader(shader);
     });
+    // delete objects for hit-detection
+    gl.deleteFramebuffer(this.hitDetectionFramebuffer_);
+    gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_);
+    gl.deleteTexture(this.hitDetectionTexture_);
   }
 };
 
@@ -175,6 +197,18 @@ ol.webgl.Context.prototype.getGL = function() {
 
 
 /**
+ * @return {WebGLFramebuffer} The framebuffer for the hit-detection.
+ * @api
+ */
+ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() {
+  if (goog.isNull(this.hitDetectionFramebuffer_)) {
+    this.initHitDetectionFramebuffer_();
+  }
+  return this.hitDetectionFramebuffer_;
+};
+
+
+/**
  * Get shader from the cache if it's in the cache. Otherwise, create
  * the WebGL shader, compile it, and add entry to cache.
  * @param {ol.webgl.Shader} shaderObject Shader object.
@@ -247,6 +281,9 @@ ol.webgl.Context.prototype.handleWebGLContextLost = function() {
   goog.object.clear(this.shaderCache_);
   goog.object.clear(this.programCache_);
   this.currentProgram_ = null;
+  this.hitDetectionFramebuffer_ = null;
+  this.hitDetectionTexture_ = null;
+  this.hitDetectionRenderbuffer_ = null;
 };
 
 
@@ -258,6 +295,34 @@ ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
 
 
 /**
+ * Creates a 1x1 pixel framebuffer for the hit-detection.
+ * @private
+ */
+ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() {
+  var gl = this.gl_;
+  var framebuffer = gl.createFramebuffer();
+  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
+
+  var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1);
+  var renderbuffer = gl.createRenderbuffer();
+  gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
+  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1);
+  gl.framebufferTexture2D(
+      gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
+      gl.RENDERBUFFER, renderbuffer);
+
+  gl.bindTexture(gl.TEXTURE_2D, null);
+  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+
+  this.hitDetectionFramebuffer_ = framebuffer;
+  this.hitDetectionTexture_ = texture;
+  this.hitDetectionRenderbuffer_ = renderbuffer;
+};
+
+
+/**
  * Just return false if that program is used already. Other use
  * that program (call `gl.useProgram`) and make it the "current
  * program".
@@ -282,3 +347,64 @@ ol.webgl.Context.prototype.useProgram = function(program) {
  * @type {goog.log.Logger}
  */
 ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context');
+
+
+/**
+ * @param {WebGLRenderingContext} gl WebGL rendering context.
+ * @param {number=} opt_wrapS wrapS.
+ * @param {number=} opt_wrapT wrapT.
+ * @return {WebGLTexture}
+ * @private
+ */
+ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) {
+  var texture = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, texture);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+
+  if (goog.isDef(opt_wrapS)) {
+    gl.texParameteri(
+        goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, opt_wrapS);
+  }
+  if (goog.isDef(opt_wrapT)) {
+    gl.texParameteri(
+        goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, opt_wrapT);
+  }
+
+  return texture;
+};
+
+
+/**
+ * @param {WebGLRenderingContext} gl WebGL rendering context.
+ * @param {number} width Width.
+ * @param {number} height Height.
+ * @param {number=} opt_wrapS wrapS.
+ * @param {number=} opt_wrapT wrapT.
+ * @return {WebGLTexture}
+ */
+ol.webgl.Context.createEmptyTexture = function(
+    gl, width, height, opt_wrapS, opt_wrapT) {
+  var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT);
+  gl.texImage2D(
+      gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+      null);
+
+  return texture;
+};
+
+
+/**
+ * @param {WebGLRenderingContext} gl WebGL rendering context.
+ * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image.
+ * @param {number=} opt_wrapS wrapS.
+ * @param {number=} opt_wrapT wrapT.
+ * @return {WebGLTexture}
+ */
+ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) {
+  var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT);
+  gl.texImage2D(
+      gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
+
+  return texture;
+};
diff --git a/src/ol/xml.js b/src/ol/xml.js
index 8f7662d..5f2a92b 100644
--- a/src/ol/xml.js
+++ b/src/ol/xml.js
@@ -473,7 +473,7 @@ ol.xml.makeObjectPropertySetter =
           var property = goog.isDef(opt_property) ?
               opt_property : node.localName;
           goog.asserts.assert(goog.isObject(object));
-          goog.object.set(object, property, value);
+          object[property] = value;
         }
       });
 };
@@ -535,8 +535,8 @@ ol.xml.makeArraySerializer = function(nodeWriter, opt_this) {
     if (!goog.isDef(serializersNS)) {
       serializersNS = {};
       var serializers = {};
-      goog.object.set(serializers, node.localName, nodeWriter);
-      goog.object.set(serializersNS, node.namespaceURI, serializers);
+      serializers[node.localName] = nodeWriter;
+      serializersNS[node.namespaceURI] = serializers;
       nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName);
     }
     ol.xml.serialize(serializersNS, nodeFactory, value, objectStack);
diff --git a/test/spec/ol/featureoverlay.test.js b/test/spec/ol/featureoverlay.test.js
index 47aa061..78fc78f 100644
--- a/test/spec/ol/featureoverlay.test.js
+++ b/test/spec/ol/featureoverlay.test.js
@@ -25,10 +25,18 @@ describe('ol.FeatureOverlay', function() {
       expect(featureOverlay.getStyleFunction()()).to.eql(style);
     });
 
+    it('takes a map', function() {
+      var map = new ol.Map({});
+      var featureOverlay = new ol.FeatureOverlay({
+        map: map
+      });
+      expect(featureOverlay.getMap()).to.eql(map);
+    });
   });
 });
 
 goog.require('ol.Feature');
 goog.require('ol.FeatureOverlay');
+goog.require('ol.Map');
 goog.require('ol.geom.Point');
 goog.require('ol.style.Style');
diff --git a/test/spec/ol/format/gml/ogr.xml b/test/spec/ol/format/gml/ogr.xml
new file mode 100644
index 0000000..5ca69e4
--- /dev/null
+++ b/test/spec/ol/format/gml/ogr.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<wfs:FeatureCollection
+     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xmlns:wfs="http://www.opengis.net/wfs"
+     xsi:schemaLocation="http://ogr.maptools.org/ test.xsd"
+     xmlns:ogr="http://ogr.maptools.org/"
+     xmlns:gml="http://www.opengis.net/gml">
+
+                                                                                                                       
+  <gml:featureMember>
+    <ogr:Plaatsbepalingspunt fid="Plaatsbepalingspunt.0">
+		<ogr:geometryProperty>
+			<gml:Point>
+				<gml:pos srsDimension="2">115512.666 479836.28</gml:pos>
+			</gml:Point>
+		</ogr:geometryProperty>
+		<ogr:gml_id>x2</ogr:gml_id>
+		<ogr:namespace>NL.IMGEO</ogr:namespace>
+		<ogr:lokaalID>L0001.A3C177B4105A4FFD82EB80084C8CA732</ogr:lokaalID>
+		<ogr:nauwkeurigheid>60</ogr:nauwkeurigheid>
+		<ogr:datumInwinning>2014-02-14</ogr:datumInwinning>
+		<ogr:inwinnendeInstantie>L0001</ogr:inwinnendeInstantie>
+		<ogr:inwinningsmethode>fotogrammetrisch</ogr:inwinningsmethode>
+    </ogr:Plaatsbepalingspunt>
+  </gml:featureMember>
+</wfs:FeatureCollection>
diff --git a/test/spec/ol/format/gmlformat.test.js b/test/spec/ol/format/gmlformat.test.js
index 2ec050b..d68aad6 100644
--- a/test/spec/ol/format/gmlformat.test.js
+++ b/test/spec/ol/format/gmlformat.test.js
@@ -1087,6 +1087,26 @@ describe('ol.format.GML3', function() {
 
   });
 
+  describe('when parsing from OGR', function() {
+
+    var features;
+    before(function(done) {
+      afterLoadText('spec/ol/format/gml/ogr.xml', function(xml) {
+        try {
+          features = new ol.format.GML().readFeatures(xml);
+        } catch (e) {
+          done(e);
+        }
+        done();
+      });
+    });
+
+    it('reads all features', function() {
+      expect(features.length).to.be(1);
+    });
+
+  });
+
 });
 
 
diff --git a/test/spec/ol/format/gpxformat.test.js b/test/spec/ol/format/gpxformat.test.js
index 8f1483f..986d334 100644
--- a/test/spec/ol/format/gpxformat.test.js
+++ b/test/spec/ol/format/gpxformat.test.js
@@ -7,6 +7,18 @@ describe('ol.format.GPX', function() {
     format = new ol.format.GPX();
   });
 
+  describe('#readProjection', function() {
+    it('returns the default projection from document', function() {
+      var projection = format.readProjectionFromDocument();
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+
+    it('returns the default projection from node', function() {
+      var projection = format.readProjectionFromNode();
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+  });
+
   describe('readFeatures', function() {
 
     describe('rte', function() {
diff --git a/test/spec/ol/format/igcformat.test.js b/test/spec/ol/format/igcformat.test.js
index c3b9c88..c417de9 100644
--- a/test/spec/ol/format/igcformat.test.js
+++ b/test/spec/ol/format/igcformat.test.js
@@ -29,6 +29,13 @@ describe('ol.format.IGC', function() {
     format = new ol.format.IGC();
   });
 
+  describe('#readProjectionFromText', function() {
+    it('returns the default projection', function() {
+      var projection = format.readProjectionFromText(igc);
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+  });
+
   describe('#readFeature', function() {
     it('does not read invalid features', function() {
       expect(format.readFeature('invalid')).to.be(null);
diff --git a/test/spec/ol/format/kmlformat.test.js b/test/spec/ol/format/kmlformat.test.js
index 447ee87..ce3c173 100644
--- a/test/spec/ol/format/kmlformat.test.js
+++ b/test/spec/ol/format/kmlformat.test.js
@@ -8,6 +8,18 @@ describe('ol.format.KML', function() {
     format = new ol.format.KML();
   });
 
+  describe('#readProjection', function() {
+    it('returns the default projection from document', function() {
+      var projection = format.readProjectionFromDocument();
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+
+    it('returns the default projection from node', function() {
+      var projection = format.readProjectionFromNode();
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+  });
+
   describe('#readFeatures', function() {
 
     describe('id', function() {
@@ -2477,6 +2489,45 @@ describe('ol.format.KML', function() {
 
   });
 
+  describe('#readNetworkLinks', function() {
+    it('returns empty array if no network links found', function() {
+      var text =
+          '<kml xmlns="http://www.opengis.net/kml/2.2">' +
+          '  <Document>' +
+          '  </Document>' +
+          '</kml>';
+      var nl = format.readNetworkLinks(text);
+      expect(nl).to.have.length(0);
+    });
+
+    it('returns an array of network links', function() {
+      var text =
+          '<kml xmlns="http://www.opengis.net/kml/2.2">' +
+          '  <Document>' +
+          '    <NetworkLink>' +
+          '      <name>bar</name>' +
+          '      <Link>' +
+          '        <href>bar/bar.kml</href>' +
+          '      </Link>' +
+          '    </NetworkLink>' +
+          '  </Document>' +
+          '  <Folder>' +
+          '    <NetworkLink>' +
+          '      <Link>' +
+          '        <href>http://foo.com/foo.kml</href>' +
+          '      </Link>' +
+          '    </NetworkLink>' +
+          '  </Folder>' +
+          '</kml>';
+      var nl = format.readNetworkLinks(text);
+      expect(nl).to.have.length(2);
+      expect(nl[0].name).to.be('bar');
+      expect(nl[0].href).to.be('bar/bar.kml');
+      expect(nl[1].href).to.be('http://foo.com/foo.kml');
+    });
+
+  });
+
 });
 
 
diff --git a/test/spec/ol/format/osmxmlformat.test.js b/test/spec/ol/format/osmxmlformat.test.js
index 025db6e..da4e34b 100644
--- a/test/spec/ol/format/osmxmlformat.test.js
+++ b/test/spec/ol/format/osmxmlformat.test.js
@@ -8,6 +8,18 @@ describe('ol.format.OSMXML', function() {
     format = new ol.format.OSMXML();
   });
 
+  describe('#readProjection', function() {
+    it('returns the default projection from document', function() {
+      var projection = format.readProjectionFromDocument();
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+
+    it('returns the default projection from node', function() {
+      var projection = format.readProjectionFromNode();
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+  });
+
   describe('#readFeatures', function() {
 
     it('can read an empty document', function() {
diff --git a/test/spec/ol/format/owsformat.test.js b/test/spec/ol/format/owsformat.test.js
index 03adf24..fa2be4a 100644
--- a/test/spec/ol/format/owsformat.test.js
+++ b/test/spec/ol/format/owsformat.test.js
@@ -44,15 +44,15 @@ describe('ol.format.OWS 1.1', function() {
 
     var obj = parser.read(doc);
     expect(obj).to.be.ok();
-    var serviceProvider = obj.serviceProvider;
+    var serviceProvider = obj.ServiceProvider;
     expect(serviceProvider).to.be.ok();
-    expect(serviceProvider.providerName).to.eql('MiraMon');
+    expect(serviceProvider.ProviderName).to.eql('MiraMon');
     var url = 'http://www.creaf.uab.es/miramon';
-    expect(serviceProvider.providerSite).to.eql(url);
+    expect(serviceProvider.ProviderSite).to.eql(url);
     var name = 'Joan Maso Pau';
-    expect(serviceProvider.serviceContact.individualName).to.eql(name);
+    expect(serviceProvider.ServiceContact.IndividualName).to.eql(name);
     var position = 'Senior Software Engineer';
-    expect(serviceProvider.serviceContact.positionName).to.eql(position);
+    expect(serviceProvider.ServiceContact.PositionName).to.eql(position);
   });
 
   it('should read ServiceIdentification tag properly', function() {
@@ -78,11 +78,11 @@ describe('ol.format.OWS 1.1', function() {
     var obj = parser.readFromNode(doc.firstChild);
     expect(obj).to.be.ok();
 
-    var serviceIdentification = obj.serviceIdentification;
+    var serviceIdentification = obj.ServiceIdentification;
     expect(serviceIdentification).to.be.ok();
-    expect(serviceIdentification.title).to.eql('Web Map Tile Service');
-    expect(serviceIdentification.serviceTypeVersion).to.eql('1.0.0');
-    expect(serviceIdentification.serviceType).to.eql('OGC WMTS');
+    expect(serviceIdentification.Title).to.eql('Web Map Tile Service');
+    expect(serviceIdentification.ServiceTypeVersion).to.eql('1.0.0');
+    expect(serviceIdentification.ServiceType).to.eql('OGC WMTS');
   });
 
   it('should read OperationsMetadata tag properly', function() {
@@ -98,6 +98,7 @@ describe('ol.format.OWS 1.1', function() {
                             '<ows:Constraint name="GetEncoding">' +
                                 '<ows:AllowedValues>' +
                                     '<ows:Value>KVP</ows:Value>' +
+                                    '<ows:Value>SOAP</ows:Value>' +
                                 '</ows:AllowedValues>' +
                             '</ows:Constraint>' +
                         '</ows:Get>' +
@@ -130,23 +131,24 @@ describe('ol.format.OWS 1.1', function() {
     var obj = parser.readFromNode(doc.firstChild);
     expect(obj).to.be.ok();
 
-    var operationsMetadata = obj.operationsMetadata;
+    var operationsMetadata = obj.OperationsMetadata;
     expect(operationsMetadata).to.be.ok();
-    var dcp = operationsMetadata.GetCapabilities.dcp;
+    var getCap = operationsMetadata.GetCapabilities;
+    var dcp = getCap.DCP;
     var url = 'http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?';
-    expect(dcp.http.get[0].url).to.eql(url);
-    dcp = operationsMetadata.GetCapabilities.dcp;
-    expect(dcp.http.get[0].constraints.GetEncoding.allowedValues).to.eql(
-        {'KVP': true});
+    expect(dcp.HTTP.Get[0].href).to.eql(url);
+    expect(dcp.HTTP.Get[0].Constraint[0].name).to.eql('GetEncoding');
+    expect(dcp.HTTP.Get[0].Constraint[0].AllowedValues.Value[0]).to.eql('KVP');
+
     url = 'http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?';
-    dcp = operationsMetadata.GetFeatureInfo.dcp;
-    expect(dcp.http.get[0].url).to.eql(url);
-    dcp = operationsMetadata.GetFeatureInfo.dcp;
-    expect(dcp.http.get[0].constraints).to.be(undefined);
+    dcp = operationsMetadata.GetFeatureInfo.DCP;
+    expect(dcp.HTTP.Get[0].href).to.eql(url);
+    expect(dcp.HTTP.Get[0].Constraint).to.be(undefined);
+
     url = 'http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?';
-    expect(operationsMetadata.GetTile.dcp.http.get[0].url).to.eql(url);
-    dcp = operationsMetadata.GetTile.dcp;
-    expect(dcp.http.get[0].constraints).to.be(undefined);
+    dcp = operationsMetadata.GetTile.DCP;
+    expect(dcp.HTTP.Get[0].href).to.eql(url);
+    expect(dcp.HTTP.Get[0].Constraint).to.be(undefined);
   });
 
 });
diff --git a/test/spec/ol/format/polylineformat.test.js b/test/spec/ol/format/polylineformat.test.js
index 1137d7c..f6ce417 100644
--- a/test/spec/ol/format/polylineformat.test.js
+++ b/test/spec/ol/format/polylineformat.test.js
@@ -40,6 +40,12 @@ describe('ol.format.Polyline', function() {
   // Reset testing data
   beforeEach(resetTestingData);
 
+  describe('#readProjectionFromText', function() {
+    it('returns the default projection', function() {
+      var projection = format.readProjectionFromText(encodedFlatPoints);
+      expect(projection).to.eql(ol.proj.get('EPSG:4326'));
+    });
+  });
 
   describe('encodeDeltas', function() {
     it('returns expected value', function() {
diff --git a/test/spec/ol/format/wfs/EmptyFeatureCollection.xml b/test/spec/ol/format/wfs/EmptyFeatureCollection.xml
new file mode 100644
index 0000000..631df2b
--- /dev/null
+++ b/test/spec/ol/format/wfs/EmptyFeatureCollection.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+   xmlns:rws="http://mapserver.gis.umn.edu/mapserver"
+   xmlns:gml="http://www.opengis.net/gml"
+   xmlns:wfs="http://www.opengis.net/wfs"
+   xmlns:ogc="http://www.opengis.net/ogc"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://mapserver.gis.umn.edu/mapserver http://intranet.rijkswaterstaat.nl/services/geoservices/nwb_wegen?SERVICE=WFS&VERSION=1.1.0&REQUEST=DescribeFeatureType&TYPENAME=feature:AAA64&OUTPUTFORMAT=text/xml; subtype=gml/3.1.1  http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" numberOfFeatures="0">
+</wfs:FeatureCollection>
diff --git a/test/spec/ol/format/wfsformat.test.js b/test/spec/ol/format/wfsformat.test.js
index 09a3f6e..a13def7 100644
--- a/test/spec/ol/format/wfsformat.test.js
+++ b/test/spec/ol/format/wfsformat.test.js
@@ -90,6 +90,21 @@ describe('ol.format.WFS', function() {
   });
 
   describe('when parsing FeatureCollection', function() {
+    var xml;
+    before(function(done) {
+      afterLoadText('spec/ol/format/wfs/EmptyFeatureCollection.xml',
+          function(_xml) {
+            xml = _xml;
+            done();
+          });
+    });
+    it('returns an empty array of features when none exist', function() {
+      var result = new ol.format.WFS().readFeatures(xml);
+      expect(result).to.have.length(0);
+    });
+  });
+
+  describe('when parsing FeatureCollection', function() {
     var response;
     before(function(done) {
       afterLoadText('spec/ol/format/wfs/NumberOfFeatures.xml',
diff --git a/test/spec/ol/format/wktformat.test.js b/test/spec/ol/format/wktformat.test.js
index 0012023..705062e 100644
--- a/test/spec/ol/format/wktformat.test.js
+++ b/test/spec/ol/format/wktformat.test.js
@@ -4,6 +4,13 @@ describe('ol.format.WKT', function() {
 
   var format = new ol.format.WKT();
 
+  describe('#readProjectionFromText', function() {
+    it('returns the default projection', function() {
+      var projection = format.readProjectionFromText('POINT(1 2)');
+      expect(projection).to.be(null);
+    });
+  });
+
   describe('#readGeometry()', function() {
 
     it('transforms with dataProjection and featureProjection', function() {
diff --git a/test/spec/ol/format/wmts/ogcsample.xml b/test/spec/ol/format/wmts/ogcsample.xml
new file mode 100644
index 0000000..c122fca
--- /dev/null
+++ b/test/spec/ol/format/wmts/ogcsample.xml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities version="1.0.0" xmlns="http://www.opengis.net/wmts/1.0" xmlns:gml="http://www.opengis.net/gml" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0.0/wmtsGetCapabilities_response.xsd">
+	<ows:ServiceIdentification>
+		<ows:Title>Web Map Tile Service</ows:Title>
+		<ows:Abstract>Service that contrains the map
+access interface to some TileMatrixSets</ows:Abstract>
+		<ows:Keywords>
+			<ows:Keyword>tile</ows:Keyword>
+			<ows:Keyword>tile matrix set</ows:Keyword>
+			<ows:Keyword>map</ows:Keyword>
+		</ows:Keywords>
+		<ows:ServiceType>OGC WMTS</ows:ServiceType>
+		<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
+		<ows:Fees>none</ows:Fees>
+		<ows:AccessConstraints>none</ows:AccessConstraints>
+	</ows:ServiceIdentification>
+	<ows:ServiceProvider>
+		<ows:ProviderName>MiraMon</ows:ProviderName>
+		<ows:ProviderSite xlink:href="http://www.creaf.uab.cat/miramon"/>
+		<ows:ServiceContact>
+			<ows:IndividualName>Joan Maso Pau</ows:IndividualName>
+			<ows:PositionName>Senior Software Engineer</ows:PositionName>
+			<ows:ContactInfo>
+				<ows:Phone>
+					<ows:Voice>+34 93 581 1312</ows:Voice>
+					<ows:Facsimile>+34 93 581 4151</ows:Facsimile>
+				</ows:Phone>
+				<ows:Address>
+					<ows:DeliveryPoint>Fac Ciencies UAB</ows:DeliveryPoint>
+					<ows:City>Bellaterra</ows:City>
+					<ows:AdministrativeArea>Barcelona
+</ows:AdministrativeArea>
+					<ows:PostalCode>08193</ows:PostalCode>
+					<ows:Country>Spain</ows:Country>
+					<ows:ElectronicMailAddress>joan.maso at uab.cat
+</ows:ElectronicMailAddress>
+				</ows:Address>
+			</ows:ContactInfo>
+		</ows:ServiceContact>
+	</ows:ServiceProvider>
+	<ows:OperationsMetadata>
+		<ows:Operation name="GetCapabilities">
+			<ows:DCP>
+				<ows:HTTP>
+					<ows:Get xlink:href="http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?">
+						<ows:Constraint name="GetEncoding">
+							<ows:AllowedValues>
+								<ows:Value>KVP</ows:Value>
+                                <ows:Value>SOAP</ows:Value>
+							</ows:AllowedValues>
+						</ows:Constraint>
+					</ows:Get>
+				</ows:HTTP>
+			</ows:DCP>
+		</ows:Operation>
+		<ows:Operation name="GetTile">
+			<ows:DCP>
+				<ows:HTTP>
+					<ows:Get xlink:href="http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?">
+						<ows:Constraint name="GetEncoding">
+							<ows:AllowedValues>
+								<ows:Value>KVP</ows:Value>
+							</ows:AllowedValues>
+						</ows:Constraint>
+					</ows:Get>
+				</ows:HTTP>
+			</ows:DCP>
+		</ows:Operation>
+	</ows:OperationsMetadata>
+	<Contents>
+		<Layer>
+			<ows:Title>Blue Marble Next Generation</ows:Title>
+			<ows:Abstract>Blue Marble Next Generation NASA Product
+</ows:Abstract>
+			<ows:WGS84BoundingBox>
+				<ows:LowerCorner>-180 -90</ows:LowerCorner>
+				<ows:UpperCorner>180 90</ows:UpperCorner>
+			</ows:WGS84BoundingBox>
+			<ows:Identifier>BlueMarbleNextGeneration</ows:Identifier>
+			<Style isDefault="true">
+				<ows:Title>Dark Blue</ows:Title>
+				<ows:Identifier>DarkBlue</ows:Identifier>
+				<LegendURL format="image/png" xlink:href="http://www.miramon.uab.es/wmts/Coastlines/coastlines_darkBlue.png"/>
+			</Style>
+			<Style>
+				<ows:Title>Thick And Red</ows:Title>
+				<ows:Abstract>Specify this style if you want your maps to have thick red coastlines.
+</ows:Abstract>
+				<ows:Identifier>thickAndRed</ows:Identifier>
+			</Style>
+			<Format>image/jpeg</Format>
+			<Format>image/gif</Format>
+			<TileMatrixSetLink>
+				<TileMatrixSet>BigWorldPixel</TileMatrixSet>
+			</TileMatrixSetLink>
+			<ResourceURL format="image/png" resourceType="tile" template="http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png"/>
+			<ResourceURL format="application/gml+xml; version=3.1" resourceType="FeatureInfo" template="http://www.example.com/wmts/coastlines/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}/{J}/{I}.xml"/>
+		</Layer>
+		<TileMatrixSet>
+			<ows:Identifier>BigWorldPixel</ows:Identifier>
+			<ows:SupportedCRS>urn:ogc:def:crs:OGC:1.3:CRS84
+</ows:SupportedCRS>
+			<WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Pixel
+</WellKnownScaleSet>
+			<TileMatrix>
+				<ows:Identifier>10000m</ows:Identifier>
+				<ScaleDenominator>33130800.83133142</ScaleDenominator>
+				<TopLeftCorner>-180 90</TopLeftCorner>
+				<TileWidth>640</TileWidth>
+				<TileHeight>480</TileHeight>
+				<MatrixWidth>7</MatrixWidth>
+				<MatrixHeight>5</MatrixHeight>
+			</TileMatrix>
+			<TileMatrix>
+				<ows:Identifier>20000m</ows:Identifier>
+				<ScaleDenominator>66261601.66266284</ScaleDenominator>
+				<TopLeftCorner>-180 90</TopLeftCorner>
+				<TileWidth>640</TileWidth>
+				<TileHeight>480</TileHeight>
+				<MatrixWidth>4</MatrixWidth>
+				<MatrixHeight>3</MatrixHeight>
+			</TileMatrix>
+			<TileMatrix>
+				<ows:Identifier>40000m</ows:Identifier>
+				<ScaleDenominator>132523203.3253257</ScaleDenominator>
+				<TopLeftCorner>-180 90</TopLeftCorner>
+				<TileWidth>640</TileWidth>
+				<TileHeight>480</TileHeight>
+				<MatrixWidth>2</MatrixWidth>
+				<MatrixHeight>2</MatrixHeight>
+			</TileMatrix>
+			<TileMatrix>
+				<ows:Identifier>60000m</ows:Identifier>
+				<ScaleDenominator>198784804.9879885</ScaleDenominator>
+				<TopLeftCorner>-180 90</TopLeftCorner>
+				<TileWidth>640</TileWidth>
+				<TileHeight>480</TileHeight>
+				<MatrixWidth>1</MatrixWidth>
+				<MatrixHeight>1</MatrixHeight>
+			</TileMatrix>
+			<TileMatrix>
+				<ows:Identifier>120000m</ows:Identifier>
+				<ScaleDenominator>397569609.9759771</ScaleDenominator>
+				<TopLeftCorner>-180 90</TopLeftCorner>
+				<TileWidth>640</TileWidth>
+				<TileHeight>480</TileHeight>
+				<MatrixWidth>1</MatrixWidth>
+				<MatrixHeight>1</MatrixHeight>
+			</TileMatrix>
+			<TileMatrix>
+				<ows:Identifier>240000m</ows:Identifier>
+				<ScaleDenominator>795139219.9519541</ScaleDenominator>
+				<TopLeftCorner>-180 90</TopLeftCorner>
+				<TileWidth>640</TileWidth>
+				<TileHeight>480</TileHeight>
+				<MatrixWidth>1</MatrixWidth>
+				<MatrixHeight>1</MatrixHeight>
+			</TileMatrix>
+		</TileMatrixSet>
+		<TileMatrixSet>
+            <ows:Identifier>BigWorld</ows:Identifier>
+            <ows:SupportedCRS>urn:ogc:def:crs:OGC:1.3:CRS84</ows:SupportedCRS>
+            <TileMatrix>
+                <ows:Identifier>1e6</ows:Identifier>
+                <ScaleDenominator>1e6</ScaleDenominator>
+                <TopLeftCorner>-180 84</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>60000</MatrixWidth>
+                <MatrixHeight>50000</MatrixHeight>
+            </TileMatrix>
+            <TileMatrix>
+                <ows:Identifier>2.5e6</ows:Identifier>
+                <ScaleDenominator>2.5e6</ScaleDenominator>
+                <TopLeftCorner>-180 84</TopLeftCorner>
+                <TileWidth>256</TileWidth>
+                <TileHeight>256</TileHeight>
+                <MatrixWidth>9000</MatrixWidth>
+                <MatrixHeight>7000</MatrixHeight>
+            </TileMatrix>
+        </TileMatrixSet>
+	</Contents>
+	<ServiceMetadataURL xlink:href="http://www.maps.bob/wmts/1.0.0/WMTSCapabilities.xml"/>
+</Capabilities>
diff --git a/test/spec/ol/format/wmtscapabilitiesformat.test.js b/test/spec/ol/format/wmtscapabilitiesformat.test.js
new file mode 100644
index 0000000..9ca27ad
--- /dev/null
+++ b/test/spec/ol/format/wmtscapabilitiesformat.test.js
@@ -0,0 +1,106 @@
+goog.provide('ol.test.format.WMTSCapabilities');
+
+describe('ol.format.WMTSCapabilities', function() {
+
+  describe('when parsing ogcsample.xml', function() {
+
+    var parser = new ol.format.WMTSCapabilities();
+    var capabilities;
+    before(function(done) {
+      afterLoadText('spec/ol/format/wmts/ogcsample.xml', function(xml) {
+        try {
+          capabilities = parser.read(xml);
+        } catch (e) {
+          done(e);
+        }
+        done();
+      });
+    });
+
+    it('can read Capability.Contents.Layer', function() {
+
+      expect(capabilities.Contents.Layer).to.be.an('array');
+      expect(capabilities.Contents.Layer).to.have.length(1);
+
+
+      var layer = capabilities.Contents.Layer[0];
+      expect(layer.Abstract).to.be
+        .eql('Blue Marble Next Generation NASA Product');
+      expect(layer.Identifier).to.be.eql('BlueMarbleNextGeneration');
+      expect(layer.Title).to.be.eql('Blue Marble Next Generation');
+
+      expect(layer.Format).to.be.an('array');
+      expect(layer.Format).to.have.length(2);
+      expect(layer.Format[0]).to.be.eql('image/jpeg');
+
+      expect(layer.Style).to.be.an('array');
+      expect(layer.Style).to.have.length(2);
+      expect(layer.Style[0].Identifier).to.be.eql('DarkBlue');
+      expect(layer.Style[0].isDefault).to.be(true);
+      expect(layer.Style[0].Title).to.be.eql('Dark Blue');
+      expect(layer.Style[0].LegendURL[0].href).to.be
+        .eql('http://www.miramon.uab.es/wmts/Coastlines/' +
+          'coastlines_darkBlue.png');
+      expect(layer.Style[0].LegendURL[0].format).to.be.eql('image/png');
+
+      expect(layer.TileMatrixSetLink).to.be.an('array');
+      expect(layer.TileMatrixSetLink).to.have.length(1);
+      expect(layer.TileMatrixSetLink[0].TileMatrixSet).to.be
+        .eql('BigWorldPixel');
+
+      var wgs84Bbox = layer.WGS84BoundingBox;
+      expect(wgs84Bbox).to.be.an('array');
+      expect(wgs84Bbox[0]).to.be.eql(-180);
+      expect(wgs84Bbox[2]).to.be.eql(180);
+      expect(wgs84Bbox[1]).to.be.eql(-90);
+      expect(wgs84Bbox[3]).to.be.eql(90.0);
+
+      expect(layer.ResourceURL).to.be.an('array');
+      expect(layer.ResourceURL).to.have.length(2);
+      expect(layer.ResourceURL[0].format).to.be.eql('image/png');
+      expect(layer.ResourceURL[0].template).to.be
+        .eql('http://www.example.com/wmts/coastlines/{TileMatrix}' +
+          '/{TileRow}/{TileCol}.png');
+
+    });
+
+    it('Can read Capabilities.Content.TileMatrixSet', function() {
+      expect(capabilities.Contents.TileMatrixSet).to.be.ok();
+
+      var bigWorld = capabilities.Contents.TileMatrixSet[1];
+      expect(bigWorld).to.be.ok();
+      expect(bigWorld.Identifier).to.be.eql('BigWorld');
+      expect(bigWorld.SupportedCRS).to.be.eql('urn:ogc:def:crs:OGC:1.3:CRS84');
+      expect(bigWorld.TileMatrix).to.have.length(2);
+      expect(bigWorld.TileMatrix[0].Identifier).to.be.eql('1e6');
+      expect(bigWorld.TileMatrix[0].MatrixHeight).to.be.eql(50000);
+      expect(bigWorld.TileMatrix[0].MatrixWidth).to.be.eql(60000);
+      expect(bigWorld.TileMatrix[0].ScaleDenominator).to.be.eql(1000000);
+      expect(bigWorld.TileMatrix[0].TileWidth).to.be.eql(256);
+      expect(bigWorld.TileMatrix[0].TileHeight).to.be.eql(256);
+      expect(bigWorld.TileMatrix[0].TopLeftCorner).to.be.a('array');
+      expect(bigWorld.TileMatrix[0].TopLeftCorner[0]).to.be.eql(-180);
+      expect(bigWorld.TileMatrix[0].TopLeftCorner[1]).to.be.eql(84);
+      expect(bigWorld.TileMatrix[1].Identifier).to.be.eql('2.5e6');
+      expect(bigWorld.TileMatrix[1].MatrixHeight).to.be.eql(7000);
+      expect(bigWorld.TileMatrix[1].MatrixWidth).to.be.eql(9000);
+      expect(bigWorld.TileMatrix[1].ScaleDenominator).to.be.eql(2500000);
+      expect(bigWorld.TileMatrix[1].TileWidth).to.be.eql(256);
+      expect(bigWorld.TileMatrix[1].TileHeight).to.be.eql(256);
+      expect(bigWorld.TileMatrix[1].TopLeftCorner).to.be.a('array');
+      expect(bigWorld.TileMatrix[1].TopLeftCorner[0]).to.be.eql(-180);
+      expect(bigWorld.TileMatrix[1].TopLeftCorner[1]).to.be.eql(84);
+
+
+    });
+
+    it('Can read OWS tags', function() {
+      expect(capabilities.ServiceIdentification).to.be.ok();
+      expect(capabilities.OperationsMetadata).to.be.ok();
+
+    });
+
+  });
+});
+
+goog.require('ol.format.WMTSCapabilities');
diff --git a/test/spec/ol/geom/flat/reverseflatgeom.js b/test/spec/ol/geom/flat/reverseflatgeom.test.js
similarity index 100%
rename from test/spec/ol/geom/flat/reverseflatgeom.js
rename to test/spec/ol/geom/flat/reverseflatgeom.test.js
diff --git a/test/spec/ol/layer/layer.test.js b/test/spec/ol/layer/layer.test.js
index 93cee8f..3ab583d 100644
--- a/test/spec/ol/layer/layer.test.js
+++ b/test/spec/ol/layer/layer.test.js
@@ -46,6 +46,14 @@ describe('ol.layer.Layer', function() {
       expect(layer.getVisible()).to.be(true);
     });
 
+    it('provides default max resolution', function() {
+      expect(layer.getMaxResolution()).to.be(Infinity);
+    });
+
+    it('provides default min resolution', function() {
+      expect(layer.getMinResolution()).to.be(0);
+    });
+
     it('provides default layerState', function() {
       expect(layer.getLayerState()).to.eql({
         layer: layer,
diff --git a/test/spec/ol/layer/tilelayer.test.js b/test/spec/ol/layer/tilelayer.test.js
new file mode 100644
index 0000000..7b7b302
--- /dev/null
+++ b/test/spec/ol/layer/tilelayer.test.js
@@ -0,0 +1,37 @@
+goog.provide('ol.test.layer.Tile');
+
+describe('ol.layer.Tile', function() {
+
+  describe('constructor (defaults)', function() {
+
+    var layer;
+
+    beforeEach(function() {
+      layer = new ol.layer.Tile({
+        source: new ol.source.OSM()
+      });
+    });
+
+    afterEach(function() {
+      goog.dispose(layer);
+    });
+
+    it('creates an instance', function() {
+      expect(layer).to.be.a(ol.layer.Tile);
+    });
+
+    it('provides default preload', function() {
+      expect(layer.getPreload()).to.be(0);
+    });
+
+    it('provides default useInterimTilesOnError', function() {
+      expect(layer.getUseInterimTilesOnError()).to.be(true);
+    });
+
+  });
+
+});
+
+goog.require('goog.dispose');
+goog.require('ol.layer.Tile');
+goog.require('ol.source.OSM');
diff --git a/test/spec/ol/mapbrowserevent.test.js b/test/spec/ol/mapbrowserevent.test.js
index c046545..a72b45d 100644
--- a/test/spec/ol/mapbrowserevent.test.js
+++ b/test/spec/ol/mapbrowserevent.test.js
@@ -96,22 +96,22 @@ describe('ol.MapBrowserEventHandler', function() {
 
   });
 
-  describe('#getDown()', function() {
+  describe('#down_', function() {
 
     var handler;
     beforeEach(function() {
       handler = new ol.MapBrowserEventHandler(new ol.Map({}));
     });
 
-    it('returns null if no "down" type event has been handled', function() {
-      expect(handler.getDown()).to.be(null);
+    it('is null if no "down" type event has been handled', function() {
+      expect(handler.down_).to.be(null);
     });
 
-    it('returns an event after handlePointerDown_ has been called', function() {
+    it('is an event after handlePointerDown_ has been called', function() {
       var event = new ol.pointer.PointerEvent('pointerdown',
           new goog.events.BrowserEvent({}));
       handler.handlePointerDown_(event);
-      expect(handler.getDown()).to.be(event);
+      expect(handler.down_).to.be(event);
     });
 
   });
diff --git a/test/spec/ol/render/vector.test.js b/test/spec/ol/render/vector.test.js
index eef8d0f..826d3ae 100644
--- a/test/spec/ol/render/vector.test.js
+++ b/test/spec/ol/render/vector.test.js
@@ -3,37 +3,40 @@ goog.provide('ol.test.renderer.vector');
 describe('ol.renderer.vector', function() {
   describe('#renderFeature', function() {
     var replayGroup;
+    var feature, iconStyle, style, squaredTolerance, listener, listenerThis;
+    var iconStyleLoadSpy;
 
     beforeEach(function() {
       replayGroup = new ol.render.canvas.ReplayGroup(1);
+      feature = new ol.Feature();
+      iconStyle = new ol.style.Icon({
+        src: 'http://example.com/icon.png'
+      });
+      style = new ol.style.Style({
+        image: iconStyle,
+        fill: new ol.style.Fill({}),
+        stroke: new ol.style.Stroke({})
+      });
+      squaredTolerance = 1;
+      listener = function() {};
+      listenerThis = {};
+      iconStyleLoadSpy = sinon.stub(iconStyle, 'load', function() {
+        iconStyle.iconImage_.imageState_ = ol.style.ImageState.LOADING;
+      });
+    });
+
+    afterEach(function() {
+      iconStyleLoadSpy.restore();
     });
 
     describe('call multiple times', function() {
 
       it('does not set multiple listeners', function() {
-        var iconStyle = new ol.style.Icon({
-          src: 'http://example.com/icon.png'
-        });
-
-        var iconImage = iconStyle.iconImage_;
-
-        var iconStyleLoadSpy = sinon.stub(iconStyle, 'load', function() {
-          iconImage.imageState_ = ol.style.ImageState.LOADING;
-        });
-
-        var style = new ol.style.Style({
-          image: iconStyle
-        });
-
-        var feature = new ol.Feature();
-
-        var listener = function() {};
-        var listenerThis = {};
         var listeners;
 
         // call #1
         ol.renderer.vector.renderFeature(replayGroup, feature,
-            style, 1, listener, listenerThis);
+            style, squaredTolerance, listener, listenerThis);
 
         expect(iconStyleLoadSpy.calledOnce).to.be.ok();
         listeners = goog.events.getListeners(
@@ -42,7 +45,7 @@ describe('ol.renderer.vector', function() {
 
         // call #2
         ol.renderer.vector.renderFeature(replayGroup, feature,
-            style, 1, listener, listenerThis);
+            style, squaredTolerance, listener, listenerThis);
 
         expect(iconStyleLoadSpy.calledOnce).to.be.ok();
         listeners = goog.events.getListeners(
@@ -52,14 +55,119 @@ describe('ol.renderer.vector', function() {
 
     });
 
+    describe('call renderFeature with a loading icon', function() {
+
+      it('does not render the point', function() {
+        feature.setGeometry(new ol.geom.Point([0, 0]));
+        var imageReplay = replayGroup.getReplay(
+            style.getZIndex(), ol.render.ReplayType.IMAGE);
+        var setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
+        var drawPointGeometrySpy = sinon.stub(imageReplay,
+            'drawPointGeometry', goog.nullFunction);
+        ol.renderer.vector.renderFeature(replayGroup, feature,
+            style, squaredTolerance, listener, listenerThis);
+        expect(setImageStyleSpy.called).to.be(false);
+        setImageStyleSpy.restore();
+        drawPointGeometrySpy.restore();
+      });
+
+      it('does not render the multipoint', function() {
+        feature.setGeometry(new ol.geom.MultiPoint([[0, 0], [1, 1]]));
+        var imageReplay = replayGroup.getReplay(
+            style.getZIndex(), ol.render.ReplayType.IMAGE);
+        var setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
+        var drawMultiPointGeometrySpy = sinon.stub(imageReplay,
+            'drawMultiPointGeometry', goog.nullFunction);
+        ol.renderer.vector.renderFeature(replayGroup, feature,
+            style, squaredTolerance, listener, listenerThis);
+        expect(setImageStyleSpy.called).to.be(false);
+        setImageStyleSpy.restore();
+        drawMultiPointGeometrySpy.restore();
+      });
+
+      it('does render the linestring', function() {
+        feature.setGeometry(new ol.geom.LineString([[0, 0], [1, 1]]));
+        var lineStringReplay = replayGroup.getReplay(
+            style.getZIndex(), ol.render.ReplayType.LINE_STRING);
+        var setFillStrokeStyleSpy = sinon.spy(lineStringReplay,
+            'setFillStrokeStyle');
+        var drawLineStringGeometrySpy = sinon.stub(lineStringReplay,
+            'drawLineStringGeometry', goog.nullFunction);
+        ol.renderer.vector.renderFeature(replayGroup, feature,
+            style, squaredTolerance, listener, listenerThis);
+        expect(setFillStrokeStyleSpy.called).to.be(true);
+        expect(drawLineStringGeometrySpy.called).to.be(true);
+        setFillStrokeStyleSpy.restore();
+        drawLineStringGeometrySpy.restore();
+      });
+
+      it('does render the multilinestring', function() {
+        feature.setGeometry(new ol.geom.MultiLineString([[[0, 0], [1, 1]]]));
+        var lineStringReplay = replayGroup.getReplay(
+            style.getZIndex(), ol.render.ReplayType.LINE_STRING);
+        var setFillStrokeStyleSpy = sinon.spy(lineStringReplay,
+            'setFillStrokeStyle');
+        var drawMultiLineStringGeometrySpy = sinon.stub(lineStringReplay,
+            'drawMultiLineStringGeometry', goog.nullFunction);
+        ol.renderer.vector.renderFeature(replayGroup, feature,
+            style, squaredTolerance, listener, listenerThis);
+        expect(setFillStrokeStyleSpy.called).to.be(true);
+        expect(drawMultiLineStringGeometrySpy.called).to.be(true);
+        setFillStrokeStyleSpy.restore();
+        drawMultiLineStringGeometrySpy.restore();
+      });
+
+      it('does render the polygon', function() {
+        feature.setGeometry(new ol.geom.Polygon(
+            [[[0, 0], [1, 1], [1, 0], [0, 0]]]));
+        var polygonReplay = replayGroup.getReplay(
+            style.getZIndex(), ol.render.ReplayType.POLYGON);
+        var setFillStrokeStyleSpy = sinon.spy(polygonReplay,
+            'setFillStrokeStyle');
+        var drawPolygonGeometrySpy = sinon.stub(polygonReplay,
+            'drawPolygonGeometry', goog.nullFunction);
+        ol.renderer.vector.renderFeature(replayGroup, feature,
+            style, squaredTolerance, listener, listenerThis);
+        expect(setFillStrokeStyleSpy.called).to.be(true);
+        expect(drawPolygonGeometrySpy.called).to.be(true);
+        setFillStrokeStyleSpy.restore();
+        drawPolygonGeometrySpy.restore();
+      });
+
+      it('does render the multipolygon', function() {
+        feature.setGeometry(new ol.geom.MultiPolygon(
+            [[[[0, 0], [1, 1], [1, 0], [0, 0]]]]));
+        var polygonReplay = replayGroup.getReplay(
+            style.getZIndex(), ol.render.ReplayType.POLYGON);
+        var setFillStrokeStyleSpy = sinon.spy(polygonReplay,
+            'setFillStrokeStyle');
+        var drawMultiPolygonGeometrySpy = sinon.stub(polygonReplay,
+            'drawMultiPolygonGeometry', goog.nullFunction);
+        ol.renderer.vector.renderFeature(replayGroup, feature,
+            style, squaredTolerance, listener, listenerThis);
+        expect(setFillStrokeStyleSpy.called).to.be(true);
+        expect(drawMultiPolygonGeometrySpy.called).to.be(true);
+        setFillStrokeStyleSpy.restore();
+        drawMultiPolygonGeometrySpy.restore();
+      });
+    });
+
   });
 });
 
 goog.require('goog.events');
 goog.require('goog.events.EventType');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.MultiPoint');
+goog.require('ol.geom.MultiPolygon');
 goog.require('ol.render.canvas.ReplayGroup');
 goog.require('ol.renderer.vector');
+goog.require('ol.style.Fill');
 goog.require('ol.style.Icon');
 goog.require('ol.style.ImageState');
+goog.require('ol.style.Stroke');
 goog.require('ol.style.Style');
 goog.require('ol.Feature');
diff --git a/test/spec/ol/render/webglreplay.test.js b/test/spec/ol/render/webglreplay.test.js
index 8e60018..838004e 100644
--- a/test/spec/ol/render/webglreplay.test.js
+++ b/test/spec/ol/render/webglreplay.test.js
@@ -16,9 +16,15 @@ describe('ol.render.webgl.ImageReplay', function() {
     imageStyle.getImage = function() {
       return image;
     };
+    imageStyle.getHitDetectionImage = function() {
+      return image;
+    };
     imageStyle.getImageSize = function() {
       return [512, 512];
     };
+    imageStyle.getHitDetectionImageSize = function() {
+      return [512, 512];
+    };
     imageStyle.getOrigin = function() {
       return [200, 200];
     };
@@ -59,14 +65,20 @@ describe('ol.render.webgl.ImageReplay', function() {
       expect(replay.width_).to.be(256);
       expect(replay.images_).to.have.length(1);
       expect(replay.groupIndices_).to.have.length(0);
+      expect(replay.hitDetectionImages_).to.have.length(1);
+      expect(replay.hitDetectionGroupIndices_).to.have.length(0);
 
       replay.setImageStyle(imageStyle1);
       expect(replay.images_).to.have.length(1);
       expect(replay.groupIndices_).to.have.length(0);
+      expect(replay.hitDetectionImages_).to.have.length(1);
+      expect(replay.hitDetectionGroupIndices_).to.have.length(0);
 
       replay.setImageStyle(imageStyle2);
       expect(replay.images_).to.have.length(2);
       expect(replay.groupIndices_).to.have.length(1);
+      expect(replay.hitDetectionImages_).to.have.length(2);
+      expect(replay.hitDetectionGroupIndices_).to.have.length(1);
     });
   });
 
diff --git a/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js b/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js
index 65971bb..2457dae 100644
--- a/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js
+++ b/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js
@@ -58,7 +58,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
 
   });
 
-  describe('#forEachFeatureAtPixel', function() {
+  describe('#forEachFeatureAtCoordinate', function() {
     var renderer;
 
     beforeEach(function() {
@@ -70,8 +70,8 @@ describe('ol.renderer.canvas.VectorLayer', function() {
           map.getRenderer(), layer);
       var replayGroup = {};
       renderer.replayGroup_ = replayGroup;
-      replayGroup.forEachGeometryAtPixel = function(resolution,
-          rotation, coordinate, skippedFeaturesUids, callback) {
+      replayGroup.forEachFeatureAtCoordinate = function(coordinate,
+          resolution, rotation, skippedFeaturesUids, callback) {
         var geometry = new ol.geom.Point([0, 0]);
         var feature = new ol.Feature();
         callback(geometry, feature);
@@ -89,7 +89,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
           rotation: 0
         }
       };
-      renderer.forEachFeatureAtPixel(
+      renderer.forEachFeatureAtCoordinate(
           coordinate, frameState, spy, undefined);
       expect(spy.callCount).to.be(1);
     });
diff --git a/test/spec/ol/renderer/layerrenderer.test.js b/test/spec/ol/renderer/layerrenderer.test.js
new file mode 100644
index 0000000..d16f242
--- /dev/null
+++ b/test/spec/ol/renderer/layerrenderer.test.js
@@ -0,0 +1,89 @@
+goog.provide('ol.test.renderer.Layer');
+
+describe('ol.renderer.Layer', function() {
+  var renderer;
+  var eventType = goog.events.EventType.CHANGE;
+
+  beforeEach(function() {
+    var map = new ol.Map({});
+    var mapRenderer = map.getRenderer();
+    var layer = new ol.layer.Layer({});
+    renderer = new ol.renderer.Layer(mapRenderer, layer);
+  });
+
+  describe('#loadImage', function() {
+    var image;
+    var imageLoadFunction;
+
+    beforeEach(function() {
+      var extent = [];
+      var resolution = 1;
+      var pixelRatio = 1;
+      var attributions = [];
+      var src = '';
+      var crossOrigin = '';
+      imageLoadFunction = sinon.spy();
+      image = new ol.Image(extent, resolution, pixelRatio, attributions,
+          src, crossOrigin, imageLoadFunction);
+    });
+
+    describe('load IDLE image', function() {
+
+      it('returns false', function() {
+        var loaded = renderer.loadImage(image);
+        expect(loaded).to.be(false);
+      });
+
+      it('registers a listener', function() {
+        renderer.loadImage(image);
+        var listeners = image.getListeners(eventType, false);
+        expect(listeners).to.have.length(1);
+      });
+
+    });
+
+    describe('load LOADED image', function() {
+
+      it('returns true', function() {
+        image.state = ol.ImageState.LOADED;
+        var loaded = renderer.loadImage(image);
+        expect(loaded).to.be(true);
+      });
+
+      it('does not register a listener', function() {
+        image.state = ol.ImageState.LOADED;
+        var loaded = renderer.loadImage(image);
+        expect(loaded).to.be(true);
+      });
+
+    });
+
+    describe('load LOADING image', function() {
+
+      beforeEach(function() {
+        renderer.loadImage(image);
+        expect(image.getState()).to.be(ol.ImageState.LOADING);
+      });
+
+      it('returns false', function() {
+        var loaded = renderer.loadImage(image);
+        expect(loaded).to.be(false);
+      });
+
+      it('does not register a new listener', function() {
+        renderer.loadImage(image);
+        var listeners = image.getListeners(eventType, false);
+        expect(listeners).to.have.length(1);
+      });
+
+    });
+
+  });
+});
+
+goog.require('goog.events.EventType');
+goog.require('ol.Image');
+goog.require('ol.ImageState');
+goog.require('ol.Map');
+goog.require('ol.layer.Layer');
+goog.require('ol.renderer.Layer');
diff --git a/test/spec/ol/structs/rbush.test.js b/test/spec/ol/structs/rbush.test.js
index c765329..44117fb 100644
--- a/test/spec/ol/structs/rbush.test.js
+++ b/test/spec/ol/structs/rbush.test.js
@@ -44,6 +44,14 @@ describe('ol.structs.RBush', function() {
       expect(rBush.getInExtent([2, 2, 3, 3])).to.eql([obj]);
     });
 
+    it('don\'t throws an exception if the extent is not modified', function() {
+      expect(function() {
+        rBush.forEach(function(value) {
+          rBush.update([0, 0, 1, 1], obj);
+        });
+      }).not.to.throwException();
+    });
+
   });
 
   describe('with a few objects', function() {
diff --git a/test/spec/ol/style/atlasmanager.test.js b/test/spec/ol/style/atlasmanager.test.js
index 66f5707..66f1ca1 100644
--- a/test/spec/ol/style/atlasmanager.test.js
+++ b/test/spec/ol/style/atlasmanager.test.js
@@ -191,7 +191,7 @@ describe('ol.style.AtlasManager', function() {
 
       expect(info).to.eql({
         offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_,
-        hitOffsetX: undefined, hitOffsetY: undefined, hitImage: undefined});
+        hitImage: manager.hitAtlases_[0].canvas_});
 
       expect(manager.getInfo('1')).to.eql(info);
     });
@@ -202,7 +202,6 @@ describe('ol.style.AtlasManager', function() {
 
       expect(info).to.eql({
         offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_,
-        hitOffsetX: 1, hitOffsetY: 1,
         hitImage: manager.hitAtlases_[0].canvas_});
 
       expect(manager.getInfo('1')).to.eql(info);
@@ -258,6 +257,20 @@ describe('ol.style.AtlasManager', function() {
       expect(manager.add('2', 2048, 2048, defaultRender, defaultRender))
           .to.eql(null);
     });
+
+    it('always has the same offset for the hit-detection', function() {
+      var manager = new ol.style.AtlasManager({initialSize: 128});
+      // add one image without hit-detection callback
+      var info = manager.add('1', 32, 32, defaultRender);
+      // add then one with hit-detection callback
+      info = manager.add('2', 32, 32, defaultRender, defaultRender);
+
+      expect(info).to.eql({
+        offsetX: 34, offsetY: 1, image: manager.atlases_[0].canvas_,
+        hitImage: manager.hitAtlases_[0].canvas_});
+
+      expect(manager.getInfo('2')).to.eql(info);
+    });
   });
 
   describe('#getInfo', function() {
diff --git a/test/spec/ol/style/circlestyle.test.js b/test/spec/ol/style/circlestyle.test.js
index 4e5d0b9..20b42b1 100644
--- a/test/spec/ol/style/circlestyle.test.js
+++ b/test/spec/ol/style/circlestyle.test.js
@@ -16,7 +16,6 @@ describe('ol.style.Circle', function() {
       expect(style.getImage()).to.not.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
-      expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
     });
 
     it('creates a canvas if no atlas is used (fill-style)', function() {
@@ -35,7 +34,6 @@ describe('ol.style.Circle', function() {
       expect(style.getImage()).to.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
-      expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
     });
 
     it('adds itself to an atlas manager (no fill-style)', function() {
@@ -50,7 +48,6 @@ describe('ol.style.Circle', function() {
       expect(style.getImage()).to.not.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
-      expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
     });
 
     it('adds itself to an atlas manager (fill-style)', function() {
@@ -71,7 +68,6 @@ describe('ol.style.Circle', function() {
       expect(style.getImage()).to.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
-      expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
     });
   });
 
diff --git a/test/spec/ol/style/regularshapestyle.test.js b/test/spec/ol/style/regularshapestyle.test.js
index f972ae7..8df8e55 100644
--- a/test/spec/ol/style/regularshapestyle.test.js
+++ b/test/spec/ol/style/regularshapestyle.test.js
@@ -49,7 +49,6 @@ describe('ol.style.RegularShape', function() {
       expect(style.getImage()).to.not.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
-      expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
     });
 
     it('creates a canvas if no atlas is used (fill-style)', function() {
@@ -68,7 +67,6 @@ describe('ol.style.RegularShape', function() {
       expect(style.getImage()).to.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
-      expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
     });
 
     it('adds itself to an atlas manager (no fill-style)', function() {
@@ -84,7 +82,6 @@ describe('ol.style.RegularShape', function() {
       expect(style.getImage()).to.not.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
-      expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
     });
 
     it('adds itself to an atlas manager (fill-style)', function() {
@@ -105,7 +102,6 @@ describe('ol.style.RegularShape', function() {
       expect(style.getImage()).to.be(style.getHitDetectionImage());
       expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
       expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
-      expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
     });
   });
 
diff --git a/test/spec/ol/view.test.js b/test/spec/ol/view.test.js
index fe35e8d..c4f3eb0 100644
--- a/test/spec/ol/view.test.js
+++ b/test/spec/ol/view.test.js
@@ -1,6 +1,24 @@
 goog.provide('ol.test.View');
 
 describe('ol.View', function() {
+
+  describe('constructor (defaults)', function() {
+    var view;
+
+    beforeEach(function() {
+      view = new ol.View();
+    });
+
+    it('creates an instance', function() {
+      expect(view).to.be.a(ol.View);
+    });
+
+    it('provides default rotation', function() {
+      expect(view.getRotation()).to.be(0);
+    });
+
+  });
+
   describe('create constraints', function() {
 
     describe('create resolution constraint', function() {

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/openlayers3.git



More information about the Pkg-grass-devel mailing list