[med-svn] [orthanc-wsi] 04/07: New upstream version 0.1+dfsg

Andreas Tille tille at debian.org
Mon Oct 31 08:45:52 UTC 2016


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

tille pushed a commit to branch master
in repository orthanc-wsi.

commit ce60603a9655d9fdde29a47a85900aff3a015eb0
Author: Andreas Tille <tille at debian.org>
Date:   Mon Oct 31 08:33:14 2016 +0100

    New upstream version 0.1+dfsg
---
 .hg_archival.txt                                   |     5 +
 AUTHORS                                            |    13 +
 Applications/ApplicationToolbox.cpp                |   194 +
 Applications/ApplicationToolbox.h                  |    46 +
 Applications/CMakeLists.txt                        |   313 +
 Applications/DicomToTiff.cpp                       |   329 +
 Applications/Dicomizer.cpp                         |   886 +
 COPYING                                            |   661 +
 Framework/Algorithms/PyramidReader.cpp             |   306 +
 Framework/Algorithms/PyramidReader.h               |    96 +
 Framework/Algorithms/ReconstructPyramidCommand.cpp |   185 +
 Framework/Algorithms/ReconstructPyramidCommand.h   |    72 +
 Framework/Algorithms/TranscodeTileCommand.cpp      |   121 +
 Framework/Algorithms/TranscodeTileCommand.h        |    59 +
 Framework/DicomToolbox.cpp                         |   251 +
 Framework/DicomToolbox.h                           |    83 +
 Framework/DicomizerParameters.cpp                  |   275 +
 Framework/DicomizerParameters.h                    |   257 +
 Framework/Enumerations.cpp                         |   185 +
 Framework/Enumerations.h                           |    66 +
 Framework/ImageToolbox.cpp                         |   368 +
 Framework/ImageToolbox.h                           |    75 +
 Framework/ImagedVolumeParameters.cpp               |    89 +
 Framework/ImagedVolumeParameters.h                 |    85 +
 Framework/Inputs/DecodedTiledPyramid.cpp           |   118 +
 Framework/Inputs/DecodedTiledPyramid.h             |    75 +
 Framework/Inputs/DicomPyramid.cpp                  |   238 +
 Framework/Inputs/DicomPyramid.h                    |    83 +
 Framework/Inputs/DicomPyramidInstance.cpp          |   171 +
 Framework/Inputs/DicomPyramidInstance.h            |    93 +
 Framework/Inputs/DicomPyramidLevel.cpp             |   129 +
 Framework/Inputs/DicomPyramidLevel.h               |    83 +
 Framework/Inputs/HierarchicalTiff.cpp              |   308 +
 Framework/Inputs/HierarchicalTiff.h                |   111 +
 Framework/Inputs/ITiledPyramid.h                   |    68 +
 Framework/Inputs/OpenSlideLibrary.cpp              |   234 +
 Framework/Inputs/OpenSlideLibrary.h                |   112 +
 Framework/Inputs/OpenSlidePyramid.cpp              |    48 +
 Framework/Inputs/OpenSlidePyramid.h                |    76 +
 Framework/Inputs/PyramidWithRawTiles.cpp           |    75 +
 Framework/Inputs/PyramidWithRawTiles.h             |    34 +
 Framework/Inputs/SingleLevelDecodedPyramid.cpp     |    59 +
 Framework/Inputs/SingleLevelDecodedPyramid.h       |    77 +
 Framework/Inputs/TiledJpegImage.h                  |    44 +
 Framework/Inputs/TiledPngImage.h                   |    44 +
 Framework/Inputs/TiledPyramidStatistics.cpp        |    74 +
 Framework/Inputs/TiledPyramidStatistics.h          |    86 +
 Framework/Jpeg2000Reader.cpp                       |   491 +
 Framework/Jpeg2000Reader.h                         |    53 +
 Framework/Jpeg2000Writer.cpp                       |   365 +
 Framework/Jpeg2000Writer.h                         |    55 +
 Framework/Messaging/CurlOrthancConnection.cpp      |    59 +
 Framework/Messaging/CurlOrthancConnection.h        |    53 +
 Framework/Messaging/FolderTarget.cpp               |    45 +
 Framework/Messaging/FolderTarget.h                 |    45 +
 Framework/Messaging/IFileTarget.h                  |    37 +
 Framework/Messaging/IOrthancConnection.cpp         |    63 +
 Framework/Messaging/IOrthancConnection.h           |    52 +
 Framework/Messaging/OrthancConnectionBase.cpp      |    41 +
 Framework/Messaging/OrthancConnectionBase.h        |    51 +
 Framework/Messaging/OrthancTarget.cpp              |    58 +
 Framework/Messaging/OrthancTarget.h                |    48 +
 Framework/Messaging/PluginOrthancConnection.cpp    |   133 +
 Framework/Messaging/PluginOrthancConnection.h      |    50 +
 Framework/Orthanc/Core/ChunkedBuffer.cpp           |   102 +
 Framework/Orthanc/Core/ChunkedBuffer.h             |    71 +
 Framework/Orthanc/Core/DicomFormat/DicomArray.cpp  |    71 +
 Framework/Orthanc/Core/DicomFormat/DicomArray.h    |    66 +
 Framework/Orthanc/Core/DicomFormat/DicomElement.h  |    92 +
 Framework/Orthanc/Core/DicomFormat/DicomMap.cpp    |   783 +
 Framework/Orthanc/Core/DicomFormat/DicomMap.h      |   184 +
 Framework/Orthanc/Core/DicomFormat/DicomTag.cpp    |   252 +
 Framework/Orthanc/Core/DicomFormat/DicomTag.h      |   182 +
 Framework/Orthanc/Core/DicomFormat/DicomValue.cpp  |    93 +
 Framework/Orthanc/Core/DicomFormat/DicomValue.h    |    91 +
 Framework/Orthanc/Core/Endianness.h                |   157 +
 Framework/Orthanc/Core/EnumerationDictionary.h     |   125 +
 Framework/Orthanc/Core/Enumerations.cpp            |  1396 +
 Framework/Orthanc/Core/Enumerations.h              |   543 +
 Framework/Orthanc/Core/HttpClient.cpp              |   836 +
 Framework/Orthanc/Core/HttpClient.h                |   286 +
 Framework/Orthanc/Core/ICommand.h                  |    48 +
 Framework/Orthanc/Core/IDynamicObject.h            |    52 +
 Framework/Orthanc/Core/Images/IImageWriter.cpp     |    55 +
 Framework/Orthanc/Core/Images/IImageWriter.h       |    77 +
 Framework/Orthanc/Core/Images/Image.cpp            |    58 +
 Framework/Orthanc/Core/Images/Image.h              |    53 +
 Framework/Orthanc/Core/Images/ImageAccessor.cpp    |   296 +
 Framework/Orthanc/Core/Images/ImageAccessor.h      |   131 +
 Framework/Orthanc/Core/Images/ImageBuffer.cpp      |   184 +
 Framework/Orthanc/Core/Images/ImageBuffer.h        |   114 +
 Framework/Orthanc/Core/Images/ImageProcessing.cpp  |   754 +
 Framework/Orthanc/Core/Images/ImageProcessing.h    |    76 +
 Framework/Orthanc/Core/Images/JpegErrorManager.cpp |    69 +
 Framework/Orthanc/Core/Images/JpegErrorManager.h   |    74 +
 Framework/Orthanc/Core/Images/JpegReader.cpp       |   185 +
 Framework/Orthanc/Core/Images/JpegReader.h         |    57 +
 Framework/Orthanc/Core/Images/JpegWriter.cpp       |   204 +
 Framework/Orthanc/Core/Images/JpegWriter.h         |    71 +
 Framework/Orthanc/Core/Images/PngReader.cpp        |   315 +
 Framework/Orthanc/Core/Images/PngReader.h          |    69 +
 Framework/Orthanc/Core/Images/PngWriter.cpp        |   268 +
 Framework/Orthanc/Core/Images/PngWriter.h          |    78 +
 Framework/Orthanc/Core/Logging.cpp                 |   459 +
 Framework/Orthanc/Core/Logging.h                   |   117 +
 Framework/Orthanc/Core/MultiThreading/BagOfTasks.h |    83 +
 .../Core/MultiThreading/BagOfTasksProcessor.cpp    |   276 +
 .../Core/MultiThreading/BagOfTasksProcessor.h      |   149 +
 .../Orthanc/Core/MultiThreading/Semaphore.cpp      |    64 +
 Framework/Orthanc/Core/MultiThreading/Semaphore.h  |    72 +
 .../Core/MultiThreading/SharedMessageQueue.cpp     |   208 +
 .../Core/MultiThreading/SharedMessageQueue.h       |    84 +
 Framework/Orthanc/Core/OrthancException.h          |    76 +
 Framework/Orthanc/Core/PrecompiledHeaders.cpp      |    33 +
 Framework/Orthanc/Core/PrecompiledHeaders.h        |    61 +
 Framework/Orthanc/Core/Toolbox.cpp                 |  1682 +
 Framework/Orthanc/Core/Toolbox.h                   |   246 +
 Framework/Orthanc/Core/Uuid.cpp                    |   162 +
 Framework/Orthanc/Core/Uuid.h                      |    86 +
 Framework/Orthanc/Core/WebServiceParameters.cpp    |   265 +
 Framework/Orthanc/Core/WebServiceParameters.h      |   124 +
 .../Orthanc/OrthancServer/FromDcmtkBridge.cpp      |  1767 +
 Framework/Orthanc/OrthancServer/FromDcmtkBridge.h  |   167 +
 .../OrthancServer/PrecompiledHeadersServer.h       |    77 +
 .../Orthanc/OrthancServer/ServerEnumerations.cpp   |   467 +
 .../Orthanc/OrthancServer/ServerEnumerations.h     |   232 +
 Framework/Orthanc/OrthancServer/ToDcmtkBridge.cpp  |   167 +
 Framework/Orthanc/OrthancServer/ToDcmtkBridge.h    |    52 +
 Framework/Orthanc/Plugins/Engine/SharedLibrary.cpp |   140 +
 Framework/Orthanc/Plugins/Engine/SharedLibrary.h   |    78 +
 .../Plugins/Samples/Common/ExportedSymbols.list    |     7 +
 .../Samples/Common/OrthancPluginCppWrapper.cpp     |  1002 +
 .../Samples/Common/OrthancPluginCppWrapper.h       |   445 +
 .../Plugins/Samples/Common/VersionScript.map       |    12 +
 Framework/Orthanc/README.txt                       |     3 +
 .../Resources/CMake/AutoGeneratedCode.cmake        |    53 +
 .../Resources/CMake/BoostConfiguration.cmake       |   225 +
 Framework/Orthanc/Resources/CMake/Compiler.cmake   |   161 +
 .../Resources/CMake/DcmtkConfiguration.cmake       |   289 +
 .../Orthanc/Resources/CMake/DownloadPackage.cmake  |   168 +
 .../Resources/CMake/JsonCppConfiguration.cmake     |    60 +
 .../Resources/CMake/LibCurlConfiguration.cmake     |   122 +
 .../Resources/CMake/LibJpegConfiguration.cmake     |    95 +
 .../Resources/CMake/LibPngConfiguration.cmake      |    61 +
 .../Resources/CMake/OpenSslConfiguration.cmake     |   230 +
 .../CMake/VisualStudioPrecompiledHeaders.cmake     |    14 +
 .../Resources/CMake/ZlibConfiguration.cmake        |    36 +
 Framework/Orthanc/Resources/EmbedResources.py      |   426 +
 .../Orthanc/Resources/MinGW-W64-Toolchain32.cmake  |    17 +
 .../Orthanc/Resources/MinGW-W64-Toolchain64.cmake  |    17 +
 Framework/Orthanc/Resources/MinGWToolchain.cmake   |    17 +
 .../Resources/Patches/dcmtk-3.6.0-mingw64.patch    |    22 +
 .../Resources/Patches/dcmtk-3.6.0-speed.patch      |    44 +
 .../Resources/Patches/dcmtk-3.6.1-speed.patch      |    26 +
 .../Resources/ThirdParty/VisualStudio/stdint.h     |   259 +
 .../Orthanc/Resources/ThirdParty/base64/base64.cpp |   128 +
 .../Orthanc/Resources/ThirdParty/base64/base64.h   |     4 +
 Framework/Orthanc/Resources/WindowsResources.py    |    89 +
 Framework/Orthanc/Resources/WindowsResources.rc    |    30 +
 .../Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h     |  4740 +
 Framework/Outputs/DicomPyramidWriter.cpp           |   207 +
 Framework/Outputs/DicomPyramidWriter.h             |    78 +
 Framework/Outputs/HierarchicalTiffWriter.cpp       |   422 +
 Framework/Outputs/HierarchicalTiffWriter.h         |    86 +
 Framework/Outputs/IPyramidWriter.h                 |    60 +
 Framework/Outputs/InMemoryTiledImage.cpp           |   172 +
 Framework/Outputs/InMemoryTiledImage.h             |   109 +
 Framework/Outputs/MultiframeDicomWriter.cpp        |   305 +
 Framework/Outputs/MultiframeDicomWriter.h          |    93 +
 Framework/Outputs/PyramidWriterBase.cpp            |   151 +
 Framework/Outputs/PyramidWriterBase.h              |   124 +
 Framework/Outputs/TruncatedPyramidWriter.cpp       |   118 +
 Framework/Outputs/TruncatedPyramidWriter.h         |    78 +
 Framework/PrecompiledHeadersWSI.cpp                |    21 +
 Framework/PrecompiledHeadersWSI.h                  |    34 +
 NEWS                                               |    25 +
 README                                             |    88 +
 Resources/BrightfieldOpticalPath.json              |    20 +
 Resources/CMake/BoostExtendedConfiguration.cmake   |    21 +
 Resources/CMake/LibTiffConfiguration.cmake         |   125 +
 Resources/CMake/OpenJpegConfiguration.cmake        |   159 +
 Resources/CMake/OpenJpegConfiguration.patch        |    44 +
 Resources/CMake/Version.cmake                      |     5 +
 Resources/OrthancLogoDocumentation.png             |   Bin 0 -> 3761 bytes
 Resources/OrthancWSI.doxygen                       |  1794 +
 Resources/SampleDataset.json                       |    43 +
 Resources/SyncOrthancFolder.py                     |   170 +
 Resources/sRGB.icc                                 |   Bin 0 -> 6922 bytes
 Resources/sRGB.txt                                 |    69 +
 TODO                                               |    33 +
 ViewerPlugin/CMakeLists.txt                        |   238 +
 ViewerPlugin/OrthancExplorer.js                    |    34 +
 ViewerPlugin/Plugin.cpp                            |   397 +
 ViewerPlugin/viewer.html                           |    32 +
 ViewerPlugin/viewer.js                             |   125 +
 debian/JS/openlayers-3.19.0/README.txt             |     8 -
 debian/JS/openlayers-3.19.0/ol-debug.js            | 88520 -------------------
 debian/JS/openlayers-3.19.0/ol.css                 |   241 -
 debian/README.Debian                               |    18 -
 debian/changelog                                   |     5 -
 debian/compat                                      |     1 -
 debian/control                                     |    41 -
 debian/copyright                                   |   149 -
 debian/docs/OrthancWSIDicomToTiff.1                |    52 -
 debian/docs/OrthancWSIDicomizer.1                  |   145 -
 debian/manpages                                    |     1 -
 debian/patches/cmake                               |    22 -
 debian/patches/series                              |     1 -
 debian/postinst                                    |    27 -
 debian/postrm                                      |    27 -
 debian/rules                                       |    67 -
 debian/source/format                               |     1 -
 debian/source/include-binaries                     |     1 -
 debian/upstream/metadata                           |     6 -
 debian/watch                                       |     3 -
 215 files changed, 39453 insertions(+), 89336 deletions(-)

diff --git a/.hg_archival.txt b/.hg_archival.txt
new file mode 100644
index 0000000..9dab074
--- /dev/null
+++ b/.hg_archival.txt
@@ -0,0 +1,5 @@
+repo: 4a7a53257c7df5a97aea39377b8c9a6e815c9763
+node: b1d6a0efe09b27f8d7095a792823c6da658b45b3
+branch: OrthancWSI-0.1
+latesttag: null
+latesttagdistance: 35
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..12a3d37
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,13 @@
+Orthanc for Whole-Slide Imaging
+===============================
+
+
+Authors
+-------
+
+* Sebastien Jodogne <s.jodogne at gmail.com>
+  Department of Medical Physics
+  University Hospital of Liege
+  Belgium
+
+  Overall design and lead developer.
diff --git a/Applications/ApplicationToolbox.cpp b/Applications/ApplicationToolbox.cpp
new file mode 100644
index 0000000..89fe8dd
--- /dev/null
+++ b/Applications/ApplicationToolbox.cpp
@@ -0,0 +1,194 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ApplicationToolbox.h"
+
+#include "../Framework/Inputs/OpenSlideLibrary.h"
+#include "../Framework/Orthanc/Core/HttpClient.h"
+#include "../Framework/Orthanc/Core/Logging.h"
+#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h"
+#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/regex.hpp>
+
+namespace OrthancWSI
+{
+  namespace ApplicationToolbox
+  {
+    void GlobalInitialize()
+    {
+      Orthanc::Logging::Initialize();
+      Orthanc::HttpClient::InitializeOpenSsl();
+      Orthanc::HttpClient::GlobalInitialize();
+      Orthanc::FromDcmtkBridge::InitializeDictionary();
+    }
+
+
+    void GlobalFinalize()
+    {
+      OrthancWSI::OpenSlideLibrary::Finalize();
+      Orthanc::HttpClient::GlobalFinalize();
+      Orthanc::HttpClient::FinalizeOpenSsl();
+    }
+
+
+    static void PrintProgress(Orthanc::BagOfTasksProcessor::Handle* handle,
+                              bool* done)
+    {
+      unsigned int previous = 0;
+
+      while (!*done)
+      {
+        unsigned int progress = static_cast<unsigned int>(100.0f * handle->GetProgress());
+        if (previous != progress)
+        {
+          LOG(WARNING) << "Progress: " << progress << "%";
+          previous = progress;
+        }
+
+        boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+      }
+    }
+
+
+    void Execute(Orthanc::BagOfTasks& tasks,
+                 unsigned int threadsCount)
+    {
+      if (threadsCount > 1)
+      {
+        // Submit the tasks to a newly-created processor
+        LOG(WARNING) << "Running " << tasks.GetSize() << " tasks";
+        LOG(WARNING) << "Using " << threadsCount << " threads for the computation";
+        Orthanc::BagOfTasksProcessor processor(threadsCount);
+        std::auto_ptr<Orthanc::BagOfTasksProcessor::Handle> handle(processor.Submit(tasks));
+
+        // Start a thread to display the progress
+        bool done = false;
+        boost::thread progress(PrintProgress, handle.get(), &done);
+
+        // Wait for the completion of the tasks
+        bool success = handle->Join();
+
+        // Stop the progress-printing thread
+        done = true;
+        
+        if (progress.joinable())
+        {
+          progress.join();
+        }
+
+        if (success)
+        {
+          LOG(WARNING) << "All tasks have finished";
+        }
+        else
+        {
+          LOG(ERROR) << "Error has occurred, aborting";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+      }
+      else
+      {
+        LOG(WARNING) << "Running " << tasks.GetSize() << " tasks without multithreading";
+
+        unsigned int previous = 0;
+        unsigned int size = tasks.GetSize();
+
+        // No multithreading
+        while (!tasks.IsEmpty())
+        {
+          std::auto_ptr<Orthanc::ICommand> task(tasks.Pop());
+          if (task->Execute())
+          {
+            unsigned int progress = static_cast<unsigned int>(100.0f * 
+                                                              static_cast<float>((size - tasks.GetSize())) / 
+                                                              static_cast<float>(size));
+            if (progress != previous)
+            {
+              LOG(WARNING) << "Progress: " << progress << "%";
+              previous = progress;
+            }
+          }
+          else
+          {
+            LOG(ERROR) << "Error has occurred, aborting";
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+        }
+      }
+    }
+
+
+    void ParseColor(uint8_t& red,
+                    uint8_t& green,
+                    uint8_t& blue,
+                    const std::string& color)
+    {
+      boost::regex pattern("([0-9]*),([0-9]*),([0-9]*)");
+    
+      bool ok = false;
+      boost::cmatch what;
+
+      try
+      {
+        if (regex_match(color.c_str(), what, pattern))
+        {
+          int r = boost::lexical_cast<int>(what[1]);
+          int g = boost::lexical_cast<int>(what[2]);
+          int b = boost::lexical_cast<int>(what[3]);
+
+          if (r >= 0 && r <= 255 &&
+              g >= 0 && g <= 255 &&
+              b >= 0 && b <= 255)
+          {
+            red = static_cast<uint8_t>(r);
+            green = static_cast<uint8_t>(g);
+            blue = static_cast<uint8_t>(b);
+            ok = true;
+          }
+        }
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+      }
+    
+      if (!ok)
+      {
+        LOG(ERROR) << "Bad color specification: " << color;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
+    void PrintVersion(const char* path)
+    {
+      std::cout
+        << path << " " << ORTHANC_WSI_VERSION << std::endl
+        << "Copyright (C) 2012-2016 Sebastien Jodogne, "
+        << "Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl
+        << "Licensing AGPL: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl.html>." << std::endl
+        << "This is free software: you are free to change and redistribute it." << std::endl
+        << "There is NO WARRANTY, to the extent permitted by law." << std::endl
+        << std::endl
+        << "Written by Sebastien Jodogne <s.jodogne at gmail.com>" << std::endl;
+    }
+  }
+}
diff --git a/Applications/ApplicationToolbox.h b/Applications/ApplicationToolbox.h
new file mode 100644
index 0000000..6650f1c
--- /dev/null
+++ b/Applications/ApplicationToolbox.h
@@ -0,0 +1,46 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasks.h"
+
+#include <string>
+#include <stdint.h>
+
+namespace OrthancWSI
+{
+  namespace ApplicationToolbox
+  {
+    void GlobalInitialize();
+
+    void GlobalFinalize();
+
+    void Execute(Orthanc::BagOfTasks& tasks,
+                 unsigned int threadsCount);
+
+    void ParseColor(uint8_t& red,
+                    uint8_t& green,
+                    uint8_t& blue,
+                    const std::string& color);
+
+    void PrintVersion(const char* path);
+  }
+}
diff --git a/Applications/CMakeLists.txt b/Applications/CMakeLists.txt
new file mode 100644
index 0000000..622ba23
--- /dev/null
+++ b/Applications/CMakeLists.txt
@@ -0,0 +1,313 @@
+cmake_minimum_required(VERSION 2.8)
+project(OrthancWSIApplications)
+
+
+#####################################################################
+## Parameters of the build
+#####################################################################
+
+# Generic parameters
+SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
+# Optional components
+SET(ENABLE_SSL OFF CACHE BOOL "Include support for SSL")
+SET(USE_DCMTK_361 OFF CACHE BOOL "Use forthcoming DCMTK version 3.6.1 in static builds (instead of 3.6.0)")
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+  set(TMP ON)
+else()
+  set(TMP OFF)
+endif()
+
+
+# Advanced parameters to fine-tune linking against system libraries
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+SET(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl")
+SET(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK")
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg")
+SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng")
+SET(USE_SYSTEM_LIBTIFF ON CACHE BOOL "Use the system version of libtiff")
+SET(USE_SYSTEM_OPENJPEG ON CACHE BOOL "Use the system version of OpenJpeg")
+SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL")
+SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib")
+
+SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") 
+
+
+#####################################################################
+## Configure mandatory third-party components
+#####################################################################
+
+SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
+SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Framework/Orthanc)
+SET(USE_OPENJPEG_JP2 ON)
+SET(ENABLE_JPEG OFF)           # Disable DCMTK's support for JPEG, that clashes with libtiff
+SET(ENABLE_JPEG_LOSSLESS OFF)  # Disable DCMTK's support for JPEG-LS
+SET(ENABLE_DCMTK_NETWORK OFF)  # Disable DCMTK's support for DICOM networking
+SET(STANDALONE_BUILD ON)       # Embed DCMTK's dictionaries for static builds
+
+include(CheckIncludeFiles)
+include(CheckIncludeFileCXX)
+include(CheckLibraryExists)
+include(FindPythonInterp)
+include(FindPkgConfig)
+include(CheckSymbolExists)
+
+include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake)
+
+# Third-party components shipped with Orthanc
+include(${ORTHANC_ROOT}/Resources/CMake/DcmtkConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibCurlConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibJpegConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake)
+
+# Include components specific to WSI
+include(${ORTHANC_WSI_DIR}/Resources/CMake/Version.cmake)
+include(${ORTHANC_WSI_DIR}/Resources/CMake/BoostExtendedConfiguration.cmake)
+include(${ORTHANC_WSI_DIR}/Resources/CMake/OpenJpegConfiguration.cmake)
+include(${ORTHANC_WSI_DIR}/Resources/CMake/LibTiffConfiguration.cmake)
+
+add_definitions(
+  -DORTHANC_ENABLE_BASE64=1
+  -DORTHANC_ENABLE_CURL=1
+  -DORTHANC_ENABLE_DCMTK=1
+  -DORTHANC_ENABLE_LOGGING=1
+  -DORTHANC_ENABLE_MD5=0
+  -DORTHANC_JPEG_ENABLED=0     # Disable DCMTK's support for JPEG
+  -DORTHANC_PKCS11_ENABLED=0
+  -DORTHANC_PLUGINS_ENABLED=1  # To enable class Orthanc::SharedLibrary
+  -DORTHANC_PUGIXML_ENABLED=0
+  )
+
+
+#####################################################################
+## Configure optional third-party components
+#####################################################################
+
+if (ENABLE_SSL)
+  set(ENABLE_PKCS11 OFF)
+  add_definitions(-DORTHANC_SSL_ENABLED=1)
+  include(${ORTHANC_ROOT}/Resources/CMake/OpenSslConfiguration.cmake)
+else()
+  add_definitions(-DORTHANC_SSL_ENABLED=0)
+endif()
+
+
+#####################################################################
+## Create the list of the source files that depend upon the
+## precompiled headers
+#####################################################################
+
+set(ORTHANC_WSI_SOURCES
+  #${ORTHANC_WSI_DIR}/Framework/Messaging/PluginOrthancConnection.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Algorithms/PyramidReader.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Algorithms/ReconstructPyramidCommand.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Algorithms/TranscodeTileCommand.cpp
+  ${ORTHANC_WSI_DIR}/Framework/DicomToolbox.cpp
+  ${ORTHANC_WSI_DIR}/Framework/DicomizerParameters.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Enumerations.cpp
+  ${ORTHANC_WSI_DIR}/Framework/ImageToolbox.cpp
+  ${ORTHANC_WSI_DIR}/Framework/ImagedVolumeParameters.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/DecodedTiledPyramid.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramid.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidInstance.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidLevel.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/HierarchicalTiff.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlideLibrary.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlidePyramid.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/SingleLevelDecodedPyramid.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/TiledPyramidStatistics.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/CurlOrthancConnection.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/FolderTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/IOrthancConnection.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancConnectionBase.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Outputs/DicomPyramidWriter.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Outputs/HierarchicalTiffWriter.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Outputs/InMemoryTiledImage.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Outputs/MultiframeDicomWriter.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Outputs/PyramidWriterBase.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Outputs/TruncatedPyramidWriter.cpp
+  )
+
+set(ORTHANC_CORE_SOURCES
+  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/HttpClient.cpp
+  ${ORTHANC_ROOT}/Core/Images/IImageWriter.cpp
+  ${ORTHANC_ROOT}/Core/Images/Image.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegWriter.cpp
+  ${ORTHANC_ROOT}/Core/Images/PngReader.cpp
+  ${ORTHANC_ROOT}/Core/Images/PngWriter.cpp
+  ${ORTHANC_ROOT}/Core/Logging.cpp
+  ${ORTHANC_ROOT}/Core/MultiThreading/BagOfTasksProcessor.cpp
+  ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Core/Uuid.cpp
+  ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
+  ${ORTHANC_ROOT}/OrthancServer/FromDcmtkBridge.cpp
+  ${ORTHANC_ROOT}/OrthancServer/ServerEnumerations.cpp
+  ${ORTHANC_ROOT}/OrthancServer/ToDcmtkBridge.cpp
+  ${ORTHANC_ROOT}/Plugins/Engine/SharedLibrary.cpp
+  ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
+  )
+
+EmbedResources(
+  ${DCMTK_DICTIONARIES}
+  BRIGHTFIELD_OPTICAL_PATH  ${ORTHANC_WSI_DIR}/Resources/BrightfieldOpticalPath.json
+  SAMPLE_DATASET            ${ORTHANC_WSI_DIR}/Resources/SampleDataset.json
+  SRGB_ICC_PROFILE          ${ORTHANC_WSI_DIR}/Resources/sRGB.icc
+  )
+
+
+#####################################################################
+## Setup precompiled headers for Microsoft Visual Studio
+#####################################################################
+
+if (MSVC)
+  add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1)
+
+  ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
+    "PrecompiledHeaders.h" "${ORTHANC_WSI_DIR}/Framework/Orthanc/Core/PrecompiledHeaders.cpp" ORTHANC_CORE_SOURCES)
+
+  ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
+    "PrecompiledHeadersWSI.h" "${ORTHANC_WSI_DIR}/Framework/PrecompiledHeadersWSI.cpp" ORTHANC_WSI_SOURCES)
+
+  source_group(ThirdParty\\OrthancCore FILES ${ORTHANC_CORE_SOURCES})
+endif()
+
+
+#####################################################################
+## Create the static library containing the framework
+#####################################################################
+
+add_library(OrthancWSIFramework STATIC
+  ${ORTHANC_CORE_SOURCES}
+  ${ORTHANC_WSI_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+
+  # Mandatory components
+  ${BOOST_SOURCES}
+  ${CURL_SOURCES}
+  ${DCMTK_SOURCES}
+  ${JSONCPP_SOURCES}
+  ${LIBJPEG_SOURCES}
+  ${LIBPNG_SOURCES}
+  ${LIBTIFF_SOURCES}
+  ${OPENJPEG_SOURCES}
+  ${ZLIB_SOURCES}
+
+  # Optional components
+  ${OPENSSL_SOURCES}
+  )
+
+
+#####################################################################
+## Build the WSI DICOM-izer
+#####################################################################
+
+# Create the Windows resources, if need be
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
+    ${ORTHANC_WSI_VERSION} OrthancWSIDicomizer OrthancWSIDicomizer.exe "Companion tool to Orthanc for whole-slide imaging"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/OrthancWSIDicomizer.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  set(DICOMIZER_RESOURCES ${AUTOGENERATED_DIR}/OrthancWSIDicomizer.rc)
+endif()
+
+
+add_executable(OrthancWSIDicomizer
+  Dicomizer.cpp
+  ApplicationToolbox.cpp
+  ${DICOMIZER_RESOURCES}
+  )
+
+target_link_libraries(OrthancWSIDicomizer OrthancWSIFramework ${DCMTK_LIBRARIES})
+
+install(
+  TARGETS OrthancWSIDicomizer
+  RUNTIME DESTINATION bin
+  )
+
+
+#####################################################################
+## Build the DICOM-to-TIFF conversion tool
+#####################################################################
+
+# Create the Windows resources, if need be
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
+    ${ORTHANC_WSI_VERSION} OrthancWSIDicomToTiff OrthancWSIDicomToTiff.exe "Companion tool to Orthanc for whole-slide imaging"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/OrthancWSIDicomToTiff.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  set(DICOM_TO_TIFF_RESOURCES ${AUTOGENERATED_DIR}/OrthancWSIDicomToTiff.rc)
+endif()
+
+
+add_executable(OrthancWSIDicomToTiff
+  DicomToTiff.cpp
+  ApplicationToolbox.cpp
+  ${DICOM_TO_TIFF_RESOURCES}
+  )
+
+target_link_libraries(OrthancWSIDicomToTiff OrthancWSIFramework ${DCMTK_LIBRARIES})
+
+install(
+  TARGETS OrthancWSIDicomToTiff
+  RUNTIME DESTINATION bin
+  )
+
+
+#####################################################################
+## Generate the documentation if Doxygen is present
+#####################################################################
+
+find_package(Doxygen)
+if (DOXYGEN_FOUND)
+  configure_file(
+    ${ORTHANC_WSI_DIR}/Resources/OrthancWSI.doxygen
+    ${CMAKE_CURRENT_BINARY_DIR}/OrthancWSI.doxygen
+    @ONLY)
+
+  add_custom_target(doc
+    ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancWSI.doxygen
+    COMMENT "Generating documentation with Doxygen" VERBATIM
+    )
+else()
+  message("Doxygen not found. The documentation will not be built.")
+endif()
diff --git a/Applications/DicomToTiff.cpp b/Applications/DicomToTiff.cpp
new file mode 100644
index 0000000..99d2c9c
--- /dev/null
+++ b/Applications/DicomToTiff.cpp
@@ -0,0 +1,329 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Framework/DicomToolbox.h"
+#include "../Framework/ImageToolbox.h"
+#include "../Framework/Inputs/DicomPyramid.h"
+#include "../Framework/Messaging/CurlOrthancConnection.h"
+#include "../Framework/Orthanc/Core/Logging.h"
+#include "../Framework/Orthanc/Core/OrthancException.h"
+#include "../Framework/Outputs/HierarchicalTiffWriter.h"
+
+#include "ApplicationToolbox.h"
+
+#include <boost/program_options.hpp>
+
+
+static bool ParseParameters(int& exitStatus,
+                            boost::program_options::variables_map& options,
+                            int argc, 
+                            char* argv[])
+{
+  // Declare the supported parameters
+  boost::program_options::options_description generic("Generic options");
+  generic.add_options()
+    ("help", "Display this help and exit")
+    ("version", "Output version information and exit")
+    ("verbose", "Be verbose in logs")
+    ;
+
+  boost::program_options::options_description source("Options for the source DICOM image");
+  source.add_options()
+    ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"),
+     "URL to the REST API of the target Orthanc server")
+    ("username", boost::program_options::value<std::string>(), "Username for the target Orthanc server")
+    ("password", boost::program_options::value<std::string>(), "Password for the target Orthanc server")
+    ;
+
+  boost::program_options::options_description target("Options for the target TIFF image");
+  target.add_options()
+    ("color", boost::program_options::value<std::string>(), "Color of the background for missing tiles (e.g. \"255,0,0\")")
+    ("reencode", boost::program_options::value<bool>(), 
+     "Whether to re-encode each tile in JPEG (no transcoding, much slower) (Boolean)")
+    ("jpeg-quality", boost::program_options::value<int>(), "Set quality level for JPEG (0..100)")
+    ;
+
+  boost::program_options::options_description hidden;
+  hidden.add_options()
+    ("input", boost::program_options::value<std::string>(), "Orthanc identifier of the input series of interest")
+    ("output", boost::program_options::value<std::string>(), "Output TIFF file");
+  ;
+
+  boost::program_options::options_description allWithoutHidden;
+  allWithoutHidden.add(generic).add(source).add(target);
+
+  boost::program_options::options_description all = allWithoutHidden;
+  all.add(hidden);
+
+  boost::program_options::positional_options_description positional;
+  positional.add("input", 1);
+  positional.add("output", 1);
+
+  bool error = false;
+
+  try
+  {
+    boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
+                                  options(all).positional(positional).run(), options);
+    boost::program_options::notify(options);    
+  }
+  catch (boost::program_options::error& e)
+  {
+    LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what();
+    error = true;
+  }
+
+  if (!error &&
+      options.count("help") == 0 &&
+      options.count("version") == 0)
+  {
+    if (options.count("input") != 1)
+    {
+      LOG(ERROR) << "No input series was specified";
+      error = true;
+    }
+
+    if (options.count("output") != 1)
+    {
+      LOG(ERROR) << "No output file was specified";
+      error = true;
+    }
+  }
+
+  if (error || options.count("help")) 
+  {
+    std::cout << std::endl
+              << "Usage: " << argv[0] << " [OPTION]... [INPUT] [OUTPUT]"
+              << std::endl
+              << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
+              << std::endl << std::endl
+              << "Convert a DICOM for digital pathology stored in some Orthanc server as a standard hierarchical TIFF."
+              << std::endl;
+
+    std::cout << allWithoutHidden << "\n";
+
+    if (error)
+    {
+      exitStatus = -1;
+    }
+
+    return false;
+  }
+
+  if (options.count("version")) 
+  { 
+    OrthancWSI::ApplicationToolbox::PrintVersion(argv[0]);
+    return false;
+  }
+
+  if (options.count("verbose"))
+  {
+    Orthanc::Logging::EnableInfoLevel(true);
+  }
+
+  return true;
+}
+
+
+
+static Orthanc::ImageAccessor* CreateEmptyTile(const OrthancWSI::IPyramidWriter& writer,
+                                               const boost::program_options::variables_map& options)
+{
+  std::auto_ptr<Orthanc::ImageAccessor> tile
+    (OrthancWSI::ImageToolbox::Allocate(writer.GetPixelFormat(), 
+                                        writer.GetTileWidth(), 
+                                        writer.GetTileHeight()));
+
+  uint8_t red = 255;
+  uint8_t green = 255;
+  uint8_t blue = 255;
+
+  if (options.count("color"))
+  {
+    OrthancWSI::ApplicationToolbox::ParseColor(red, green, blue, options["color"].as<std::string>());
+  }
+
+  OrthancWSI::ImageToolbox::Set(*tile, red, green, blue);
+
+  return tile.release();
+}
+
+
+
+static void RunTranscode(OrthancWSI::ITiledPyramid& source,
+                         const boost::program_options::variables_map& options)
+{
+  OrthancWSI::HierarchicalTiffWriter target(options["output"].as<std::string>(),
+                                            source.GetPixelFormat(), 
+                                            source.GetImageCompression(), 
+                                            source.GetTileWidth(), 
+                                            source.GetTileHeight());
+
+  std::auto_ptr<Orthanc::ImageAccessor> empty(CreateEmptyTile(target, options));
+
+  for (unsigned int level = 0; level < source.GetLevelCount(); level++)
+  {
+    LOG(WARNING) << "Creating level " << level << " of size " 
+                 << source.GetLevelWidth(level) << "x" << source.GetLevelHeight(level);
+    target.AddLevel(source.GetLevelWidth(level), source.GetLevelHeight(level));
+  }
+
+  for (unsigned int level = 0; level < source.GetLevelCount(); level++)
+  {
+    LOG(WARNING) << "Transcoding level " << level;
+
+    unsigned int countX = OrthancWSI::CeilingDivision(source.GetLevelWidth(level), source.GetTileWidth());
+    unsigned int countY = OrthancWSI::CeilingDivision(source.GetLevelHeight(level), source.GetTileHeight());
+
+    for (unsigned int tileY = 0; tileY < countY; tileY++)
+    {
+      for (unsigned int tileX = 0; tileX < countX; tileX++)
+      {
+        LOG(INFO) << "Dealing with tile (" << tileX << "," << tileY << ") at level " << level;
+        std::string tile;
+
+        if (source.ReadRawTile(tile, level, tileX, tileY))
+        {
+          target.WriteRawTile(tile, source.GetImageCompression(), level, tileX, tileY);
+        }
+        else
+        {
+          target.EncodeTile(*empty, level, tileX, tileY);
+        }
+      }        
+    }
+
+    target.Flush();
+  }
+}
+
+
+static void RunReencode(OrthancWSI::ITiledPyramid& source,
+                        const boost::program_options::variables_map& options)
+{
+  OrthancWSI::HierarchicalTiffWriter target(options["output"].as<std::string>(),
+                                            source.GetPixelFormat(), 
+                                            OrthancWSI::ImageCompression_Jpeg,
+                                            source.GetTileWidth(), 
+                                            source.GetTileHeight());
+
+  if (options.count("jpeg-quality"))
+  {
+    target.SetJpegQuality(options["jpeg-quality"].as<int>());
+  }
+
+  std::auto_ptr<Orthanc::ImageAccessor> empty(CreateEmptyTile(target, options));
+
+  for (unsigned int level = 0; level < source.GetLevelCount(); level++)
+  {
+    LOG(WARNING) << "Creating level " << level << " of size " 
+                 << source.GetLevelWidth(level) << "x" << source.GetLevelHeight(level);
+    target.AddLevel(source.GetLevelWidth(level), source.GetLevelHeight(level));
+  }
+
+  for (unsigned int level = 0; level < source.GetLevelCount(); level++)
+  {
+    LOG(WARNING) << "Reencoding level " << level;
+
+    unsigned int countX = OrthancWSI::CeilingDivision(source.GetLevelWidth(level), source.GetTileWidth());
+    unsigned int countY = OrthancWSI::CeilingDivision(source.GetLevelHeight(level), source.GetTileHeight());
+
+    for (unsigned int tileY = 0; tileY < countY; tileY++)
+    {
+      for (unsigned int tileX = 0; tileX < countX; tileX++)
+      {
+        LOG(INFO) << "Dealing with tile (" << tileX << "," << tileY << ") at level " << level;
+
+        std::auto_ptr<Orthanc::ImageAccessor> tile(source.DecodeTile(level, tileX, tileY));
+        if (tile.get() == NULL)
+        {
+          target.EncodeTile(*empty, level, tileX, tileY);
+        }
+        else
+        {
+          target.EncodeTile(*tile, level, tileX, tileY);
+        }
+      }        
+    }
+
+    target.Flush();
+  }
+}
+
+
+
+int main(int argc, char* argv[])
+{
+  OrthancWSI::ApplicationToolbox::GlobalInitialize();
+
+  int exitStatus = 0;
+  boost::program_options::variables_map options;
+
+  try
+  {
+    if (ParseParameters(exitStatus, options, argc, argv))
+    {
+      Orthanc::WebServiceParameters params;
+
+      if (options.count("orthanc"))
+      {
+        params.SetUrl(options["orthanc"].as<std::string>());
+      }
+
+      if (options.count("username"))
+      {
+        params.SetUsername(options["username"].as<std::string>());
+      }
+
+      if (options.count("password"))
+      {
+        params.SetPassword(options["password"].as<std::string>());
+      }
+
+      OrthancWSI::CurlOrthancConnection orthanc(params);
+      OrthancWSI::DicomPyramid source(orthanc, options["input"].as<std::string>());
+
+      if (options.count("reencode") &&
+          options["reencode"].as<bool>())
+      {
+        RunReencode(source, options);
+      }
+      else
+      {
+        RunTranscode(source, options);
+      }
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Terminating on exception: " << e.What();
+
+    if (options.count("reencode") == 0)
+    {
+      LOG(ERROR) << "Consider using option \"--reencode\"";
+    }
+    
+    exitStatus = -1;
+  }
+
+  OrthancWSI::ApplicationToolbox::GlobalFinalize();
+
+  return exitStatus;
+}
diff --git a/Applications/Dicomizer.cpp b/Applications/Dicomizer.cpp
new file mode 100644
index 0000000..c325fcc
--- /dev/null
+++ b/Applications/Dicomizer.cpp
@@ -0,0 +1,886 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Framework/Algorithms/ReconstructPyramidCommand.h"
+#include "../Framework/Algorithms/TranscodeTileCommand.h"
+#include "../Framework/DicomToolbox.h"
+#include "../Framework/DicomizerParameters.h"
+#include "../Framework/ImagedVolumeParameters.h"
+#include "../Framework/Inputs/HierarchicalTiff.h"
+#include "../Framework/Inputs/OpenSlidePyramid.h"
+#include "../Framework/Inputs/TiledJpegImage.h"
+#include "../Framework/Inputs/TiledPngImage.h"
+#include "../Framework/Inputs/TiledPyramidStatistics.h"
+#include "../Framework/Orthanc/Core/HttpClient.h"
+#include "../Framework/Orthanc/Core/Logging.h"
+#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h"
+#include "../Framework/Orthanc/Core/Toolbox.h"
+#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h"
+#include "../Framework/Outputs/DicomPyramidWriter.h"
+#include "../Framework/Outputs/TruncatedPyramidWriter.h"
+
+#include "ApplicationToolbox.h"
+
+#include <EmbeddedResources.h>
+
+#include <boost/program_options.hpp>
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcuid.h>
+#include <dcmtk/dcmdata/dcvrobow.h>
+#include <dcmtk/dcmdata/dcvrat.h>
+
+
+static void TranscodePyramid(OrthancWSI::PyramidWriterBase& target,
+                             OrthancWSI::ITiledPyramid& source,
+                             const OrthancWSI::DicomizerParameters& parameters)
+{
+  Orthanc::BagOfTasks tasks;
+
+  for (unsigned int i = 0; i < source.GetLevelCount(); i++)
+  {
+    LOG(WARNING) << "Creating level " << i << " of size " 
+                 << source.GetLevelWidth(i) << "x" << source.GetLevelHeight(i);
+    target.AddLevel(source.GetLevelWidth(i), source.GetLevelHeight(i));
+  }
+
+  LOG(WARNING) << "Transcoding the source pyramid";
+
+  OrthancWSI::TranscodeTileCommand::PrepareBagOfTasks(tasks, target, source, parameters);
+  OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount());
+}
+
+
+static void ReconstructPyramid(OrthancWSI::PyramidWriterBase& target,
+                               OrthancWSI::ITiledPyramid& source,
+                               const OrthancWSI::DicomizerParameters& parameters)
+{
+  Orthanc::BagOfTasks tasks;
+
+  unsigned int levelsCount = parameters.GetPyramidLevelsCount(target, source);
+  LOG(WARNING) << "The target pyramid will have " << levelsCount << " levels";
+  assert(levelsCount >= 1);
+
+  for (unsigned int i = 0; i < levelsCount; i++)
+  {
+    unsigned int width = OrthancWSI::CeilingDivision(source.GetLevelWidth(0), 1 << i);
+    unsigned int height = OrthancWSI::CeilingDivision(source.GetLevelHeight(0), 1 << i);
+
+    LOG(WARNING) << "Creating level " << i << " of size " << width << "x" << height;
+    target.AddLevel(width, height);
+  }
+
+  unsigned int lowerLevelsCount = parameters.GetPyramidLowerLevelsCount(target, source);
+  if (lowerLevelsCount > levelsCount)
+  {
+    LOG(WARNING) << "The number of lower levels (" << lowerLevelsCount
+                 << ") exceeds the number of levels (" << levelsCount
+                 << "), cropping it";
+    lowerLevelsCount = levelsCount;
+  }
+
+  assert(lowerLevelsCount <= levelsCount);
+  if (lowerLevelsCount != levelsCount)
+  {
+    LOG(WARNING) << "Constructing the " << lowerLevelsCount << " lower levels of the pyramid";
+    OrthancWSI::TruncatedPyramidWriter truncated(target, lowerLevelsCount);
+    OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks
+      (tasks, truncated, source, lowerLevelsCount + 1, 0, parameters);
+    OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount());
+
+    assert(tasks.GetSize() == 0);
+
+    const unsigned int upperLevelsCount = levelsCount - lowerLevelsCount;
+    LOG(WARNING) << "Constructing the " << upperLevelsCount << " upper levels of the pyramid";
+    OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks
+      (tasks, target, truncated.GetUpperLevel(), 
+       upperLevelsCount, lowerLevelsCount, parameters); 
+    OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount());
+  }
+  else
+  {
+    LOG(WARNING) << "Constructing the pyramid";
+    OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks
+      (tasks, target, source, levelsCount, 0, parameters);
+    OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount());
+  }
+}
+
+
+static void Recompress(OrthancWSI::IFileTarget& output,
+                       OrthancWSI::ITiledPyramid& source,
+                       const DcmDataset& dataset,
+                       const OrthancWSI::DicomizerParameters& parameters,
+                       const OrthancWSI::ImagedVolumeParameters& volume)
+{
+  OrthancWSI::TiledPyramidStatistics stats(source);
+
+  LOG(WARNING) << "Size of source tiles: " << stats.GetTileWidth() << "x" << stats.GetTileHeight();
+  LOG(WARNING) << "Source image compression: " << OrthancWSI::EnumerationToString(stats.GetImageCompression());
+  LOG(WARNING) << "Pixel format: " << Orthanc::EnumerationToString(stats.GetPixelFormat());
+  LOG(WARNING) << "Smoothing is " << (parameters.IsSmoothEnabled() ? "enabled" : "disabled");
+
+  if (parameters.IsRepaintBackground())
+  {
+    LOG(WARNING) << "Repainting the background with color: (" 
+                 << static_cast<int>(parameters.GetBackgroundColorRed()) << ","
+                 << static_cast<int>(parameters.GetBackgroundColorGreen()) << ","
+                 << static_cast<int>(parameters.GetBackgroundColorBlue()) << ")";
+  }
+  else
+  {
+    LOG(WARNING) << "No repainting of the background";
+  }
+
+  OrthancWSI::DicomPyramidWriter target(output, dataset, 
+                                        source.GetPixelFormat(), 
+                                        parameters.GetTargetCompression(),
+                                        parameters.GetTargetTileWidth(source),
+                                        parameters.GetTargetTileHeight(source),
+                                        parameters.GetDicomMaxFileSize(),
+                                        volume);
+  target.SetJpegQuality(parameters.GetJpegQuality());
+
+  LOG(WARNING) << "Size of target tiles: " << target.GetTileWidth() << "x" << target.GetTileHeight();
+
+  if (target.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg)
+  {
+    LOG(WARNING) << "Target image compression: Jpeg with quality " << static_cast<int>(target.GetJpegQuality());
+    target.SetJpegQuality(target.GetJpegQuality());
+  }
+  else
+  {
+    LOG(WARNING) << "Target image compression: " << OrthancWSI::EnumerationToString(target.GetImageCompression());
+  }
+
+  if (stats.GetTileWidth() % target.GetTileWidth() != 0 ||
+      stats.GetTileHeight() % target.GetTileHeight() != 0)
+  {
+    LOG(ERROR) << "When resampling the tile size, it must be a integer divisor of the original tile size";
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+  }
+
+  if (target.GetTileWidth() <= 16 ||
+      target.GetTileHeight() <= 16)
+  {
+    LOG(ERROR) << "Tiles are too small (16 pixels minimum): "
+               << target.GetTileWidth() << "x" << target.GetTileHeight();
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+  }
+
+  if (parameters.IsReconstructPyramid())
+  {
+    ReconstructPyramid(target, stats, parameters);
+  }
+  else
+  {
+    TranscodePyramid(target, stats, parameters);
+  }
+
+  target.Flush();
+}
+
+
+
+static DcmDataset* ParseDataset(const std::string& path)
+{
+  Json::Value json;
+
+  if (path.empty())
+  {
+    json = Json::objectValue;   // Empty dataset => TODO EMBED
+  }
+  else
+  {
+    std::string content;
+    Orthanc::Toolbox::ReadFile(content, path);
+
+    Json::Reader reader;
+    if (!reader.parse(content, json, false))
+    {
+      LOG(ERROR) << "Cannot parse the JSON file in: " << path;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+  std::auto_ptr<DcmDataset> dataset(Orthanc::FromDcmtkBridge::FromJson(json, true, true, Orthanc::Encoding_Latin1));
+  if (dataset.get() == NULL)
+  {
+    LOG(ERROR) << "Cannot convert to JSON file to a DICOM dataset: " << path;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+  }
+
+  // VL Whole Slide Microscopy Image IOD
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SOPClassUID, "1.2.840.10008.5.1.4.1.1.77.1.6");
+
+  // Slide Microscopy
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_Modality, "SM");  
+
+  // Patient orientation makes no sense in whole-slide images
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_PatientOrientation, "");  
+
+  // Some basic coordinate information
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_VolumetricProperties, "VOLUME");
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ImageOrientationSlide, "0\\-1\\0\\-1\\0\\0");
+
+  std::string date, time;
+  Orthanc::Toolbox::GetNowDicom(date, time);
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyDate, date);
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyTime, time);
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SeriesDate, date);
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SeriesTime, time);
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ContentDate, date);
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ContentTime, time);
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_AcquisitionDateTime, date + time);
+
+  return dataset.release();
+}
+
+
+
+static void SetupDimension(DcmDataset& dataset,
+                           const std::string& opticalPathId,
+                           const OrthancWSI::ITiledPyramid& source,
+                           const OrthancWSI::ImagedVolumeParameters& volume)
+{
+  std::string uid;
+  DcmItem* previous = OrthancWSI::DicomToolbox::ExtractSingleSequenceItem(dataset, DCM_DimensionOrganizationSequence);
+
+  if (previous != NULL)
+  {
+    const char* tmp = NULL;
+    if (previous->findAndGetString(DCM_DimensionOrganizationUID, tmp).good() &&
+        tmp != NULL)
+    {
+      uid.assign(tmp);
+    }
+  }
+
+  if (uid.empty())
+  {
+    // Generate an unique identifier for the Dimension Organization
+    uid = Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Instance);
+  }
+
+  dataset.remove(DCM_DimensionIndexSequence);
+
+  std::auto_ptr<DcmItem> item(new DcmItem);
+  std::auto_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_DimensionOrganizationSequence));
+
+  if (!item->putAndInsertString(DCM_DimensionOrganizationUID, uid.c_str()).good() ||
+      !sequence->insert(item.release(), false, false).good() ||
+      !dataset.insert(sequence.release(), true, false).good())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+  item.reset(new DcmItem);
+  sequence.reset(new DcmSequenceOfItems(DCM_DimensionIndexSequence));
+
+  std::auto_ptr<DcmAttributeTag> a1(new DcmAttributeTag(DCM_FunctionalGroupPointer));
+  std::auto_ptr<DcmAttributeTag> a2(new DcmAttributeTag(DCM_DimensionIndexPointer));
+
+  if (!item->putAndInsertString(DCM_DimensionOrganizationUID, uid.c_str()).good() ||
+      !a1->putTagVal(DCM_FrameContentSequence).good() ||
+      !a2->putTagVal(DCM_DimensionIndexValues).good() ||
+      !item->insert(a1.release()).good() ||
+      !item->insert(a2.release()).good() ||
+      !sequence->insert(item.release(), false, false).good() ||
+      !dataset.insert(sequence.release(), true, false).good())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+  float spacingX = volume.GetWidth() / static_cast<float>(source.GetLevelHeight(0));  // Remember to switch X/Y!
+  float spacingY = volume.GetHeight() / static_cast<float>(source.GetLevelWidth(0));  // Remember to switch X/Y!
+  std::string spacing = (boost::lexical_cast<std::string>(spacingX) + '\\' +
+                         boost::lexical_cast<std::string>(spacingY));
+
+  item.reset(new DcmItem);
+  sequence.reset(new DcmSequenceOfItems(DCM_SharedFunctionalGroupsSequence));
+  std::auto_ptr<DcmItem> item2(new DcmItem);
+  std::auto_ptr<DcmItem> item3(new DcmItem);
+  std::auto_ptr<DcmSequenceOfItems> sequence2(new DcmSequenceOfItems(DCM_PixelMeasuresSequence));
+  std::auto_ptr<DcmSequenceOfItems> sequence3(new DcmSequenceOfItems(DCM_OpticalPathIdentificationSequence));
+
+  OrthancWSI::DicomToolbox::SetStringTag(*item2, DCM_SliceThickness, boost::lexical_cast<std::string>(volume.GetDepth()));
+  OrthancWSI::DicomToolbox::SetStringTag(*item2, DCM_PixelSpacing, spacing);
+  OrthancWSI::DicomToolbox::SetStringTag(*item3, DCM_OpticalPathIdentifier, opticalPathId);
+
+  if (!sequence2->insert(item2.release(), false, false).good() ||
+      !sequence3->insert(item3.release(), false, false).good() ||
+      !item->insert(sequence2.release(), false, false).good() ||
+      !item->insert(sequence3.release(), false, false).good() ||
+      !sequence->insert(item.release(), false, false).good() ||
+      !dataset.insert(sequence.release(), true, false).good())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
+
+
+static void EnrichDataset(DcmDataset& dataset,
+                          const OrthancWSI::ITiledPyramid& source,
+                          const OrthancWSI::DicomizerParameters& parameters,
+                          const OrthancWSI::ImagedVolumeParameters& volume)
+{
+  Orthanc::Encoding encoding = Orthanc::FromDcmtkBridge::DetectEncoding(dataset, Orthanc::Encoding_Latin1);
+
+  if (source.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg ||
+      parameters.GetTargetCompression() == OrthancWSI::ImageCompression_Jpeg)
+  {
+    // Takes as estimation a 1:10 compression ratio
+    OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompression, "01");
+    OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompressionRatio, "10");
+    OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompressionMethod, "ISO_10918_1"); // JPEG Lossy Compression
+  }
+  else
+  {
+    OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompression, "00");
+  }
+
+  OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeWidth, boost::lexical_cast<std::string>(volume.GetWidth()));
+  OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeHeight, boost::lexical_cast<std::string>(volume.GetHeight()));
+  OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeDepth, boost::lexical_cast<std::string>(volume.GetDepth()));
+
+  std::auto_ptr<DcmItem> origin(new DcmItem);
+  OrthancWSI::DicomToolbox::SetStringTag(*origin, DCM_XOffsetInSlideCoordinateSystem, 
+                                         boost::lexical_cast<std::string>(volume.GetOffsetX()));
+  OrthancWSI::DicomToolbox::SetStringTag(*origin, DCM_YOffsetInSlideCoordinateSystem, 
+                                         boost::lexical_cast<std::string>(volume.GetOffsetY()));
+
+  std::auto_ptr<DcmSequenceOfItems> sequenceOrigin(new DcmSequenceOfItems(DCM_TotalPixelMatrixOriginSequence));
+  if (!sequenceOrigin->insert(origin.release(), false, false).good() ||
+      !dataset.insert(sequenceOrigin.release(), false, false).good())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  if (parameters.GetOpticalPath() == OrthancWSI::OpticalPath_Brightfield)
+  {
+    if (dataset.tagExists(DCM_OpticalPathSequence))
+    {
+      LOG(ERROR) << "The user DICOM dataset already contains an optical path sequence, giving up";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);      
+    }
+
+    std::string brightfield;
+    Orthanc::EmbeddedResources::GetFileResource(brightfield, Orthanc::EmbeddedResources::BRIGHTFIELD_OPTICAL_PATH);
+
+    Json::Value json;
+    Json::Reader reader;
+    if (!reader.parse(brightfield, json, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    std::auto_ptr<DcmElement> element(Orthanc::FromDcmtkBridge::FromJson(
+                                        Orthanc::DicomTag(DCM_OpticalPathSequence.getGroup(),
+                                                          DCM_OpticalPathSequence.getElement()),
+                                        json, false, encoding));
+    if (!dataset.insert(element.release()).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  std::string profile;
+  if (parameters.GetIccProfilePath().empty())
+  {
+    Orthanc::EmbeddedResources::GetFileResource(profile, Orthanc::EmbeddedResources::SRGB_ICC_PROFILE);
+  }
+  else
+  {
+    Orthanc::Toolbox::ReadFile(profile, parameters.GetIccProfilePath());
+  }
+
+  
+  DcmItem* opticalPath = OrthancWSI::DicomToolbox::ExtractSingleSequenceItem(dataset, DCM_OpticalPathSequence);
+  if (opticalPath == NULL)
+  {
+    LOG(ERROR) << "No optical path specified";
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+  }
+
+  if (!opticalPath->tagExists(DCM_ICCProfile))
+  {
+    std::auto_ptr<DcmOtherByteOtherWord> icc(new DcmOtherByteOtherWord(DCM_ICCProfile));
+
+    if (!icc->putUint8Array(reinterpret_cast<const Uint8*>(profile.c_str()), profile.size()).good() ||
+        !opticalPath->insert(icc.release()).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+  const char* opticalPathId = NULL;
+  if (!opticalPath->findAndGetString(DCM_OpticalPathIdentifier, opticalPathId).good() ||
+      opticalPathId == NULL)
+  {
+    LOG(ERROR) << "No identifier in the optical path";
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);    
+  }
+
+  SetupDimension(dataset, opticalPathId, source, volume);
+}
+
+
+static bool ParseParameters(int& exitStatus,
+                            OrthancWSI::DicomizerParameters& parameters,
+                            OrthancWSI::ImagedVolumeParameters& volume,
+                            int argc, 
+                            char* argv[])
+{
+  // Declare the supported parameters
+  boost::program_options::options_description generic("Generic options");
+  generic.add_options()
+    ("help", "Display this help and exit")
+    ("version", "Output version information and exit")
+    ("verbose", "Be verbose in logs")
+    ("threads", boost::program_options::value<int>()->default_value(parameters.GetThreadsCount()), 
+     "Number of processing threads to be used")
+    ("openslide", boost::program_options::value<std::string>(), 
+     "Path to the shared library of OpenSlide (not necessary if converting from standard hierarchical TIFF)")
+    ;
+
+  boost::program_options::options_description source("Options for the source image");
+  source.add_options()
+    ("dataset", boost::program_options::value<std::string>(), "Path to a JSON file containing the DICOM dataset")
+    ("sample-dataset", "Display a minimalistic sample DICOM dataset in JSON format, then exit")
+    ("reencode", boost::program_options::value<bool>(), "Whether to re-encode each tile (no transcoding, much slower) (Boolean)")
+    ("repaint", boost::program_options::value<bool>(), "Whether to repaint the background of the image (Boolean)")
+    ("color", boost::program_options::value<std::string>(), "Color of the background (e.g. \"255,0,0\")")
+    ;
+
+  boost::program_options::options_description pyramid("Options to construct the pyramid");
+  pyramid.add_options()
+    ("pyramid", boost::program_options::value<bool>()->default_value(false), 
+     "Reconstruct the full pyramid (slow) (Boolean)")
+    ("smooth", boost::program_options::value<bool>()->default_value(false), 
+     "Apply smoothing when reconstructing the pyramid "
+     "(slower, but higher quality) (Boolean)")
+    ("levels", boost::program_options::value<int>(), "Number of levels in the target pyramid")
+    ;
+
+  boost::program_options::options_description target("Options for the target image");
+  target.add_options()
+    ("tile-width", boost::program_options::value<int>(), "Width of the tiles in the target image")
+    ("tile-height", boost::program_options::value<int>(), "Height of the tiles in the target image")
+    ("compression", boost::program_options::value<std::string>(), 
+     "Compression of the target image (\"none\", \"jpeg\" or \"jpeg2000\")")
+    ("jpeg-quality", boost::program_options::value<int>(), "Set quality level for JPEG (0..100)")
+    ("max-size", boost::program_options::value<int>()->default_value(10), "Maximum size per DICOM instance (in MB)")
+    ("folder", boost::program_options::value<std::string>(),
+     "Folder where to store the output DICOM instances")
+    ("folder-pattern", boost::program_options::value<std::string>()->default_value("wsi-%06d.dcm"),
+     "Pattern for the files in the output folder")
+    ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"),
+     "URL to the REST API of the target Orthanc server")
+    ("username", boost::program_options::value<std::string>(), "Username for the target Orthanc server")
+    ("password", boost::program_options::value<std::string>(), "Password for the target Orthanc server")
+    ;
+
+  boost::program_options::options_description volumeOptions("Description of the imaged volume");
+  volumeOptions.add_options()
+    ("imaged-width", boost::program_options::value<float>()->default_value(15), "With of the specimen (in mm)")
+    ("imaged-height", boost::program_options::value<float>()->default_value(15), "Height of the specimen (in mm)")
+    ("imaged-depth", boost::program_options::value<float>()->default_value(1), "Depth of the specimen (in mm)")
+    ("offset-x", boost::program_options::value<float>()->default_value(20), 
+     "X offset the specimen, wrt. slide coordinates origin (in mm)")
+    ("offset-y", boost::program_options::value<float>()->default_value(40), 
+     "Y offset the specimen, wrt. slide coordinates origin (in mm)")
+    ;
+
+  boost::program_options::options_description advancedOptions("Advanced options");
+  advancedOptions.add_options()
+    ("optical-path", boost::program_options::value<std::string>()->default_value("brightfield"), 
+     "Optical path to be automatically added to the DICOM dataset (\"none\" or \"brightfield\")")
+    ("icc-profile", boost::program_options::value<std::string>(), 
+     "Path to the ICC profile to be included. If empty, a default sRGB profile will be added.")
+    ("safety", boost::program_options::value<bool>()->default_value(true), 
+     "Whether to do additional checks to verify the source image is supported (might slow down) (Boolean)")
+    ("lower-levels", boost::program_options::value<int>(), "Number of pyramid levels up to which multithreading "
+     "should be applied (only for performance/memory tuning)")
+    ;
+
+  boost::program_options::options_description hidden;
+  hidden.add_options()
+    ("input", boost::program_options::value<std::string>(), "Input file");
+  ;
+
+  boost::program_options::options_description allWithoutHidden;
+  allWithoutHidden.add(generic).add(source).add(pyramid).add(target).add(volumeOptions).add(advancedOptions);
+
+  boost::program_options::options_description all = allWithoutHidden;
+  all.add(hidden);
+
+  boost::program_options::positional_options_description positional;
+  positional.add("input", 1);
+
+  boost::program_options::variables_map options;
+  bool error = false;
+
+  try
+  {
+    boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
+                                  options(all).positional(positional).run(), options);
+    boost::program_options::notify(options);    
+  }
+  catch (boost::program_options::error& e)
+  {
+    LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what();
+    error = true;
+  }
+
+  if (!error &&
+      options.count("sample-dataset"))
+  {
+    std::string sample;
+    Orthanc::EmbeddedResources::GetFileResource(sample, Orthanc::EmbeddedResources::SAMPLE_DATASET);
+
+    std::cout << std::endl << sample << std::endl;
+
+    return false;
+  }
+
+  if (!error &&
+      options.count("help") == 0 &&
+      options.count("version") == 0 &&
+      options.count("input") != 1)
+  {
+    LOG(ERROR) << "No input file was specified";
+    error = true;
+  }
+
+  if (error || options.count("help")) 
+  {
+    std::cout << std::endl
+              << "Usage: " << argv[0] << " [OPTION]... [INPUT]"
+              << std::endl
+              << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
+              << std::endl << std::endl
+              << "Create a DICOM file from a digital pathology image."
+              << std::endl;
+
+    std::cout << allWithoutHidden << "\n";
+
+    if (error)
+    {
+      exitStatus = -1;
+    }
+
+    return false;
+  }
+
+  if (options.count("version")) 
+  { 
+    OrthancWSI::ApplicationToolbox::PrintVersion(argv[0]);
+    return false;
+  }
+
+  if (options.count("verbose"))
+  {
+    Orthanc::Logging::EnableInfoLevel(true);
+  }
+
+  if (options.count("openslide"))
+  {
+    OrthancWSI::OpenSlideLibrary::Initialize(options["openslide"].as<std::string>());
+  }
+
+  if (options.count("pyramid") &&
+      options["pyramid"].as<bool>())
+  {
+    parameters.SetReconstructPyramid(true);
+  }
+
+  if (options.count("smooth") &&
+      options["smooth"].as<bool>())
+  {
+    parameters.SetSmoothEnabled(true);
+  }
+
+  if (options.count("safety") &&
+      options["safety"].as<bool>())
+  {
+    parameters.SetSafetyCheck(true);
+  }
+
+  if (options.count("reencode") &&
+      options["reencode"].as<bool>())
+  {
+    parameters.SetForceReencode(true);
+  }
+
+  if (options.count("repaint") &&
+      options["repaint"].as<bool>())
+  {
+    parameters.SetRepaintBackground(true);
+  }
+
+  if (options.count("tile-width") ||
+      options.count("tile-height"))
+  {
+    int w = 0;
+    if (options.count("tile-width"))
+    {
+      w = options["tile-width"].as<int>();
+    }
+
+    unsigned int h = 0;
+    if (options.count("tile-height"))
+    {
+      h = options["tile-height"].as<int>();
+    }
+
+    if (w < 0 || h < 0)
+    {
+      LOG(ERROR) << "Negative target tile size specified: " << w << "x" << h;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    parameters.SetTargetTileSize(w, h);
+  }
+
+  parameters.SetInputFile(options["input"].as<std::string>());
+
+  if (options.count("color"))
+  {
+    uint8_t r, g, b;
+    OrthancWSI::ApplicationToolbox::ParseColor(r, g, b, options["color"].as<std::string>());
+    parameters.SetBackgroundColor(r, g, b);
+  }
+
+  if (options.count("compression"))
+  {
+    std::string s = options["compression"].as<std::string>();
+    if (s == "none")
+    {
+      parameters.SetTargetCompression(OrthancWSI::ImageCompression_None);
+    }
+    else if (s == "jpeg")
+    {
+      parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg);
+    }
+    else if (s == "jpeg2000")
+    {
+      parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg2000);
+    }
+    else
+    {
+      LOG(ERROR) << "Unknown image compression for the target image: " << s;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  if (options.count("jpeg-quality"))
+  {
+    parameters.SetJpegQuality(options["jpeg-quality"].as<int>());
+  }
+
+  if (options.count("levels"))
+  {
+    parameters.SetPyramidLevelsCount(options["levels"].as<int>());
+  }
+
+  if (options.count("lower-levels"))
+  {
+    parameters.SetPyramidLowerLevelsCount(options["lower-levels"].as<int>());
+  }
+
+  if (options.count("threads"))
+  {
+    parameters.SetThreadsCount(options["threads"].as<int>());
+  }
+
+  if (options.count("max-size"))
+  {
+    parameters.SetDicomMaxFileSize(options["max-size"].as<int>() * 1024 * 1024);
+  }
+
+  if (options.count("folder"))
+  {
+    parameters.SetTargetFolder(options["folder"].as<std::string>());
+  }
+
+  if (options.count("folder-pattern"))
+  {
+    parameters.SetTargetFolderPattern(options["folder-pattern"].as<std::string>());
+  }
+
+  if (options.count("orthanc"))
+  {
+    parameters.GetOrthancParameters().SetUrl(options["orthanc"].as<std::string>());
+
+    if (options.count("username") &&
+        options.count("password"))
+    {
+      parameters.GetOrthancParameters().SetUsername(options["username"].as<std::string>());
+      parameters.GetOrthancParameters().SetPassword(options["password"].as<std::string>());
+    }
+  }
+
+  if (options.count("dataset"))
+  {
+    parameters.SetDatasetPath(options["dataset"].as<std::string>());
+  }
+
+  if (options.count("imaged-width"))
+  {
+    volume.SetWidth(options["imaged-width"].as<float>());
+  }
+
+  if (options.count("imaged-height"))
+  {
+    volume.SetHeight(options["imaged-height"].as<float>());
+  }
+
+  if (options.count("imaged-depth"))
+  {
+    volume.SetDepth(options["imaged-depth"].as<float>());
+  }
+
+  if (options.count("offset-x"))
+  {
+    volume.SetOffsetX(options["offset-x"].as<float>());
+  }
+
+  if (options.count("offset-y"))
+  {
+    volume.SetOffsetY(options["offset-y"].as<float>());
+  }
+
+  if (options.count("optical-path"))
+  {
+    std::string s = options["optical-path"].as<std::string>();
+    if (s == "none")
+    {
+      parameters.SetOpticalPath(OrthancWSI::OpticalPath_None);
+    }
+    else if (s == "brightfield")
+    {
+      parameters.SetOpticalPath(OrthancWSI::OpticalPath_Brightfield);
+    }
+    else
+    {
+      LOG(ERROR) << "Unknown optical path definition: " << s;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  if (options.count("icc-profile"))
+  {
+    parameters.SetIccProfilePath(options["icc-profile"].as<std::string>());
+  }
+
+  return true;
+}
+
+
+OrthancWSI::ITiledPyramid* OpenInputPyramid(const std::string& path,
+                                            const OrthancWSI::DicomizerParameters& parameters)
+{
+  LOG(WARNING) << "The input image is: " << path;
+
+  OrthancWSI::ImageCompression format = OrthancWSI::DetectFormatFromFile(path);
+  LOG(WARNING) << "File format of the input image: " << EnumerationToString(format);
+
+  switch (format)
+  {
+    case OrthancWSI::ImageCompression_Png:
+      return new OrthancWSI::TiledPngImage(path, 
+                                           parameters.GetTargetTileWidth(512), 
+                                           parameters.GetTargetTileHeight(512));
+
+    case OrthancWSI::ImageCompression_Jpeg:
+      return new OrthancWSI::TiledJpegImage(path, 
+                                            parameters.GetTargetTileWidth(512), 
+                                            parameters.GetTargetTileHeight(512));
+
+    case OrthancWSI::ImageCompression_Tiff:
+    {
+      try
+      {
+        return new OrthancWSI::HierarchicalTiff(path);
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        LOG(WARNING) << "This is not a standard hierarchical TIFF file";
+      }
+    }
+
+    default:
+      break;
+  }
+
+  try
+  {
+    LOG(WARNING) << "Trying to open the input pyramid with OpenSlide";
+    return new OrthancWSI::OpenSlidePyramid(path, 
+                                            parameters.GetTargetTileWidth(512), 
+                                            parameters.GetTargetTileHeight(512));
+  }
+  catch (Orthanc::OrthancException&)
+  {
+    LOG(ERROR) << "This file is not supported by OpenSlide";
+    return NULL;
+  }
+}
+
+
+int main(int argc, char* argv[])
+{
+  OrthancWSI::ApplicationToolbox::GlobalInitialize();
+
+  int exitStatus = 0;
+
+  try
+  {
+    OrthancWSI::DicomizerParameters  parameters;
+    OrthancWSI::ImagedVolumeParameters volume;
+
+    if (ParseParameters(exitStatus, parameters, volume, argc, argv))
+    {
+      std::auto_ptr<OrthancWSI::ITiledPyramid> source(OpenInputPyramid(parameters.GetInputFile(), parameters));
+      if (source.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      
+      // Create the shared DICOM tags
+      std::auto_ptr<DcmDataset> dataset(ParseDataset(parameters.GetDatasetPath()));
+      EnrichDataset(*dataset, *source, parameters, volume);
+
+      std::auto_ptr<OrthancWSI::IFileTarget> output(parameters.CreateTarget());
+      Recompress(*output, *source, *dataset, parameters, volume);
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Terminating on exception: " << e.What();
+    exitStatus = -1;
+  }
+
+  OrthancWSI::ApplicationToolbox::GlobalFinalize();
+
+  return exitStatus;
+}
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..dba13ed
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/Framework/Algorithms/PyramidReader.cpp b/Framework/Algorithms/PyramidReader.cpp
new file mode 100644
index 0000000..1bb30b1
--- /dev/null
+++ b/Framework/Algorithms/PyramidReader.cpp
@@ -0,0 +1,306 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "PyramidReader.h"
+
+#include "../ImageToolbox.h"
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <cassert>
+
+namespace OrthancWSI
+{
+  class PyramidReader::SourceTile : public boost::noncopyable
+  {
+  private: 
+    PyramidReader&  that_;
+    unsigned int    tileX_;
+    unsigned int    tileY_;
+    bool            hasRawTile_;
+    std::string     rawTile_;
+
+    std::auto_ptr<Orthanc::ImageAccessor>  decoded_;
+
+    bool IsRepaintNeeded() const
+    {
+      return (that_.parameters_.IsRepaintBackground() &&
+              ((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_ ||
+               (tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_));
+    }
+
+    void RepaintBackground()
+    {
+      assert(decoded_.get() != NULL);
+      LOG(INFO) << "Repainting background of tile ("
+                << tileX_ << "," << tileY_ << ") at level " << that_.level_;
+
+      if ((tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_)
+      {
+        // Bottom overflow
+        assert(tileY_ * that_.sourceTileHeight_ < that_.levelHeight_);
+
+        unsigned int bottom = that_.levelHeight_ - tileY_ * that_.sourceTileHeight_;
+        Orthanc::ImageAccessor a = decoded_->GetRegion(0, bottom, 
+                                                       that_.sourceTileWidth_, 
+                                                       that_.sourceTileHeight_ - bottom);
+        ImageToolbox::Set(a, 
+                          that_.parameters_.GetBackgroundColorRed(),
+                          that_.parameters_.GetBackgroundColorGreen(),
+                          that_.parameters_.GetBackgroundColorBlue());
+
+      }
+
+      if ((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_)
+      {
+        // Right overflow
+        assert(tileX_ * that_.sourceTileWidth_ < that_.levelWidth_);
+
+        unsigned int right = that_.levelWidth_ - tileX_ * that_.sourceTileWidth_;
+        Orthanc::ImageAccessor a = decoded_->GetRegion(right, 0, 
+                                                       that_.sourceTileWidth_ - right, 
+                                                       that_.sourceTileHeight_);
+        ImageToolbox::Set(a,
+                          that_.parameters_.GetBackgroundColorRed(),
+                          that_.parameters_.GetBackgroundColorGreen(),
+                          that_.parameters_.GetBackgroundColorBlue());
+      }
+    }
+
+
+  public:
+    SourceTile(PyramidReader& that,
+               unsigned int tileX,
+               unsigned int tileY) :
+      that_(that),
+      tileX_(tileX),
+      tileY_(tileY)
+    {
+      if (!that_.parameters_.IsForceReencode() &&
+          !IsRepaintNeeded() &&
+          that_.source_.ReadRawTile(rawTile_, that_.level_, tileX, tileY))
+      {
+        hasRawTile_ = true;
+      }
+      else
+      {
+        hasRawTile_ = false;
+        decoded_.reset(that_.source_.DecodeTile(that_.level_, tileX, tileY));
+        if (decoded_.get() == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        RepaintBackground();
+      }
+    }
+
+    bool HasRawTile() const
+    {
+      return hasRawTile_;
+    }
+
+    const std::string& GetRawTile() const
+    {
+      if (hasRawTile_)
+      {
+        return rawTile_;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    const Orthanc::ImageAccessor& GetDecodedTile()
+    {
+      if (decoded_.get() == NULL)
+      {
+        if (!hasRawTile_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        decoded_.reset(ImageToolbox::DecodeTile(rawTile_, that_.source_.GetImageCompression()));
+        if (decoded_.get() == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+          
+        RepaintBackground();
+      }
+
+      return *decoded_;
+    }
+  };
+
+
+  Orthanc::ImageAccessor& PyramidReader::GetOutsideTile()
+  {
+    if (outside_.get() == NULL)
+    {
+      outside_.reset(ImageToolbox::Allocate(source_.GetPixelFormat(), targetTileWidth_, targetTileHeight_));
+      ImageToolbox::Set(*outside_,
+                        parameters_.GetBackgroundColorRed(),
+                        parameters_.GetBackgroundColorGreen(),
+                        parameters_.GetBackgroundColorBlue());
+    }
+
+    return *outside_;
+  }
+
+
+  void PyramidReader::CheckTileSize(const Orthanc::ImageAccessor& tile) const
+  {
+    if (tile.GetWidth() != sourceTileWidth_ ||
+        tile.GetHeight() != sourceTileHeight_)
+    {
+      LOG(ERROR) << "One tile in the input image has size " << tile.GetWidth() << "x" << tile.GetHeight() 
+                 << " instead of required " << source_.GetTileWidth() << "x" << source_.GetTileHeight();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+  }
+
+
+  void PyramidReader::CheckTileSize(const std::string& tile) const
+  {
+    if (parameters_.IsSafetyCheck())
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(tile, source_.GetImageCompression()));
+      CheckTileSize(*decoded);
+    }
+  }
+
+
+  PyramidReader::SourceTile& PyramidReader::AccessSourceTile(const Location& location)
+  {
+    Cache::iterator found = cache_.find(location);
+    if (found != cache_.end())
+    {
+      return *found->second;
+    }
+    else
+    {
+      SourceTile *tile = new SourceTile(*this, location.first, location.second);
+      cache_[location] = tile;
+      return *tile;
+    }
+  }
+
+
+  PyramidReader::Location PyramidReader::MapTargetToSourceLocation(unsigned int tileX,
+                                                                   unsigned int tileY)
+  {
+    return std::make_pair(tileX / (sourceTileWidth_ / targetTileWidth_),
+                          tileY / (sourceTileHeight_ / targetTileHeight_));
+  }
+
+
+  PyramidReader::PyramidReader(ITiledPyramid& source,
+                               unsigned int level,
+                               unsigned int targetTileWidth,
+                               unsigned int targetTileHeight,
+                               const DicomizerParameters& parameters) :
+    source_(source),
+    level_(level),
+    levelWidth_(source.GetLevelWidth(level)),
+    levelHeight_(source.GetLevelHeight(level)),
+    sourceTileWidth_(source.GetTileWidth()),
+    sourceTileHeight_(source.GetTileHeight()),
+                                                                         targetTileWidth_(targetTileWidth),
+                                                                         targetTileHeight_(targetTileHeight),
+                                                                         parameters_(parameters)
+  {
+    if (sourceTileWidth_ % targetTileWidth_ != 0 ||
+        sourceTileHeight_ % targetTileHeight_ != 0)
+    {
+      LOG(ERROR) << "When resampling the tile size, it must be a integer divisor of the original tile size";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+  }
+
+
+  PyramidReader::~PyramidReader()
+  {
+    for (Cache::iterator it = cache_.begin(); it != cache_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+
+  const std::string* PyramidReader::GetRawTile(unsigned int tileX,
+                                               unsigned int tileY)
+  {
+    if (sourceTileWidth_ != targetTileWidth_ ||
+        sourceTileHeight_ != targetTileHeight_)
+    {
+      return NULL;
+    }
+
+    SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
+    if (source.HasRawTile())
+    {
+      CheckTileSize(source.GetRawTile());
+      return &source.GetRawTile();
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  Orthanc::ImageAccessor PyramidReader::GetDecodedTile(unsigned int tileX,
+                                                       unsigned int tileY)
+  {
+    if (tileX * targetTileWidth_ >= levelWidth_ ||
+        tileY * targetTileHeight_ >= levelHeight_)
+    {
+      // Accessing a tile out of the source image
+      return GetOutsideTile();
+    }
+
+    SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
+    const Orthanc::ImageAccessor& tile = source.GetDecodedTile();
+
+    CheckTileSize(tile);
+
+    assert(sourceTileWidth_ % targetTileWidth_ == 0 &&
+           sourceTileHeight_ % targetTileHeight_ == 0);
+
+    unsigned int xx = tileX % (sourceTileWidth_ / targetTileWidth_);
+    unsigned int yy = tileY % (sourceTileHeight_ / targetTileHeight_);
+
+    const uint8_t* bytes = 
+      reinterpret_cast<const uint8_t*>(tile.GetConstRow(yy * targetTileHeight_)) +
+      GetBytesPerPixel(tile.GetFormat()) * xx * targetTileWidth_;
+
+    Orthanc::ImageAccessor region;
+    region.AssignReadOnly(tile.GetFormat(),
+                          targetTileWidth_,
+                          targetTileHeight_,
+                          tile.GetPitch(), bytes);                                    
+
+    return region;
+  }
+}
diff --git a/Framework/Algorithms/PyramidReader.h b/Framework/Algorithms/PyramidReader.h
new file mode 100644
index 0000000..d8ede82
--- /dev/null
+++ b/Framework/Algorithms/PyramidReader.h
@@ -0,0 +1,96 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Inputs/ITiledPyramid.h"
+#include "../DicomizerParameters.h"
+
+#include <map>
+#include <memory>
+
+namespace OrthancWSI
+{
+  // This class is not thread-safe
+  class PyramidReader : public boost::noncopyable
+  {
+  private:
+    class SourceTile;
+
+    typedef std::pair<unsigned int, unsigned int>  Location;
+    typedef std::map<Location, SourceTile*>        Cache;
+
+    ITiledPyramid&  source_;
+    unsigned int    level_;
+    unsigned int    levelWidth_;
+    unsigned int    levelHeight_;
+    unsigned int    sourceTileWidth_;
+    unsigned int    sourceTileHeight_;
+    unsigned int    targetTileWidth_;
+    unsigned int    targetTileHeight_;
+    const DicomizerParameters& parameters_;
+
+    Cache           cache_;
+
+    std::auto_ptr<Orthanc::ImageAccessor>  outside_;
+
+
+    Orthanc::ImageAccessor& GetOutsideTile();
+
+    void CheckTileSize(const Orthanc::ImageAccessor& tile) const;
+
+    void CheckTileSize(const std::string& tile) const;
+
+    SourceTile& AccessSourceTile(const Location& location);
+
+    Location MapTargetToSourceLocation(unsigned int tileX,
+                                       unsigned int tileY);
+
+  public:
+    PyramidReader(ITiledPyramid& source,
+                  unsigned int level,
+                  unsigned int targetTileWidth,
+                  unsigned int targetTileHeight,
+                  const DicomizerParameters& parameters);
+
+    ~PyramidReader();
+
+    const DicomizerParameters& GetParameters() const
+    {
+      return parameters_;
+    }
+
+    ImageCompression GetImageCompression() const
+    {
+      return source_.GetImageCompression();
+    }
+
+    Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return source_.GetPixelFormat();
+    }
+
+    const std::string* GetRawTile(unsigned int tileX,
+                                  unsigned int tileY);
+
+    Orthanc::ImageAccessor GetDecodedTile(unsigned int tileX,
+                                          unsigned int tileY);  
+  };
+}
diff --git a/Framework/Algorithms/ReconstructPyramidCommand.cpp b/Framework/Algorithms/ReconstructPyramidCommand.cpp
new file mode 100644
index 0000000..fd4f368
--- /dev/null
+++ b/Framework/Algorithms/ReconstructPyramidCommand.cpp
@@ -0,0 +1,185 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "ReconstructPyramidCommand.h"
+
+#include "../ImageToolbox.h"
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Images/Image.h"
+
+#include <cassert>
+
+namespace OrthancWSI
+{
+  Orthanc::ImageAccessor* ReconstructPyramidCommand::Explore(unsigned int level,
+                                                             unsigned int offsetX,
+                                                             unsigned int offsetY)
+  {
+    unsigned int zoom = 1 << level;
+    assert(x_ % zoom == 0 && y_ % zoom == 0);
+    unsigned int x = x_ / zoom + offsetX;
+    unsigned int y = y_ / zoom + offsetY;
+
+    if (x >= target_.GetCountTilesX(level + shiftTargetLevel_) ||
+        y >= target_.GetCountTilesY(level + shiftTargetLevel_))
+    {
+      return NULL;
+    }
+
+    std::auto_ptr<Orthanc::ImageAccessor> result;
+
+    if (level == 0)
+    {
+      result.reset(new Orthanc::ImageAccessor(source_.GetDecodedTile(x, y)));
+
+      const std::string* rawTile = source_.GetRawTile(x, y);
+
+      if (rawTile != NULL)
+      {
+        // Simple transcoding
+        target_.WriteRawTile(*rawTile, source_.GetImageCompression(), level + shiftTargetLevel_, x, y);
+      }
+      else
+      {
+        // Re-encoding the file
+        target_.EncodeTile(*result, level + shiftTargetLevel_, x, y);
+      }
+    }
+    else
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> mosaic(ImageToolbox::Allocate(source_.GetPixelFormat(), 
+                                                                          2 * target_.GetTileWidth(), 
+                                                                          2 * target_.GetTileHeight()));
+      ImageToolbox::Set(*mosaic, 
+                        source_.GetParameters().GetBackgroundColorRed(),
+                        source_.GetParameters().GetBackgroundColorGreen(),
+                        source_.GetParameters().GetBackgroundColorBlue());
+
+      {
+        std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX, 2 * offsetY));
+        if (subTile.get() != NULL)
+        {
+          ImageToolbox::Embed(*mosaic, *subTile, 0, 0);
+        }
+      }
+
+      {
+        std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX + 1, 2 * offsetY));
+        if (subTile.get() != NULL)
+        {
+          ImageToolbox::Embed(*mosaic, *subTile, target_.GetTileWidth(), 0);
+        }
+      }
+
+      {
+        std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX, 2 * offsetY + 1));
+        if (subTile.get() != NULL)
+        {
+          ImageToolbox::Embed(*mosaic, *subTile, 0, target_.GetTileHeight());
+        }
+      }
+
+      {
+        std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX + 1, 2 * offsetY + 1));
+        if (subTile.get() != NULL)
+        {
+          ImageToolbox::Embed(*mosaic, *subTile, target_.GetTileWidth(), target_.GetTileHeight());
+        }
+      }
+
+      result.reset(ImageToolbox::Halve(*mosaic, source_.GetParameters().IsSmoothEnabled()));
+
+      target_.EncodeTile(*result, level + shiftTargetLevel_, x, y);
+    }
+
+    return result.release();
+  }
+
+  
+  ReconstructPyramidCommand::ReconstructPyramidCommand(IPyramidWriter& target,
+                                                       ITiledPyramid& source,
+                                                       unsigned int upToLevel,
+                                                       unsigned int x,
+                                                       unsigned int y,
+                                                       const DicomizerParameters& parameters) :
+    target_(target),
+    source_(source, 0, target.GetTileWidth(), target.GetTileHeight(), parameters),
+    upToLevel_(upToLevel),
+    x_(x),
+    y_(y),
+    shiftTargetLevel_(0)
+  {
+    unsigned int zoom = 1 << upToLevel;
+    if (x % zoom != 0 ||
+        y % zoom != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (target.GetPixelFormat() != source.GetPixelFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+  }
+
+
+  bool ReconstructPyramidCommand::Execute()
+  {
+    std::auto_ptr<Orthanc::ImageAccessor> root(Explore(upToLevel_, 0, 0));
+    return true;
+  }
+
+
+  void ReconstructPyramidCommand::PrepareBagOfTasks(Orthanc::BagOfTasks& tasks,
+                                                    IPyramidWriter& target,
+                                                    ITiledPyramid& source,
+                                                    unsigned int countLevels,
+                                                    unsigned int shiftTargetLevel,    
+                                                    const DicomizerParameters& parameters)
+  {
+    if (countLevels == 0)
+    {
+      return;
+    }
+
+    if (shiftTargetLevel + countLevels > target.GetLevelCount())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    const unsigned int targetCountTilesX = target.GetCountTilesX(shiftTargetLevel);
+    const unsigned int targetCountTilesY = target.GetCountTilesY(shiftTargetLevel);
+    const unsigned int step = 1 << (countLevels - 1);
+
+    for (unsigned int y = 0; y < targetCountTilesY; y += step)
+    {
+      for (unsigned int x = 0; x < targetCountTilesX; x += step)
+      {
+        std::auto_ptr<ReconstructPyramidCommand> command;
+        command.reset(new ReconstructPyramidCommand
+                      (target, source, countLevels - 1, x, y, parameters));
+        command->SetShiftTargetLevel(shiftTargetLevel);
+        tasks.Push(command.release());
+      }
+    }
+  }
+}
diff --git a/Framework/Algorithms/ReconstructPyramidCommand.h b/Framework/Algorithms/ReconstructPyramidCommand.h
new file mode 100644
index 0000000..d2f6c09
--- /dev/null
+++ b/Framework/Algorithms/ReconstructPyramidCommand.h
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PyramidReader.h"
+#include "../Outputs/IPyramidWriter.h"
+#include "../Orthanc/Core/MultiThreading/BagOfTasks.h"
+
+
+namespace OrthancWSI
+{
+  class ReconstructPyramidCommand : public Orthanc::ICommand
+  {
+  private:
+    IPyramidWriter& target_;
+    PyramidReader  source_;
+
+    unsigned int upToLevel_;
+    unsigned int x_;
+    unsigned int y_;
+    unsigned int shiftTargetLevel_;
+
+    Orthanc::ImageAccessor* Explore(unsigned int level,
+                                    unsigned int offsetX,
+                                    unsigned int offsetY);
+
+  public:
+    ReconstructPyramidCommand(IPyramidWriter& target,
+                              ITiledPyramid& source,
+                              unsigned int upToLevel,
+                              unsigned int x,
+                              unsigned int y,
+                              const DicomizerParameters& parameters);
+
+    void SetShiftTargetLevel(unsigned int shift)
+    {
+      shiftTargetLevel_ = shift;
+    }
+
+    unsigned int GetShiftTargetLevel() const
+    {
+      return shiftTargetLevel_;
+    }
+
+    virtual bool Execute();
+
+    static void PrepareBagOfTasks(Orthanc::BagOfTasks& tasks,
+                                  IPyramidWriter& target,
+                                  ITiledPyramid& source,
+                                  unsigned int countLevels,
+                                  unsigned int shiftTargetLevel,    
+                                  const DicomizerParameters& parameters);
+  };
+}
diff --git a/Framework/Algorithms/TranscodeTileCommand.cpp b/Framework/Algorithms/TranscodeTileCommand.cpp
new file mode 100644
index 0000000..4409c0a
--- /dev/null
+++ b/Framework/Algorithms/TranscodeTileCommand.cpp
@@ -0,0 +1,121 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "TranscodeTileCommand.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Logging.h"
+
+#include <cassert>
+
+namespace OrthancWSI
+{
+  TranscodeTileCommand::TranscodeTileCommand(IPyramidWriter& target,
+                                             ITiledPyramid& source,
+                                             unsigned int level,
+                                             unsigned int x,
+                                             unsigned int y,
+                                             unsigned int countTilesX,
+                                             unsigned int countTilesY,
+                                             const DicomizerParameters& parameters) :
+  target_(target),
+    source_(source, level, target.GetTileWidth(), target.GetTileHeight(), parameters),
+    level_(level),
+    x_(x),
+    y_(y),
+    countTilesX_(countTilesX),
+    countTilesY_(countTilesY)
+  {
+    assert(x_ + countTilesX_ <= target_.GetCountTilesX(level_) &&
+           y_ + countTilesY_ <= target_.GetCountTilesY(level_));
+
+    if (target.GetPixelFormat() != source.GetPixelFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+  }
+
+
+  bool TranscodeTileCommand::Execute()
+  {
+    for (unsigned int x = x_; x < x_ + countTilesX_; x++)
+    {
+      for (unsigned int y = y_; y < y_ + countTilesY_; y++)
+      {
+        LOG(INFO) << "Adding tile (" << x << "," << y << ") at level " << level_;
+        const std::string* rawTile = source_.GetRawTile(x, y);
+
+        if (rawTile != NULL)
+        {
+          // Simple transcoding
+          target_.WriteRawTile(*rawTile, source_.GetImageCompression(), level_, x, y);
+        }
+        else
+        {
+          Orthanc::ImageAccessor tile = source_.GetDecodedTile(x, y);
+
+          // Re-encoding the file
+          target_.EncodeTile(tile, level_, x, y);
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+  void TranscodeTileCommand::PrepareBagOfTasks(Orthanc::BagOfTasks& tasks,
+                                               IPyramidWriter& target,
+                                               ITiledPyramid& source,
+                                               const DicomizerParameters& parameters)
+  {
+    const unsigned int stepX = source.GetTileWidth() / target.GetTileWidth();
+    const unsigned int stepY = source.GetTileHeight() / target.GetTileHeight();
+    assert(stepX >= 1 && stepY >= 1);
+
+    for (unsigned int level = 0; level < source.GetLevelCount(); level++)
+    {
+      const unsigned int targetCountTilesX = target.GetCountTilesX(level);
+      const unsigned int targetCountTilesY = target.GetCountTilesY(level);
+
+      for (unsigned int y = 0; y < targetCountTilesY; y += stepY)
+      {
+        unsigned int cy = stepY;
+        if (y + cy > targetCountTilesY)
+        {
+          cy = targetCountTilesY - y;
+        }
+
+        for (unsigned int x = 0; x < targetCountTilesX; x += stepX)
+        {
+          unsigned int cx = stepX;
+          if (x + cx > targetCountTilesX)
+          {
+            cx = targetCountTilesX - x;
+          }
+
+          tasks.Push(new TranscodeTileCommand
+                     (target, source, level, x, y, cx, cy, parameters));
+        }
+      }
+    }
+  }
+}
diff --git a/Framework/Algorithms/TranscodeTileCommand.h b/Framework/Algorithms/TranscodeTileCommand.h
new file mode 100644
index 0000000..7136140
--- /dev/null
+++ b/Framework/Algorithms/TranscodeTileCommand.h
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PyramidReader.h"
+#include "../Outputs/IPyramidWriter.h"
+
+#include "../Orthanc/Core/MultiThreading/BagOfTasks.h"
+
+namespace OrthancWSI
+{
+  class TranscodeTileCommand : public Orthanc::ICommand
+  {
+  private:
+    IPyramidWriter& target_;
+    PyramidReader  source_;
+
+    unsigned int  level_;
+    unsigned int x_;
+    unsigned int y_;
+    unsigned int countTilesX_;
+    unsigned int countTilesY_;
+
+  public:
+    TranscodeTileCommand(IPyramidWriter& target,
+                         ITiledPyramid& source,
+                         unsigned int level,
+                         unsigned int x,
+                         unsigned int y,
+                         unsigned int countTilesX,
+                         unsigned int countTilesY,
+                         const DicomizerParameters& parameters);
+
+    virtual bool Execute();
+
+    static void PrepareBagOfTasks(Orthanc::BagOfTasks& tasks,
+                                  IPyramidWriter& target,
+                                  ITiledPyramid& source,
+                                  const DicomizerParameters& parameters);
+  };
+}
diff --git a/Framework/DicomToolbox.cpp b/Framework/DicomToolbox.cpp
new file mode 100644
index 0000000..1130a30
--- /dev/null
+++ b/Framework/DicomToolbox.cpp
@@ -0,0 +1,251 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
+#include "DicomToolbox.h"
+
+#include "Orthanc/Core/Logging.h"
+#include "Orthanc/Core/OrthancException.h"
+#include "Orthanc/Core/Toolbox.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include <dcmtk/dcmdata/dcelem.h>
+#  include <dcmtk/dcmdata/dcsequen.h>
+#endif
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancWSI
+{
+  namespace DicomToolbox
+  {
+#if ORTHANC_ENABLE_DCMTK == 1
+    void SetStringTag(DcmItem& dataset,
+                      const DcmTagKey& key,
+                      const std::string& value)
+    {
+      if (!dataset.tagExists(key) &&
+          !dataset.putAndInsertString(key, value.c_str()).good())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    void SetUint16Tag(DcmItem& dataset,
+                      const DcmTagKey& key,
+                      uint16_t value)
+    {
+      if (!dataset.tagExists(key) &&
+          !dataset.putAndInsertUint16(key, value).good())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+
+    void SetUint32Tag(DcmItem& dataset,
+                      const DcmTagKey& key,
+                      uint32_t value)
+    {
+      if (!dataset.tagExists(key) &&
+          !dataset.putAndInsertUint32(key, value).good())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+
+    DcmItem* ExtractSingleSequenceItem(DcmItem& dataset,
+                                       const DcmTagKey& key)
+    {
+      DcmElement* element = NULL;
+      if (!const_cast<DcmItem&>(dataset).findAndGetElement(key, element).good() ||
+          element == NULL)
+      {
+        return NULL;
+      }
+
+      if (element->getVR() != EVR_SQ)
+      {
+        DcmTag tag(key);
+        LOG(ERROR) << "The following element in the DICOM dataset is not a sequence as expected: " << tag.getTagName();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+      if (sequence.card() != 1)
+      {
+        LOG(ERROR) << "Bad number of elements in the sequence (it must contain exactly 1 element)";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      return sequence.getItem(0);
+    }
+
+
+    uint16_t GetUint16Tag(DcmItem& dataset,
+                          const DcmTagKey& key)
+    {
+      uint16_t value;
+      if (dataset.findAndGetUint16(key, value).good())
+      {
+        return value;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
+      }
+    }
+
+
+    uint32_t GetUint32Tag(DcmItem& dataset,
+                          const DcmTagKey& key)
+    {
+      Uint32 value;
+      if (dataset.findAndGetUint32(key, value).good())
+      {
+        return value;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
+      }
+    }
+
+
+    int32_t GetInt32Tag(DcmItem& dataset,
+                        const DcmTagKey& key)
+    {
+      Sint32 value;
+      if (dataset.findAndGetSint32(key, value).good())
+      {
+        return value;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
+      }
+    }
+
+
+    std::string GetStringTag(DcmItem& dataset,
+                             const DcmTagKey& key)
+    {
+      const char* value = NULL;
+      if (dataset.findAndGetString(key, value).good() &&
+          value != NULL)
+      {
+        return Orthanc::Toolbox::StripSpaces(value);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
+      }
+    }
+#endif
+
+
+    bool GetStringTag(std::string& result,
+                      const Json::Value& simplifiedTags,
+                      const std::string& tagName,
+                      const std::string& defaultValue)
+    {
+      if (simplifiedTags.type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      if (!simplifiedTags.isMember(tagName))
+      {
+        result = defaultValue;
+        return false;
+      }
+      else if (simplifiedTags[tagName].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        result = simplifiedTags[tagName].asString();
+        return true;
+      }
+    }
+
+
+    std::string GetMandatoryStringTag(const Json::Value& simplifiedTags,
+                                      const std::string& tagName)
+    {
+      std::string s;
+      if (GetStringTag(s, simplifiedTags, tagName, ""))
+      {
+        return s;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);        
+      }
+    }
+
+
+    const Json::Value& GetSequenceTag(const Json::Value& simplifiedTags,
+                                      const std::string& tagName)
+    {
+      if (simplifiedTags.type() != Json::objectValue ||
+          !simplifiedTags.isMember(tagName) ||
+          simplifiedTags[tagName].type() != Json::arrayValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      return simplifiedTags[tagName];
+    }
+
+
+    int GetIntegerTag(const Json::Value& simplifiedTags,
+                      const std::string& tagName)
+    {
+      try
+      {
+        std::string s = Orthanc::Toolbox::StripSpaces(GetMandatoryStringTag(simplifiedTags, tagName));
+        return boost::lexical_cast<int>(s);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+      }
+    }
+
+
+    unsigned int GetUnsignedIntegerTag(const Json::Value& simplifiedTags,
+                                       const std::string& tagName)
+    {
+      int value = GetIntegerTag(simplifiedTags, tagName);
+
+      if (value >= 0)
+      {
+        return static_cast<unsigned int>(value);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+  }
+}
diff --git a/Framework/DicomToolbox.h b/Framework/DicomToolbox.h
new file mode 100644
index 0000000..f360226
--- /dev/null
+++ b/Framework/DicomToolbox.h
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include <dcmtk/dcmdata/dcdeftag.h>
+#  include <dcmtk/dcmdata/dcitem.h>
+#  include <dcmtk/dcmdata/dctagkey.h>
+#endif
+
+#include <string>
+#include <stdint.h>
+#include <json/value.h>
+
+namespace OrthancWSI
+{
+  namespace DicomToolbox
+  {
+#if ORTHANC_ENABLE_DCMTK == 1
+    void SetStringTag(DcmItem& dataset,
+                      const DcmTagKey& key,
+                      const std::string& value);
+
+    void SetUint16Tag(DcmItem& dataset,
+                      const DcmTagKey& key,
+                      uint16_t value);
+
+    void SetUint32Tag(DcmItem& dataset,
+                      const DcmTagKey& key,
+                      uint32_t value);
+
+    DcmItem* ExtractSingleSequenceItem(DcmItem& dataset,
+                                       const DcmTagKey& key);
+
+    uint16_t GetUint16Tag(DcmItem& dataset,
+                          const DcmTagKey& key);
+
+    uint32_t GetUint32Tag(DcmItem& dataset,
+                          const DcmTagKey& key);
+
+    int32_t GetInt32Tag(DcmItem& dataset,
+                        const DcmTagKey& key);
+
+    std::string GetStringTag(DcmItem& dataset,
+                             const DcmTagKey& key);
+#endif
+
+    bool GetStringTag(std::string& result,
+                      const Json::Value& simplifiedTags,
+                      const std::string& tagName,
+                      const std::string& defaultValue);
+
+    std::string GetMandatoryStringTag(const Json::Value& simplifiedTags,
+                                      const std::string& tagName);
+
+    const Json::Value& GetSequenceTag(const Json::Value& simplifiedTags,
+                                      const std::string& tagName);
+
+    int GetIntegerTag(const Json::Value& simplifiedTags,
+                      const std::string& tagName);
+
+    unsigned int GetUnsignedIntegerTag(const Json::Value& simplifiedTags,
+                                       const std::string& tagName);
+  }
+}
diff --git a/Framework/DicomizerParameters.cpp b/Framework/DicomizerParameters.cpp
new file mode 100644
index 0000000..c16c403
--- /dev/null
+++ b/Framework/DicomizerParameters.cpp
@@ -0,0 +1,275 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
+#include "DicomizerParameters.h"
+
+#include "Messaging/FolderTarget.h"
+#include "Messaging/OrthancTarget.h"
+
+#include "Orthanc/Core/OrthancException.h"
+
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancWSI
+{
+  static unsigned int ChooseNumberOfThreads()
+  {
+    unsigned int nthreads = boost::thread::hardware_concurrency();
+
+    if (nthreads % 2 == 0)
+    {
+      nthreads = nthreads / 2;
+    }
+    else
+    {
+      nthreads = nthreads / 2 + 1;
+    }
+
+    if (nthreads == 0)
+    {
+      nthreads = 1;
+    }
+
+    return nthreads;
+  }
+
+
+  DicomizerParameters::DicomizerParameters()
+  {
+    safetyCheck_ = false;
+    repaintBackground_ = false;
+    backgroundColor_[0] = 255;
+    backgroundColor_[1] = 255;
+    backgroundColor_[2] = 255;
+    targetCompression_ = ImageCompression_Jpeg;
+    hasTargetTileSize_ = false;
+    threadsCount_ = ChooseNumberOfThreads();
+    maxDicomFileSize_ = 10 * 1024 * 1024;   // 10MB
+    reconstructPyramid_ = false;
+    pyramidLevelsCount_ = 0;
+    pyramidLowerLevelsCount_ = 0;
+    smooth_ = false;
+    jpegQuality_ = 90;
+    forceReencode_ = false;
+    opticalPath_ = OpticalPath_Brightfield;
+  }
+
+
+  void DicomizerParameters::SetBackgroundColor(uint8_t red,
+                                               uint8_t green,
+                                               uint8_t blue)
+  {
+    repaintBackground_ = true;
+    backgroundColor_[0] = red;
+    backgroundColor_[1] = green;
+    backgroundColor_[2] = blue;
+  }
+
+
+  void DicomizerParameters::SetTargetTileSize(unsigned int width,
+                                              unsigned int height)
+  {
+    hasTargetTileSize_ = true;
+    targetTileWidth_ = width;
+    targetTileHeight_ = height;
+  }
+
+
+  unsigned int DicomizerParameters::GetTargetTileWidth(unsigned int defaultWidth) const
+  {
+    if (hasTargetTileSize_ &&
+        targetTileWidth_ != 0)
+    {
+      return targetTileWidth_;
+    }
+    else
+    {
+      return defaultWidth;
+    }
+  }
+
+
+  unsigned int DicomizerParameters::GetTargetTileHeight(unsigned int defaultHeight) const
+  {
+    if (hasTargetTileSize_ &&
+        targetTileHeight_ != 0)
+    {
+      return targetTileHeight_;
+    }
+    else
+    {
+      return defaultHeight;
+    }
+  }
+
+
+  void DicomizerParameters::SetThreadsCount(unsigned int threads)
+  {
+    if (threads == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    threadsCount_ = threads;
+  }
+
+
+  void DicomizerParameters::SetDicomMaxFileSize(unsigned int size)
+  {
+    if (size <= 1024)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    maxDicomFileSize_ = size;
+  }
+
+
+  void DicomizerParameters::SetPyramidLevelsCount(unsigned int count)
+  {
+    if (count <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    pyramidLevelsCount_ = count;
+  }
+
+
+  unsigned int DicomizerParameters::GetPyramidLevelsCount(const IPyramidWriter& target,
+                                                          const ITiledPyramid& source) const
+  {
+    if (!reconstructPyramid_)
+    {
+      // Only makes sense if reconstructing the pyramid
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (pyramidLevelsCount_ != 0)
+    {
+      return pyramidLevelsCount_;
+    }
+
+    // By default, the number of levels for the pyramid is taken so that
+    // the upper level is reduced either to 1 column of tiles, or to 1
+    // row of tiles.
+    unsigned int totalWidth = source.GetLevelWidth(0);
+    unsigned int totalHeight = source.GetLevelHeight(0);
+
+    unsigned int countLevels = 1;
+    for (;;)
+    {
+      unsigned int zoom = 1 << (countLevels - 1);
+
+      if (CeilingDivision(totalWidth, zoom) <= target.GetTileWidth() ||
+          CeilingDivision(totalHeight, zoom) <= target.GetTileHeight())
+      {
+        break;
+      }
+
+      countLevels += 1;
+    }
+
+    return countLevels;
+  }
+
+
+  void DicomizerParameters::SetPyramidLowerLevelsCount(unsigned int count)
+  {
+    if (count <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    pyramidLowerLevelsCount_ = count;
+  }
+
+
+  unsigned int DicomizerParameters::GetPyramidLowerLevelsCount(const IPyramidWriter& target,
+                                                               const ITiledPyramid& source) const
+  {
+    if (!reconstructPyramid_)
+    {
+      // Only makes sense if reconstructing the pyramid
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (pyramidLowerLevelsCount_ != 0)
+    {
+      return pyramidLowerLevelsCount_;
+    }
+
+    unsigned int fullNumberOfTiles = (
+      CeilingDivision(source.GetLevelWidth(0), source.GetTileWidth()) * 
+      CeilingDivision(source.GetLevelHeight(0), source.GetTileHeight()));
+
+    // By default, the number of lower levels in the pyramid is chosen
+    // as a compromise between the number of tasks (there should not be
+    // too few tasks, otherwise multithreading would not be efficient)
+    // and memory consumption (maximum 64MB of RAM due to the decoding
+    // of the tiles of the source image per thread: cf. PyramidReader).
+    unsigned int result = 1;
+    for (;;)
+    {
+      unsigned int zoom = 1 << (result - 1);
+      unsigned int numberOfTiles = CeilingDivision(fullNumberOfTiles, zoom * zoom);
+
+      if (result + 1 > target.GetLevelCount() ||
+          numberOfTiles < 4 * GetThreadsCount() ||
+          zoom * target.GetTileWidth() > 4096 ||
+          zoom * target.GetTileHeight() > 4096)
+      {
+        break;
+      }
+
+      result += 1;
+    }
+
+    return result - 1;
+  }
+
+
+  void DicomizerParameters::SetJpegQuality(int quality)
+  {
+    if (quality <= 0 ||
+        quality > 100)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    jpegQuality_ = quality;
+  }
+
+
+  IFileTarget* DicomizerParameters::CreateTarget() const
+  {
+    if (folder_.empty() ||
+        folderPattern_.empty())
+    {
+      return new OrthancTarget(orthanc_);
+    }
+    else
+    {
+      return new FolderTarget(folder_ + "/" + folderPattern_);
+    }
+  }
+}
diff --git a/Framework/DicomizerParameters.h b/Framework/DicomizerParameters.h
new file mode 100644
index 0000000..ede1506
--- /dev/null
+++ b/Framework/DicomizerParameters.h
@@ -0,0 +1,257 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Inputs/ITiledPyramid.h"
+#include "Outputs/IPyramidWriter.h"
+#include "Messaging/IFileTarget.h"
+#include "DicomToolbox.h"
+#include "Orthanc/Core/WebServiceParameters.h"
+
+#include <stdint.h>
+
+namespace OrthancWSI
+{
+  class DicomizerParameters
+  {
+  private:
+    bool              safetyCheck_;
+    bool              repaintBackground_;
+    uint8_t           backgroundColor_[3];
+    ImageCompression  targetCompression_;
+    bool              hasTargetTileSize_;
+    unsigned int      targetTileWidth_;
+    unsigned int      targetTileHeight_;
+    unsigned int      threadsCount_;
+    unsigned int      maxDicomFileSize_;
+    bool              reconstructPyramid_;
+    unsigned int      pyramidLevelsCount_;  // "0" means use default choice
+    unsigned int      pyramidLowerLevelsCount_;  // "0" means use default choice
+    bool              smooth_;
+    std::string       inputFile_;
+    uint8_t           jpegQuality_;
+    bool              forceReencode_;
+    std::string       folder_;
+    std::string       folderPattern_;
+    std::string       dataset_;
+    OpticalPath       opticalPath_;
+    std::string       iccProfile_;
+
+    Orthanc::WebServiceParameters  orthanc_;
+
+  public:
+    DicomizerParameters();
+
+    void SetSafetyCheck(bool safety)
+    {
+      safetyCheck_ = safety;
+    }
+
+    bool IsSafetyCheck() const
+    {
+      return safetyCheck_;
+    }
+
+    bool IsRepaintBackground() const
+    {
+      return repaintBackground_;
+    }
+
+    void SetRepaintBackground(bool repaint)
+    {
+      repaintBackground_ = repaint;
+    }
+
+    void SetBackgroundColor(uint8_t red,
+                            uint8_t green,
+                            uint8_t blue);
+
+    uint8_t GetBackgroundColorRed() const
+    {
+      return backgroundColor_[0];
+    }
+
+    uint8_t GetBackgroundColorGreen() const
+    {
+      return backgroundColor_[1];
+    }
+
+    uint8_t GetBackgroundColorBlue() const
+    {
+      return backgroundColor_[2];
+    }
+
+    void SetTargetCompression(ImageCompression compression)
+    {
+      targetCompression_ = compression;
+    }
+
+    ImageCompression GetTargetCompression() const
+    {
+      return targetCompression_;
+    }
+
+    void SetTargetTileSize(unsigned int width,
+                           unsigned int height);
+
+    unsigned int GetTargetTileWidth(unsigned int defaultWidth) const;
+
+    unsigned int GetTargetTileWidth(const ITiledPyramid& source) const
+    {
+      return GetTargetTileWidth(source.GetTileWidth());
+    }
+
+    unsigned int GetTargetTileHeight(unsigned int defaultHeight) const;
+
+    unsigned int GetTargetTileHeight(const ITiledPyramid& source) const
+    {
+      return GetTargetTileHeight(source.GetTileHeight());
+    }
+
+    void SetThreadsCount(unsigned int threads);
+
+    unsigned int GetThreadsCount() const
+    {
+      return threadsCount_;
+    }
+
+    void SetDicomMaxFileSize(unsigned int size);
+
+    unsigned int GetDicomMaxFileSize() const
+    {
+      return maxDicomFileSize_;
+    }
+
+    bool IsReconstructPyramid() const
+    {
+      return reconstructPyramid_;
+    }
+
+    void SetReconstructPyramid(bool reconstruct)
+    {
+      reconstructPyramid_ = reconstruct;
+    }
+    
+    void SetPyramidLevelsCount(unsigned int count);
+
+    unsigned int GetPyramidLevelsCount(const IPyramidWriter& target,
+                                       const ITiledPyramid& source) const;
+
+    void SetPyramidLowerLevelsCount(unsigned int count);
+
+    unsigned int GetPyramidLowerLevelsCount(const IPyramidWriter& target,
+                                            const ITiledPyramid& source) const;
+
+    void SetSmoothEnabled(bool smooth)
+    {
+      smooth_ = smooth;
+    }
+
+    bool IsSmoothEnabled() const
+    {
+      return smooth_;
+    }
+
+    void SetInputFile(const std::string& path)
+    {
+      inputFile_ = path;
+    }
+
+    const std::string& GetInputFile() const
+    {
+      return inputFile_;
+    }
+
+    void SetJpegQuality(int quality);
+
+    uint8_t GetJpegQuality() const
+    {
+      return jpegQuality_;
+    }
+
+    void SetForceReencode(bool force)
+    {
+      forceReencode_ = force;
+    }
+
+    bool IsForceReencode() const
+    {
+      return forceReencode_;
+    }
+
+    void SetTargetFolder(const std::string& folder)
+    {
+      folder_ = folder;
+    }
+
+    const std::string& GetTargetFolderPattern() const
+    {
+      return folderPattern_;
+    }
+
+    void SetTargetFolderPattern(const std::string& pattern)
+    {
+      folderPattern_ = pattern;
+    }
+
+    Orthanc::WebServiceParameters& GetOrthancParameters()
+    {
+      return orthanc_;
+    }
+
+    const Orthanc::WebServiceParameters& GetOrthancParameters() const
+    {
+      return orthanc_;
+    }
+
+    IFileTarget* CreateTarget() const;
+
+    void SetDatasetPath(const std::string& path)
+    {
+      dataset_ = path;
+    }
+
+    const std::string& GetDatasetPath() const
+    {
+      return dataset_;
+    }
+
+    void SetOpticalPath(OpticalPath opticalPath)
+    {
+      opticalPath_ = opticalPath;
+    }
+
+    OpticalPath GetOpticalPath() const
+    {
+      return opticalPath_;
+    }
+
+    void SetIccProfilePath(const std::string& path)
+    {
+      iccProfile_ = path;
+    }
+
+    const std::string& GetIccProfilePath() const
+    {
+      return iccProfile_;
+    }
+  };
+}
diff --git a/Framework/Enumerations.cpp b/Framework/Enumerations.cpp
new file mode 100644
index 0000000..52cda01
--- /dev/null
+++ b/Framework/Enumerations.cpp
@@ -0,0 +1,185 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
+#include "Enumerations.h"
+
+#include "Jpeg2000Reader.h"
+#include "Orthanc/Core/OrthancException.h"
+#include "Orthanc/Core/Toolbox.h"
+
+#include <string.h>
+#include <boost/algorithm/string/predicate.hpp>
+
+#define HEADER(s) (const void*) (s), sizeof(s)-1
+
+namespace OrthancWSI
+{
+  const char* EnumerationToString(ImageCompression compression)
+  {
+    switch (compression)
+    {
+      case ImageCompression_Unknown:
+        return "Unknown";
+
+      case ImageCompression_None:
+        return "Raw image";
+
+      case ImageCompression_Png:
+        return "PNG";
+
+      case ImageCompression_Jpeg:
+        return "JPEG";
+
+      case ImageCompression_Jpeg2000:
+        return "JPEG2000";
+
+      case ImageCompression_Tiff:
+        return "TIFF";
+
+      case ImageCompression_Dicom:
+        return "DICOM";
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static bool MatchHeader(const void* actual,
+                          size_t actualSize,
+                          const void* expected,
+                          size_t expectedSize)
+  {
+    if (actualSize < expectedSize)
+    {
+      return false;
+    }
+    else
+    {
+      return memcmp(actual, expected, expectedSize) == 0;
+    }
+  }
+
+
+  ImageCompression DetectFormatFromFile(const std::string& path)
+  {
+    std::string header;
+    Orthanc::Toolbox::ReadHeader(header, path, 256);
+
+    ImageCompression tmp = DetectFormatFromMemory(header.c_str(), header.size());
+    if (tmp != ImageCompression_Unknown)
+    {
+      return tmp;
+    }
+
+    // Cannot detect the format using the header, fallback to the use
+    // of the filename extension
+    
+    std::string lower;
+    Orthanc::Toolbox::ToLowerCase(lower, path);
+
+    if (boost::algorithm::ends_with(lower, ".jpeg") ||
+        boost::algorithm::ends_with(lower, ".jpg"))
+    {
+      return ImageCompression_Jpeg;
+    }
+
+    if (boost::algorithm::ends_with(lower, ".png"))
+    {
+      return ImageCompression_Png;
+    }
+
+    if (boost::algorithm::ends_with(lower, ".tiff") ||
+        boost::algorithm::ends_with(lower, ".tif"))
+    {
+      return ImageCompression_Tiff;
+    }
+
+    if (boost::algorithm::ends_with(lower, ".jp2") ||
+        boost::algorithm::ends_with(lower, ".j2k"))
+    {
+      return ImageCompression_Jpeg2000;
+    }
+
+    if (boost::algorithm::ends_with(lower, ".dcm"))
+    {
+      return ImageCompression_Dicom;
+    }
+
+    return ImageCompression_Unknown;
+  }
+
+
+  ImageCompression DetectFormatFromMemory(const void* buffer,
+                                          size_t size) 
+  {
+    if (MatchHeader(buffer, size, HEADER("\377\330\377")))
+    {
+      return ImageCompression_Jpeg;
+    }
+
+    if (MatchHeader(buffer, size, HEADER("\xff\x4f\xff\x51")) ||
+        MatchHeader(buffer, size, HEADER("\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a")))
+    {
+      return ImageCompression_Jpeg2000;
+    }
+
+    if (MatchHeader(buffer, size, HEADER("\211PNG\r\n\032\n")))
+    {
+      return ImageCompression_Png;
+    }
+
+    if (MatchHeader(buffer, size, HEADER("\115\115\000\052")) ||
+        MatchHeader(buffer, size, HEADER("\111\111\052\000")) ||
+        MatchHeader(buffer, size, HEADER("\115\115\000\053\000\010\000\000")) ||
+        MatchHeader(buffer, size, HEADER("\111\111\053\000\010\000\000\000")))
+    {
+      return ImageCompression_Tiff;
+    }
+
+    if (size >= 128 + 4 &&
+        MatchHeader(reinterpret_cast<const uint8_t*>(buffer) + 128, size - 128, HEADER("DICM")))
+    {
+      bool ok = true;
+      for (size_t i = 0; ok && i < 128; i++)
+      {
+        if (reinterpret_cast<const uint8_t*>(buffer)[i] != 0)
+        {
+          ok = false;
+        }
+      }
+
+      if (ok)
+      {
+        return ImageCompression_Dicom;
+      }
+    }        
+
+    Jpeg2000Format jpeg2000 = Jpeg2000Reader::DetectFormatFromMemory(buffer, size);
+    if (jpeg2000 == Jpeg2000Format_JP2 ||
+        jpeg2000 == Jpeg2000Format_J2K)
+    {
+      return ImageCompression_Jpeg2000;
+    }
+    
+    return ImageCompression_Unknown;
+  }
+}
diff --git a/Framework/Enumerations.h b/Framework/Enumerations.h
new file mode 100644
index 0000000..0b63f81
--- /dev/null
+++ b/Framework/Enumerations.h
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Orthanc/Core/Enumerations.h"
+
+#include <stdint.h>
+#include <string>
+
+namespace OrthancWSI
+{
+  enum ImageCompression
+  {
+    ImageCompression_Unknown,
+    ImageCompression_None,
+    ImageCompression_Dicom,
+    ImageCompression_Png,
+    ImageCompression_Jpeg,
+    ImageCompression_Jpeg2000,
+    ImageCompression_Tiff
+  };
+
+  enum OpticalPath
+  {
+    OpticalPath_None,
+    OpticalPath_Brightfield
+  };
+
+  const char* EnumerationToString(ImageCompression compression);
+
+  ImageCompression DetectFormatFromFile(const std::string& path);
+
+  ImageCompression DetectFormatFromMemory(const void* buffer,
+                                          size_t size);
+
+  inline unsigned int CeilingDivision(unsigned int a,
+                                      unsigned int b)
+  {
+    if (a % b == 0)
+    {
+      return a / b;
+    }
+    else
+    {
+      return a / b + 1;
+    }
+  }
+}
diff --git a/Framework/ImageToolbox.cpp b/Framework/ImageToolbox.cpp
new file mode 100644
index 0000000..112a41a
--- /dev/null
+++ b/Framework/ImageToolbox.cpp
@@ -0,0 +1,368 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
+#include "ImageToolbox.h"
+
+#include "Jpeg2000Reader.h"
+#include "Jpeg2000Writer.h"
+
+#include "Orthanc/Core/OrthancException.h"
+#include "Orthanc/Core/Images/ImageProcessing.h"
+#include "Orthanc/Core/Images/PngReader.h"
+#include "Orthanc/Core/Images/PngWriter.h"
+#include "Orthanc/Core/Images/JpegReader.h"
+#include "Orthanc/Core/Images/JpegWriter.h"
+#include "Orthanc/Core/Logging.h"
+
+#include <string.h>
+#include <memory>
+
+
+namespace OrthancWSI
+{
+  namespace ImageToolbox
+  {
+    Orthanc::ImageAccessor* Allocate(Orthanc::PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height)
+    {
+      return new Orthanc::Image(format, width, height, false);
+    }
+
+
+    void Embed(Orthanc::ImageAccessor& target,
+               const Orthanc::ImageAccessor& source,
+               unsigned int x,
+               unsigned int y)
+    {
+      if (target.GetFormat() != source.GetFormat())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+
+      if (x >= target.GetWidth() ||
+          y >= target.GetHeight())
+      {
+        return;
+      }
+
+      unsigned int h = std::min(source.GetHeight(), target.GetHeight() - y);
+      unsigned int w = std::min(source.GetWidth(), target.GetWidth() - x);
+
+      Orthanc::ImageAccessor targetRegion = target.GetRegion(x, y, w, h);
+      Orthanc::ImageAccessor sourceRegion = source.GetRegion(0, 0, w, h);
+      Orthanc::ImageProcessing::Copy(targetRegion, sourceRegion);
+    }
+
+
+
+    void Set(Orthanc::ImageAccessor& image,
+             uint8_t r,
+             uint8_t g,
+             uint8_t b)
+    {
+      if (image.GetWidth() == 0 ||
+          image.GetHeight() == 0)
+      {
+        return;
+      }
+
+      uint8_t grayscale = (2126 * static_cast<uint16_t>(r) + 
+                           7152 * static_cast<uint16_t>(g) +
+                           0722 * static_cast<uint16_t>(b)) / 10000;
+
+      switch (image.GetFormat())
+      {
+        case Orthanc::PixelFormat_Grayscale8:
+        {
+          for (unsigned int y = 0; y < image.GetHeight(); y++)
+          {
+            memset(image.GetRow(y), grayscale, image.GetWidth());
+          }
+
+          break;
+        }
+
+        case Orthanc::PixelFormat_RGB24:
+        {
+          for (unsigned int y = 0; y < image.GetHeight(); y++)
+          {
+            uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y));
+            for (unsigned int x = 0; x < image.GetWidth(); x++, p += 3)
+            {
+              p[0] = r;
+              p[1] = g;
+              p[2] = b;
+            }
+          }
+
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+
+
+    Orthanc::ImageAccessor* DecodeTile(const std::string& source,
+                                       ImageCompression compression)
+    {
+      switch (compression)
+      {
+        case ImageCompression_Png:
+        {
+          std::auto_ptr<Orthanc::PngReader> reader(new Orthanc::PngReader);
+          reader->ReadFromMemory(source);
+          return reader.release();
+        }
+
+        case ImageCompression_Jpeg:
+        {
+          std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
+          reader->ReadFromMemory(source);
+          return reader.release();
+        }
+
+        case ImageCompression_Jpeg2000:
+        {
+          std::auto_ptr<Jpeg2000Reader> reader(new Jpeg2000Reader);
+          reader->ReadFromMemory(source);
+          return reader.release();
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
+    void EncodeTile(std::string& target,
+                    const Orthanc::ImageAccessor& source,
+                    ImageCompression compression,
+                    uint8_t quality)
+    {
+      if (compression == ImageCompression_None)
+      {
+        unsigned int pitch = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
+        target.resize(pitch * source.GetHeight());
+
+        for (unsigned int i = 0; i < source.GetHeight(); i++)
+        {
+          memcpy(&target[i * pitch], source.GetConstRow(i), pitch);
+        }
+      }
+      else
+      {
+        std::auto_ptr<Orthanc::IImageWriter> writer;
+
+        switch (compression)
+        {
+          case ImageCompression_Png:
+            writer.reset(new Orthanc::PngWriter);
+            break;
+
+          case ImageCompression_Jpeg:
+            writer.reset(new Orthanc::JpegWriter);
+            dynamic_cast<Orthanc::JpegWriter&>(*writer).SetQuality(quality);
+            break;
+
+          case ImageCompression_Jpeg2000:
+            writer.reset(new Jpeg2000Writer);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        writer->WriteToMemory(target, source);
+      }
+    }
+
+
+    void ChangeTileCompression(std::string& target,
+                               const std::string& source,
+                               ImageCompression sourceCompression,
+                               ImageCompression targetCompression,
+                               uint8_t quality)
+    {
+      if (sourceCompression == targetCompression)
+      {
+        target = source;
+      }
+      else
+      {
+        std::auto_ptr<Orthanc::ImageAccessor> decoded(DecodeTile(source, sourceCompression));
+        EncodeTile(target, *decoded, targetCompression, quality);
+      }
+    }
+
+
+    static uint8_t GetPixelValue(const Orthanc::ImageAccessor& source,
+                                 unsigned int x,
+                                 unsigned int y,
+                                 unsigned int channel,
+                                 int offsetX,
+                                 int offsetY)
+    {
+      assert(channel < source.GetBytesPerPixel());
+      assert(source.GetFormat() == Orthanc::PixelFormat_Grayscale8 ||
+             source.GetFormat() == Orthanc::PixelFormat_RGB24 ||
+             source.GetFormat() == Orthanc::PixelFormat_RGBA32);  // 16bpp is unsupported
+
+      if (static_cast<int>(x) + offsetX < 0)
+      {
+        x = 0;
+      }
+      else
+      {
+        x += offsetX;
+        if (x >= source.GetWidth())
+        {
+          x = source.GetWidth() - 1;
+        }
+      }
+
+      if (static_cast<int>(y) + offsetY < 0)
+      {
+        y = 0;
+      }
+      else
+      {
+        y += offsetY;
+        if (y >= source.GetHeight())
+        {
+          y = source.GetHeight() - 1;
+        }
+      }
+
+      return *(reinterpret_cast<const uint8_t*>(source.GetConstBuffer()) +
+               y * source.GetPitch() + x * source.GetBytesPerPixel() + channel);
+    }
+
+
+    static uint8_t SmoothPixelValue(const Orthanc::ImageAccessor& source,
+                                    unsigned int x,
+                                    unsigned int y,
+                                    unsigned int channel)
+    {
+      static const uint32_t kernel[5] = { 1, 4, 6, 4, 1 };
+      static const uint32_t normalization = 2 * (1 + 4 + 6 + 4 + 1);
+
+      uint32_t accumulator = 0;
+
+      // Horizontal smoothing
+      for (int offset = -2; offset <= 2; offset++)
+      {
+        accumulator += kernel[offset + 2] * GetPixelValue(source, x, y, channel, offset, 0);
+      }
+
+      // Vertical smoothing
+      for (int offset = -2; offset <= 2; offset++)
+      {
+        accumulator += kernel[offset + 2] * GetPixelValue(source, x, y, channel, 0, offset);
+      }
+
+      return static_cast<uint8_t>(accumulator / normalization);
+    }
+
+
+    Orthanc::ImageAccessor* Halve(const Orthanc::ImageAccessor& source,
+                                  bool smooth)
+    {
+      if (source.GetWidth() % 2 == 1 ||
+          source.GetHeight() % 2 == 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+      }
+
+      if (source.GetFormat() != Orthanc::PixelFormat_Grayscale8 &&
+          source.GetFormat() != Orthanc::PixelFormat_RGB24 &&
+          source.GetFormat() != Orthanc::PixelFormat_RGBA32)  // 16bpp is not supported (*)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      unsigned int channelsCount = source.GetBytesPerPixel();  // OK tx (*)
+
+      std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(source.GetFormat(), 
+                                                            source.GetWidth() / 2, 
+                                                            source.GetHeight() / 2));
+
+      for (unsigned int y = 0; y < source.GetHeight() / 2; y++)
+      {
+        uint8_t* q = reinterpret_cast<uint8_t*>(result->GetRow(y));
+
+        for (unsigned int x = 0; x < source.GetWidth() / 2; x++, q += 3)
+        {
+          for (unsigned int c = 0; c < channelsCount; c++)
+          {
+            if (smooth)
+            {
+              q[c] = SmoothPixelValue(source, 2 * x, 2 * y, c);
+            }
+            else
+            {
+              q[c] = GetPixelValue(source, 2 * x, 2 * y, c, 0, 0);
+            }
+          }
+        }
+      }
+
+      return result.release();
+    }
+
+
+    Orthanc::ImageAccessor* Clone(const Orthanc::ImageAccessor& accessor)
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(accessor.GetFormat(),
+                                                            accessor.GetWidth(),
+                                                            accessor.GetHeight()));
+      Embed(*result, accessor, 0, 0);
+
+      return result.release();
+    }
+
+
+    Orthanc::ImageAccessor* Render(ITiledPyramid& pyramid,
+                                   unsigned int level)
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(pyramid.GetPixelFormat(), 
+                                                            pyramid.GetLevelWidth(level),
+                                                            pyramid.GetLevelHeight(level)));
+
+      LOG(INFO) << "Rendering a tiled image of size " << result->GetWidth() << "x" << result->GetHeight();
+
+      for (unsigned int y = 0; y < result->GetHeight(); y += pyramid.GetTileHeight())
+      {
+        for (unsigned int x = 0; x < result->GetWidth(); x += pyramid.GetTileWidth())
+        {
+          std::auto_ptr<Orthanc::ImageAccessor> tile(pyramid.DecodeTile(level,
+                                                                        x / pyramid.GetTileWidth(),
+                                                                        y / pyramid.GetTileHeight()));
+          Embed(*result, *tile, x, y);
+        }
+      }
+
+      return result.release();
+    }
+  }
+}
diff --git a/Framework/ImageToolbox.h b/Framework/ImageToolbox.h
new file mode 100644
index 0000000..f18328f
--- /dev/null
+++ b/Framework/ImageToolbox.h
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Orthanc/Core/Images/ImageAccessor.h"
+#include "Enumerations.h"
+#include "Inputs/ITiledPyramid.h"
+
+#include <stdint.h>
+
+namespace OrthancWSI
+{
+  namespace ImageToolbox
+  {
+    Orthanc::ImageAccessor* Allocate(Orthanc::PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height);
+
+    void Embed(Orthanc::ImageAccessor& target,
+               const Orthanc::ImageAccessor& source,
+               unsigned int x,
+               unsigned int y);
+
+    inline void Copy(Orthanc::ImageAccessor& target,
+                     const Orthanc::ImageAccessor& source)
+    {
+      Embed(target, source, 0, 0);
+    }
+
+    void Set(Orthanc::ImageAccessor& image,
+             uint8_t r,
+             uint8_t g,
+             uint8_t b);
+
+    Orthanc::ImageAccessor* DecodeTile(const std::string& source,
+                                       ImageCompression compression);
+    
+    void EncodeTile(std::string& target,
+                    const Orthanc::ImageAccessor& source,
+                    ImageCompression compression,
+                    uint8_t quality);  // Only for JPEG compression
+
+    void ChangeTileCompression(std::string& target,
+                               const std::string& source,
+                               ImageCompression sourceCompression,
+                               ImageCompression targetCompression,
+                               uint8_t quality);  // Only for JPEG compression
+
+    Orthanc::ImageAccessor* Halve(const Orthanc::ImageAccessor& source,
+                                  bool smooth);
+
+    Orthanc::ImageAccessor* Clone(const Orthanc::ImageAccessor& accessor);
+
+    Orthanc::ImageAccessor* Render(ITiledPyramid& pyramid,
+                                   unsigned int level);
+  }
+}
diff --git a/Framework/ImagedVolumeParameters.cpp b/Framework/ImagedVolumeParameters.cpp
new file mode 100644
index 0000000..93b458d
--- /dev/null
+++ b/Framework/ImagedVolumeParameters.cpp
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
+#include "ImagedVolumeParameters.h"
+
+#include "Orthanc/Core/OrthancException.h"
+
+namespace OrthancWSI
+{
+  ImagedVolumeParameters::ImagedVolumeParameters()
+  {
+    // Typical parameters of a specimen millimeters
+    width_ = 15;
+    height_ = 15;
+    depth_ = 1;
+    offsetX_ = 20;
+    offsetY_ = 40;
+  }
+
+
+  void ImagedVolumeParameters::SetWidth(float width)
+  {
+    if (width <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    width_ = width;
+  }
+    
+
+  void ImagedVolumeParameters::SetHeight(float height)
+  {
+    if (height <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    height_ = height;
+  }
+
+    
+  void ImagedVolumeParameters::SetDepth(float depth)
+  {
+    if (depth <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    depth_ = depth;
+  }
+    
+
+  void ImagedVolumeParameters::GetLocation(float& physicalX,
+                                           float& physicalY,
+                                           unsigned int imageX,
+                                           unsigned int imageY,
+                                           unsigned int totalWidth,
+                                           unsigned int totalHeight) const
+  {
+    if (imageX >= totalWidth ||
+        imageY >= totalHeight)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    // WARNING: The physical X/Y axes are switched wrt. the image X/Y
+    physicalX = offsetX_ - GetHeight() * static_cast<float>(imageX) / static_cast<float>(totalWidth);
+    physicalY = offsetY_ - GetWidth() * static_cast<float>(imageY) / static_cast<float>(totalHeight);
+  }
+}
diff --git a/Framework/ImagedVolumeParameters.h b/Framework/ImagedVolumeParameters.h
new file mode 100644
index 0000000..a27d49e
--- /dev/null
+++ b/Framework/ImagedVolumeParameters.h
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+namespace OrthancWSI
+{
+  class ImagedVolumeParameters
+  {
+  private:
+    float  width_;
+    float  height_;
+    float  depth_;
+    float  offsetX_;
+    float  offsetY_;
+
+  public:
+    ImagedVolumeParameters();
+
+    float GetWidth() const
+    {
+      return width_;
+    }
+    
+    float GetHeight() const
+    {
+      return height_;
+    }
+    
+    float GetDepth() const
+    {
+      return depth_;
+    }
+
+    float GetOffsetX() const
+    {
+      return offsetX_;
+    }
+
+    float GetOffsetY() const
+    {
+      return offsetY_;
+    }
+
+    void SetWidth(float width);
+    
+    void SetHeight(float height);
+    
+    void SetDepth(float depth);
+    
+    void SetOffsetX(float offset)
+    {
+      offsetX_ = offset;
+    }
+    
+    void SetOffsetY(float offset)
+    {
+      offsetY_ = offset;
+    }
+
+    void GetLocation(float& physicalX,
+                     float& physicalY,
+                     unsigned int imageX,
+                     unsigned int imageY,
+                     unsigned int totalWidth,
+                     unsigned int totalHeight) const;
+  };
+}
diff --git a/Framework/Inputs/DecodedTiledPyramid.cpp b/Framework/Inputs/DecodedTiledPyramid.cpp
new file mode 100644
index 0000000..a71af8e
--- /dev/null
+++ b/Framework/Inputs/DecodedTiledPyramid.cpp
@@ -0,0 +1,118 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "DecodedTiledPyramid.h"
+
+#include "../ImageToolbox.h"
+
+#include <memory>
+#include <cassert>
+
+namespace OrthancWSI
+{
+  DecodedTiledPyramid::DecodedTiledPyramid()
+  {
+    SetBackgroundColor(255, 255, 255);
+  }
+
+
+  void DecodedTiledPyramid::SetBackgroundColor(uint8_t red,
+                                               uint8_t green,
+                                               uint8_t blue)
+  {
+    backgroundColor_[0] = red;
+    backgroundColor_[1] = green;
+    backgroundColor_[2] = blue;
+  }
+
+
+  void DecodedTiledPyramid::GetBackgroundColor(uint8_t& red,
+                                               uint8_t& green,
+                                               uint8_t& blue) const
+  {
+    red = backgroundColor_[0];
+    green = backgroundColor_[1];
+    blue = backgroundColor_[2];
+  }
+
+
+  Orthanc::ImageAccessor* DecodedTiledPyramid::DecodeTile(unsigned int level,
+                                                          unsigned int tileX,
+                                                          unsigned int tileY)
+  {
+    unsigned int x = tileX * GetTileWidth();
+    unsigned int y = tileY * GetTileHeight();
+
+    std::auto_ptr<Orthanc::ImageAccessor> tile
+      (ImageToolbox::Allocate(GetPixelFormat(), GetTileWidth(), GetTileHeight()));
+
+    if (x >= GetLevelWidth(level) ||
+        y >= GetLevelHeight(level))   // (*)
+    {
+      ImageToolbox::Set(*tile, backgroundColor_[0], backgroundColor_[1], backgroundColor_[2]);
+      return tile.release();
+    }
+
+    bool fit = true;
+    unsigned int regionWidth;
+    if (x + GetTileWidth() <= GetLevelWidth(level))
+    {
+      regionWidth = GetTileWidth();
+    }
+    else
+    {
+      assert(GetLevelWidth(level) >= x);   // This results from (*)
+      regionWidth = GetLevelWidth(level) - x;
+      fit = false;
+    }
+    
+    unsigned int regionHeight;
+    if (y + GetTileHeight() <= GetLevelHeight(level))
+    {
+      regionHeight = GetTileHeight();
+    }
+    else
+    {
+      assert(GetLevelHeight(level) >= y);   // This results from (*)
+      regionHeight = GetLevelHeight(level) - y;
+      fit = false;
+    }
+
+    if (fit)
+    {
+      // The tile entirely lies inside the image
+      ReadRegion(*tile, level, x, y);
+    }
+    else
+    {
+      // The tile exceeds the size of image, decode it to a temporary buffer
+      std::auto_ptr<Orthanc::ImageAccessor> cropped
+        (ImageToolbox::Allocate(GetPixelFormat(), regionWidth, regionHeight));
+      ReadRegion(*cropped, level, x, y);
+
+      // Create a white tile, and fill it with the cropped content
+      ImageToolbox::Set(*tile, backgroundColor_[0], backgroundColor_[1], backgroundColor_[2]);
+      ImageToolbox::Embed(*tile, *cropped, 0, 0);
+    }
+
+    return tile.release();
+  }
+}
diff --git a/Framework/Inputs/DecodedTiledPyramid.h b/Framework/Inputs/DecodedTiledPyramid.h
new file mode 100644
index 0000000..b4a5676
--- /dev/null
+++ b/Framework/Inputs/DecodedTiledPyramid.h
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ITiledPyramid.h"
+
+
+namespace OrthancWSI
+{
+  /**
+   * This class acts as a wrapper for "cropped" tiled images, where
+   * the tiles at the right or at the bottom might not have the same
+   * dimensions of the other slides.
+   **/
+  class DecodedTiledPyramid : public ITiledPyramid
+  {
+  private:
+    uint8_t  backgroundColor_[3];
+
+  protected:
+    // Subclasses can assume that the requested region is fully inside
+    // the image, and that target has the proper size to store the
+    // region. Pay attention to implement mutual exclusion in subclasses.
+    virtual void ReadRegion(Orthanc::ImageAccessor& target,
+                            unsigned int level,
+                            unsigned int x,
+                            unsigned int y) = 0;
+
+  public:
+    DecodedTiledPyramid();
+
+    void SetBackgroundColor(uint8_t red,
+                            uint8_t green,
+                            uint8_t blue);
+
+    void GetBackgroundColor(uint8_t& red,
+                            uint8_t& green,
+                            uint8_t& blue) const;
+
+    virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level,
+                                               unsigned int tileX,
+                                               unsigned int tileY);
+
+    virtual ImageCompression GetImageCompression() const
+    {
+      return ImageCompression_None;
+    }
+
+    virtual bool ReadRawTile(std::string& tile,
+                             unsigned int level,
+                             unsigned int tileX,
+                             unsigned int tileY)
+    {
+      return false;   // No access to the raw tiles
+    }
+  };
+}
diff --git a/Framework/Inputs/DicomPyramid.cpp b/Framework/Inputs/DicomPyramid.cpp
new file mode 100644
index 0000000..b3f04e0
--- /dev/null
+++ b/Framework/Inputs/DicomPyramid.cpp
@@ -0,0 +1,238 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "DicomPyramid.h"
+
+#include "../DicomToolbox.h"
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <algorithm>
+#include <cassert>
+
+namespace OrthancWSI
+{
+  struct DicomPyramid::Comparator
+  {
+    bool operator() (DicomPyramidInstance* const& a,
+                     DicomPyramidInstance* const& b) const
+    {
+      return a->GetTotalWidth() > b->GetTotalWidth();
+    }
+  };
+
+
+  void DicomPyramid::Clear()
+  {
+    for (size_t i = 0; i < levels_.size(); i++)
+    {
+      if (levels_[i] != NULL)
+      {
+        delete levels_[i];
+      }
+    }
+
+    for (size_t i = 0; i < instances_.size(); i++)
+    {
+      if (instances_[i] != NULL)
+      {
+        delete instances_[i];
+      }
+    }
+  }
+
+
+  void DicomPyramid::RegisterInstances(const std::string& seriesId)
+  {
+    Json::Value series;
+    IOrthancConnection::RestApiGet(series, orthanc_, "/series/" + seriesId);
+
+    if (series.type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    const Json::Value& instances = DicomToolbox::GetSequenceTag(series, "Instances");
+    instances_.reserve(instances.size());
+
+    for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
+    {
+      if (instances[i].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      std::string instance = instances[i].asString();
+
+      try
+      {
+        instances_.push_back(new DicomPyramidInstance(orthanc_, instance));
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        LOG(ERROR) << "Skipping a DICOM instance that is not part of a whole-slide image: " << instance;
+      }
+    }
+  }
+
+
+  void DicomPyramid::Check(const std::string& seriesId) const
+  {
+    if (instances_.empty())
+    {
+      LOG(ERROR) << "This series does not contain a whole-slide image: " << seriesId;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    const DicomPyramidInstance& a = *instances_[0];
+
+    for (size_t i = 1; i < instances_.size(); i++)
+    {
+      const DicomPyramidInstance& b = *instances_[i];
+
+      if (a.GetImageCompression() != b.GetImageCompression() ||
+          a.GetPixelFormat() != b.GetPixelFormat() ||
+          a.GetTileWidth() != b.GetTileWidth() ||
+          a.GetTileHeight() != b.GetTileHeight() ||
+          a.GetTotalWidth() < b.GetTotalWidth() ||
+          a.GetTotalHeight() < b.GetTotalHeight())            
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+
+      if (a.GetTotalWidth() == b.GetTotalWidth() &&
+          a.GetTotalHeight() != b.GetTotalHeight())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+    }
+  }
+
+
+  void DicomPyramid::CheckLevel(size_t level) const
+  {
+    if (level >= levels_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  DicomPyramid::DicomPyramid(IOrthancConnection& orthanc,
+                             const std::string& seriesId) :
+    orthanc_(orthanc),
+    seriesId_(seriesId)
+  {
+    RegisterInstances(seriesId);
+
+    // Sort the instances of the pyramid by decreasing total widths
+    std::sort(instances_.begin(), instances_.end(), Comparator());
+
+    try
+    {
+      Check(seriesId);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      Clear();
+      throw;
+    }
+
+    for (size_t i = 0; i < instances_.size(); i++)
+    {
+      if (i == 0 ||
+          instances_[i - 1]->GetTotalWidth() != instances_[i]->GetTotalWidth())
+      {
+        levels_.push_back(new DicomPyramidLevel(*instances_[i]));
+      }
+      else
+      {
+        assert(levels_.back() != NULL);
+        levels_.back()->AddInstance(*instances_[i]);
+      }
+    }
+  }
+
+
+  unsigned int DicomPyramid::GetLevelWidth(unsigned int level) const
+  {
+    CheckLevel(level);
+    return levels_[level]->GetTotalWidth();
+  }
+
+
+  unsigned int DicomPyramid::GetLevelHeight(unsigned int level) const
+  {
+    CheckLevel(level);
+    return levels_[level]->GetTotalHeight();
+  }
+
+
+  unsigned int DicomPyramid::GetTileWidth() const
+  {
+    assert(!levels_.empty() && levels_[0] != NULL);
+    return levels_[0]->GetTileWidth();
+  }
+
+
+  unsigned int DicomPyramid::GetTileHeight() const
+  {
+    assert(!levels_.empty() && levels_[0] != NULL);
+    return levels_[0]->GetTileHeight();
+  }
+
+
+  bool DicomPyramid::ReadRawTile(std::string& tile,
+                                 unsigned int level,
+                                 unsigned int tileX,
+                                 unsigned int tileY)
+  {
+    CheckLevel(level);
+      
+    ImageCompression compression;
+    Orthanc::PixelFormat format;
+      
+    if (levels_[level]->DownloadRawTile(compression, format, tile, orthanc_, tileX, tileY))
+    {
+      assert(compression == GetImageCompression() &&
+             format == GetPixelFormat());
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  ImageCompression DicomPyramid::GetImageCompression() const
+  {
+    assert(!instances_.empty() && instances_[0] != NULL);
+    return instances_[0]->GetImageCompression();
+  }
+
+
+  Orthanc::PixelFormat DicomPyramid::GetPixelFormat() const
+  {
+    assert(!instances_.empty() && instances_[0] != NULL);
+    return instances_[0]->GetPixelFormat();
+  }
+}
diff --git a/Framework/Inputs/DicomPyramid.h b/Framework/Inputs/DicomPyramid.h
new file mode 100644
index 0000000..0232a0c
--- /dev/null
+++ b/Framework/Inputs/DicomPyramid.h
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PyramidWithRawTiles.h"
+#include "DicomPyramidInstance.h"
+#include "DicomPyramidLevel.h"
+
+namespace OrthancWSI
+{
+  class DicomPyramid : public PyramidWithRawTiles
+  {
+  private:
+    struct Comparator;
+
+    IOrthancConnection&                 orthanc_;
+    std::string                         seriesId_;
+    std::vector<DicomPyramidInstance*>  instances_;
+    std::vector<DicomPyramidLevel*>     levels_;
+
+    void Clear();
+
+    void RegisterInstances(const std::string& seriesId);
+
+    void Check(const std::string& seriesId) const;
+
+    void CheckLevel(size_t level) const;
+
+  public:
+    DicomPyramid(IOrthancConnection& orthanc,
+                 const std::string& seriesId);
+
+    virtual ~DicomPyramid()
+    {
+      Clear();
+    }
+
+    const std::string& GetSeriesId() const
+    {
+      return seriesId_;
+    }
+
+    virtual unsigned int GetLevelCount() const
+    {
+      return levels_.size();
+    }
+
+    virtual unsigned int GetLevelWidth(unsigned int level) const;
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const;
+
+    virtual unsigned int GetTileWidth() const;
+
+    virtual unsigned int GetTileHeight() const;
+
+    virtual bool ReadRawTile(std::string& tile,
+                             unsigned int level,
+                             unsigned int tileX,
+                             unsigned int tileY);
+
+    virtual ImageCompression GetImageCompression() const;
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const;
+  };
+}
diff --git a/Framework/Inputs/DicomPyramidInstance.cpp b/Framework/Inputs/DicomPyramidInstance.cpp
new file mode 100644
index 0000000..2ca7665
--- /dev/null
+++ b/Framework/Inputs/DicomPyramidInstance.cpp
@@ -0,0 +1,171 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "DicomPyramidInstance.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Toolbox.h"
+#include "../DicomToolbox.h"
+
+#include <cassert>
+
+namespace OrthancWSI
+{
+  static ImageCompression DetectImageCompression(const Json::Value& header)
+  {
+    std::string s = Orthanc::Toolbox::StripSpaces
+      (DicomToolbox::GetMandatoryStringTag(header, "TransferSyntaxUID"));
+
+    if (s == "1.2.840.10008.1.2" ||
+        s == "1.2.840.10008.1.2.1")
+    {
+      return ImageCompression_None;
+    }
+    else if (s == "1.2.840.10008.1.2.4.50")
+    {
+      return ImageCompression_Jpeg;
+    }
+    else if (s == "1.2.840.10008.1.2.4.90" ||
+             s == "1.2.840.10008.1.2.4.91")
+    {
+      return ImageCompression_Jpeg2000;
+    }
+    else
+    {
+      LOG(ERROR) << "Unsupported transfer syntax: " << s;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+
+  static Orthanc::PixelFormat DetectPixelFormat(const Json::Value& dicom)
+  {
+    std::string photometric = Orthanc::Toolbox::StripSpaces
+      (DicomToolbox::GetMandatoryStringTag(dicom, "PhotometricInterpretation"));
+
+    if (photometric == "PALETTE")
+    {
+      LOG(ERROR) << "Unsupported photometric interpretation: " << photometric;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    unsigned int bitsStored = DicomToolbox::GetUnsignedIntegerTag(dicom, "BitsStored");
+    unsigned int samplesPerPixel = DicomToolbox::GetUnsignedIntegerTag(dicom, "SamplesPerPixel");
+    bool isSigned = (DicomToolbox::GetUnsignedIntegerTag(dicom, "PixelRepresentation") != 0);
+
+    if (bitsStored == 8 &&
+        samplesPerPixel == 1 &&
+        !isSigned)
+    {
+      return Orthanc::PixelFormat_Grayscale8;
+    }
+    else if (bitsStored == 8 &&
+             samplesPerPixel == 3 &&
+             !isSigned)
+    {
+      return Orthanc::PixelFormat_RGB24;
+    }
+    else
+    {
+      LOG(ERROR) << "Unsupported pixel format";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  
+  DicomPyramidInstance::DicomPyramidInstance(IOrthancConnection&  orthanc,
+                                             const std::string& instanceId) :
+    instanceId_(instanceId)
+  {
+    Json::Value dicom, header;
+    IOrthancConnection::RestApiGet(dicom, orthanc, "/instances/" + instanceId + "/tags?simplify");
+    IOrthancConnection::RestApiGet(header, orthanc, "/instances/" + instanceId + "/header?simplify");
+
+    if (DicomToolbox::GetMandatoryStringTag(dicom, "SOPClassUID") != "1.2.840.10008.5.1.4.1.1.77.1.6" ||
+        DicomToolbox::GetMandatoryStringTag(dicom, "Modality") != "SM")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    compression_ = DetectImageCompression(header);
+    format_ = DetectPixelFormat(dicom);
+    tileWidth_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "Columns");
+    tileHeight_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "Rows");
+    totalWidth_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "TotalPixelMatrixColumns");
+    totalHeight_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "TotalPixelMatrixRows");
+
+    const Json::Value& frames = DicomToolbox::GetSequenceTag(dicom, "PerFrameFunctionalGroupsSequence");
+
+    if (frames.size() != DicomToolbox::GetUnsignedIntegerTag(dicom, "NumberOfFrames"))
+    {
+      LOG(ERROR) << "Mismatch between the number of frames in instance: " << instanceId;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    frames_.resize(frames.size());
+
+    for (Json::Value::ArrayIndex i = 0; i < frames.size(); i++)
+    {
+      const Json::Value& frame = DicomToolbox::GetSequenceTag(frames[i], "PlanePositionSlideSequence");
+      if (frame.size() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      // "-1", because coordinates are shifted by 1 in DICOM
+      int xx = DicomToolbox::GetIntegerTag(frame[0], "ColumnPositionInTotalImagePixelMatrix") - 1;
+      int yy = DicomToolbox::GetIntegerTag(frame[0], "RowPositionInTotalImagePixelMatrix") - 1;
+
+      unsigned int x = static_cast<unsigned int>(xx);
+      unsigned int y = static_cast<unsigned int>(yy);
+
+      if (xx < 0 || 
+          yy < 0 ||
+          x % tileWidth_ != 0 ||
+          y % tileHeight_ != 0 ||
+          x >= totalWidth_ ||
+          y >= totalHeight_)
+      {
+        LOG(ERROR) << "Frame " << i << " with unexpected tile location (" 
+                   << x << "," << y << ") in instance: " << instanceId;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      frames_[i].first = x / tileWidth_;
+      frames_[i].second = y / tileHeight_;
+    }
+  }
+
+
+  unsigned int DicomPyramidInstance::GetFrameLocationX(size_t frame) const
+  {
+    assert(frame < frames_.size());
+    return frames_[frame].first;
+  }
+
+
+  unsigned int DicomPyramidInstance::GetFrameLocationY(size_t frame) const
+  {
+    assert(frame < frames_.size());
+    return frames_[frame].second;
+  }
+}
diff --git a/Framework/Inputs/DicomPyramidInstance.h b/Framework/Inputs/DicomPyramidInstance.h
new file mode 100644
index 0000000..8a5657a
--- /dev/null
+++ b/Framework/Inputs/DicomPyramidInstance.h
@@ -0,0 +1,93 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+#include "../Messaging/IOrthancConnection.h"
+
+#include <boost/noncopyable.hpp>
+#include <vector>
+
+namespace OrthancWSI
+{
+  class DicomPyramidInstance : public boost::noncopyable
+  {
+  private:
+    typedef std::pair<unsigned int, unsigned int>  FrameLocation;
+
+    std::string                 instanceId_;
+    ImageCompression            compression_;
+    Orthanc::PixelFormat        format_;
+    unsigned int                tileWidth_;
+    unsigned int                tileHeight_;
+    unsigned int                totalWidth_;
+    unsigned int                totalHeight_;
+    std::vector<FrameLocation>  frames_;
+
+  public:
+    DicomPyramidInstance(IOrthancConnection&  orthanc,
+                         const std::string& instanceId);
+
+    const std::string& GetInstanceId() const
+    {
+      return instanceId_;
+    }
+
+    ImageCompression GetImageCompression() const
+    {
+      return compression_;
+    }
+
+    Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return format_;
+    }
+
+    unsigned int GetTotalWidth() const
+    {
+      return totalWidth_;
+    }
+
+    unsigned int GetTotalHeight() const
+    {
+      return totalHeight_;
+    }
+
+    unsigned int GetTileWidth() const
+    {
+      return tileWidth_;
+    }
+
+    unsigned int GetTileHeight() const
+    {
+      return tileHeight_;
+    }
+
+    size_t GetFrameCount() const
+    {
+      return frames_.size();
+    }
+
+    unsigned int GetFrameLocationX(size_t frame) const;
+
+    unsigned int GetFrameLocationY(size_t frame) const;
+  };
+}
diff --git a/Framework/Inputs/DicomPyramidLevel.cpp b/Framework/Inputs/DicomPyramidLevel.cpp
new file mode 100644
index 0000000..1626755
--- /dev/null
+++ b/Framework/Inputs/DicomPyramidLevel.cpp
@@ -0,0 +1,129 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "DicomPyramidLevel.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancWSI
+{
+  void DicomPyramidLevel::RegisterFrame(const DicomPyramidInstance& instance,
+                                        unsigned int frame)
+  {
+    TileLocation location(instance.GetFrameLocationX(frame), 
+                          instance.GetFrameLocationY(frame));
+
+    if (tiles_.find(location) != tiles_.end())
+    {
+      LOG(ERROR) << "Tile with location (" << location.first << "," 
+                 << location.second << ") is indexed twice in level of size "
+                 << totalWidth_ << "x" << totalHeight_;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    tiles_[location] = std::make_pair(&instance, frame);
+  }
+
+
+  bool DicomPyramidLevel::LookupTile(TileContent& tile,
+                                     unsigned int tileX,
+                                     unsigned int tileY) const
+  {
+    Tiles::const_iterator found = tiles_.find(std::make_pair(tileX, tileY));
+    if (found == tiles_.end())
+    {
+      return false;
+    }
+    else
+    {
+      tile = found->second;
+      return true;
+    }
+  }
+
+
+  DicomPyramidLevel::DicomPyramidLevel(const DicomPyramidInstance& instance) :
+    totalWidth_(instance.GetTotalWidth()),
+    totalHeight_(instance.GetTotalHeight()),
+    tileWidth_(instance.GetTileWidth()),
+    tileHeight_(instance.GetTileHeight())
+  {
+    if (totalWidth_ == 0 ||
+        totalHeight_ == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+      
+    AddInstance(instance);
+  }
+
+
+  void DicomPyramidLevel::AddInstance(const DicomPyramidInstance& instance)
+  {
+    if (instance.GetTotalWidth() != totalWidth_ ||
+        instance.GetTotalHeight() != totalHeight_ ||
+        instance.GetTileWidth() != tileWidth_ ||
+        instance.GetTileHeight() != tileHeight_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+
+    instances_.push_back(&instance);
+
+    for (size_t frame = 0; frame < instance.GetFrameCount(); frame++)
+    {
+      RegisterFrame(instance, frame);
+    }
+  }
+
+
+  bool DicomPyramidLevel::DownloadRawTile(ImageCompression& compression /* out */,
+                                          Orthanc::PixelFormat& format /* out */,
+                                          std::string& raw /* out */,
+                                          IOrthancConnection& orthanc,
+                                          unsigned int tileX,
+                                          unsigned int tileY) const
+  {
+    TileContent tile;
+    if (LookupTile(tile, tileX, tileY))
+    {
+      assert(tile.first != NULL);
+      const DicomPyramidInstance& instance = *tile.first;
+
+      std::string uri = ("/instances/" + instance.GetInstanceId() + 
+                         "/frames/" + boost::lexical_cast<std::string>(tile.second) + "/raw");
+
+      orthanc.RestApiGet(raw, uri);
+
+      compression = instance.GetImageCompression();
+      format = instance.GetPixelFormat();
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
diff --git a/Framework/Inputs/DicomPyramidLevel.h b/Framework/Inputs/DicomPyramidLevel.h
new file mode 100644
index 0000000..62d79d3
--- /dev/null
+++ b/Framework/Inputs/DicomPyramidLevel.h
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomPyramidInstance.h"
+
+#include <list>
+
+namespace OrthancWSI
+{
+  class DicomPyramidLevel : public boost::noncopyable
+  {
+  private:
+    typedef std::pair<unsigned int, unsigned int>                 TileLocation;
+    typedef std::pair<const DicomPyramidInstance*, unsigned int>  TileContent;
+    typedef std::map<TileLocation, TileContent>                   Tiles;
+    typedef std::list<const DicomPyramidInstance*>                Instances;
+
+    unsigned int   totalWidth_;
+    unsigned int   totalHeight_;
+    unsigned int   tileWidth_;
+    unsigned int   tileHeight_;
+    Instances      instances_;
+    Tiles          tiles_;
+
+    void RegisterFrame(const DicomPyramidInstance& instance,
+                       unsigned int frame);
+
+    bool LookupTile(TileContent& tile,
+                    unsigned int tileX,
+                    unsigned int tileY) const;
+
+  public:
+    DicomPyramidLevel(const DicomPyramidInstance& instance);
+
+    void AddInstance(const DicomPyramidInstance& instance);
+
+    unsigned int GetTotalWidth() const
+    {
+      return totalWidth_;
+    }
+
+    unsigned int GetTotalHeight() const
+    {
+      return totalHeight_;
+    }
+
+    unsigned int GetTileWidth() const
+    {
+      return tileWidth_;
+    }
+
+    unsigned int GetTileHeight() const
+    {
+      return tileHeight_;
+    }
+
+    bool DownloadRawTile(ImageCompression& compression /* out */,
+                         Orthanc::PixelFormat& format /* out */,
+                         std::string& raw /* out */,
+                         IOrthancConnection& orthanc,
+                         unsigned int tileX,
+                         unsigned int tileY) const;
+  };
+}
diff --git a/Framework/Inputs/HierarchicalTiff.cpp b/Framework/Inputs/HierarchicalTiff.cpp
new file mode 100644
index 0000000..edc4919
--- /dev/null
+++ b/Framework/Inputs/HierarchicalTiff.cpp
@@ -0,0 +1,308 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "HierarchicalTiff.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <iostream>
+#include <algorithm>
+#include <cassert>
+#include <string.h>
+
+namespace OrthancWSI
+{
+  HierarchicalTiff::Level::Level(TIFF* tiff,
+                                 tdir_t    directory,
+                                 unsigned int  width,
+                                 unsigned int  height) :
+    directory_(directory),
+    width_(width),
+    height_(height)
+  {
+    // Read the JPEG headers shared at that level, if any
+    uint8_t *tables = NULL;
+    uint32_t size;
+    if (TIFFGetField(tiff, TIFFTAG_JPEGTABLES, &size, &tables) &&
+        size > 0 && 
+        tables != NULL)
+    {
+      // Look for the EOI (end-of-image) tag == FF D9
+      // https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
+
+      bool found = false;
+
+      for (size_t i = 0; i + 1 < size; i++)
+      {
+        if (tables[i] == 0xff &&
+            tables[i + 1] == 0xd9)
+        {
+          headers_.assign(reinterpret_cast<const char*>(tables), i);
+          found = true;
+        }
+      }
+
+      if (!found)
+      {
+        headers_.assign(reinterpret_cast<const char*>(tables), size);
+      }
+    }
+  }
+
+  struct HierarchicalTiff::Comparator
+  {
+    bool operator() (const HierarchicalTiff::Level& a,
+                     const HierarchicalTiff::Level& b) const
+    {
+      return a.width_ > b.width_;
+    }
+  };
+
+
+  void HierarchicalTiff::Finalize()
+  {
+    if (tiff_)
+    {
+      TIFFClose(tiff_);
+      tiff_ = NULL;
+    }
+  }
+
+
+  void HierarchicalTiff::CheckLevel(unsigned int level) const
+  {
+    if (level >= levels_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool HierarchicalTiff::GetCurrentCompression(ImageCompression& compression)
+  {
+    uint16_t c;
+    if (!TIFFGetField(tiff_, TIFFTAG_COMPRESSION, &c))
+    {
+      return false;
+    }
+
+    switch (c)
+    {
+      case COMPRESSION_NONE:
+        compression = ImageCompression_None;
+        return true;
+
+      case COMPRESSION_JPEG:
+        compression = ImageCompression_Jpeg;
+        return true;
+
+      default:
+        return false;
+    }
+  }
+
+
+  bool HierarchicalTiff::GetCurrentPixelFormat(Orthanc::PixelFormat& pixelFormat,
+                                               ImageCompression compression)
+  {
+    // http://www.awaresystems.be/imaging/tiff/tifftags/baseline.html
+
+    uint16_t channels, photometric, bpp, planar;
+    if (!TIFFGetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, &channels) ||
+        channels == 0 ||
+        !TIFFGetField(tiff_, TIFFTAG_PHOTOMETRIC, &photometric) ||
+        !TIFFGetField(tiff_, TIFFTAG_BITSPERSAMPLE, &bpp) ||
+        !TIFFGetField(tiff_, TIFFTAG_PLANARCONFIG, &planar))
+    {
+      return false;
+    }
+
+    if (compression == ImageCompression_Jpeg &&
+        channels == 3 &&     // This is a color image
+        bpp == 8 &&
+        photometric == PHOTOMETRIC_YCBCR &&
+        planar == PLANARCONFIG_CONTIG)  // This is interleaved RGB
+    {
+      pixelFormat = Orthanc::PixelFormat_RGB24;
+    }
+    else
+    {
+      return false;
+    }
+
+    return true;
+  }
+
+
+  bool  HierarchicalTiff::Initialize()
+  {
+    bool first = true;
+    tdir_t pos = 0;
+
+    do
+    {
+      uint32_t w, h, tw, th;
+      ImageCompression compression;
+      Orthanc::PixelFormat pixelFormat;
+
+      if (TIFFSetDirectory(tiff_, pos) &&
+          TIFFGetField(tiff_, TIFFTAG_IMAGEWIDTH, &w) &&
+          TIFFGetField(tiff_, TIFFTAG_IMAGELENGTH, &h) &&
+          TIFFGetField(tiff_, TIFFTAG_TILEWIDTH, &tw) &&
+          TIFFGetField(tiff_, TIFFTAG_TILELENGTH, &th) &&
+          w > 0 &&
+          h > 0 &&
+          tw > 0 &&
+          th > 0 &&
+          GetCurrentCompression(compression) &&
+          GetCurrentPixelFormat(pixelFormat, compression))
+      {
+        if (first)
+        {
+          tileWidth_ = tw;
+          tileHeight_ = th;
+          compression_ = compression;
+          pixelFormat_ = pixelFormat;
+          first = false;
+        }
+        else if (tw != tileWidth_ ||
+                 th != tileHeight_ ||
+                 compression_ != compression ||
+                 pixelFormat_ != pixelFormat)
+        {
+          LOG(ERROR) << "The tile size or compression of the TIFF file varies along levels, this is not supported";
+          return false;
+        }
+
+        levels_.push_back(Level(tiff_, pos, w, h));
+      }
+
+      pos++;
+    }
+    while (TIFFReadDirectory(tiff_));
+
+    if (levels_.size() == 0)
+    {
+      LOG(ERROR) << "This is not a tiled TIFF image";
+      return false;
+    }
+
+    std::sort(levels_.begin(), levels_.end(), Comparator());
+    return true;
+  }
+
+
+  HierarchicalTiff::HierarchicalTiff(const std::string& path) :
+    tileWidth_(0),
+    tileHeight_(0)
+  {
+    tiff_ = TIFFOpen(path.c_str(), "r");
+    if (tiff_ == NULL)
+    {
+      LOG(ERROR) << "libtiff cannot open: " << path;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
+    }
+
+    if (!Initialize())
+    {
+      Finalize();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  unsigned int HierarchicalTiff::GetLevelWidth(unsigned int level) const
+  {
+    CheckLevel(level);
+    return levels_[level].width_;
+  }
+
+
+  unsigned int HierarchicalTiff::GetLevelHeight(unsigned int level) const
+  {
+    CheckLevel(level);
+    return levels_[level].height_;
+  }
+
+
+  bool HierarchicalTiff::ReadRawTile(std::string& tile,
+                                     unsigned int level,
+                                     unsigned int tileX,
+                                     unsigned int tileY)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    CheckLevel(level);
+
+    // Make the TIFF context point to the level of interest
+    if (!TIFFSetDirectory(tiff_, levels_[level].directory_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+    }
+
+    // Get the index of the tile
+    ttile_t index = TIFFComputeTile(tiff_, tileX * tileWidth_, tileY * tileHeight_, 0 /*z*/, 0 /*sample*/);
+
+    // Read the raw tile
+    toff_t *sizes;
+    if (!TIFFGetField(tiff_, TIFFTAG_TILEBYTECOUNTS, &sizes))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+    }
+
+    std::string raw;
+    raw.resize(sizes[index]);
+
+    tsize_t read = TIFFReadRawTile(tiff_, index, &raw[0], raw.size());
+    if (read != static_cast<tsize_t>(sizes[index]))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+    }
+
+    const std::string& headers = levels_[level].headers_;
+
+    // Possibly prepend the raw tile with the shared JPEG headers
+    if (headers.empty() ||
+        compression_ != ImageCompression_Jpeg)
+    {
+      tile.swap(raw);   // Same as "tile.assign(raw)", but optimizes memory
+    }
+    else
+    {
+      assert(compression_ == ImageCompression_Jpeg);
+
+      // Check that the raw JPEG tile starts with the SOI (start-of-image) tag == FF D8
+      if (raw.size() < 2 ||
+          static_cast<uint8_t>(raw[0]) != 0xff ||
+          static_cast<uint8_t>(raw[1]) != 0xd8)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+      }
+
+      tile.resize(headers.size() + raw.size() - 2);
+      memcpy(&tile[0], &headers[0], headers.size());
+      memcpy(&tile[0] + headers.size(), &raw[2], raw.size() - 2);
+    }
+
+    return true;
+  }
+}
diff --git a/Framework/Inputs/HierarchicalTiff.h b/Framework/Inputs/HierarchicalTiff.h
new file mode 100644
index 0000000..6d3b6b8
--- /dev/null
+++ b/Framework/Inputs/HierarchicalTiff.h
@@ -0,0 +1,111 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PyramidWithRawTiles.h"
+
+#include <tiff.h>
+#include <tiffio.h>
+#include <vector>
+#include <boost/thread.hpp>
+
+namespace OrthancWSI
+{
+  class HierarchicalTiff : public PyramidWithRawTiles
+  {
+  private:
+    struct Level
+    {
+      tdir_t    directory_;
+      unsigned int  width_;
+      unsigned int  height_;
+      std::string  headers_;
+
+      Level(TIFF* tiff,
+            tdir_t    directory,
+            unsigned int  width,
+            unsigned int  height);
+    };
+
+    struct Comparator;
+
+    boost::mutex          mutex_;
+    TIFF*                 tiff_;
+    Orthanc::PixelFormat  pixelFormat_;
+    ImageCompression      compression_;
+    unsigned int          tileWidth_;
+    unsigned int          tileHeight_;
+    std::vector<Level>    levels_;
+
+    void Finalize();
+
+    void CheckLevel(unsigned int level) const;
+
+    bool GetCurrentCompression(ImageCompression& compression);
+
+    bool GetCurrentPixelFormat(Orthanc::PixelFormat& pixelFormat,
+                               ImageCompression compression);
+
+    bool Initialize();
+
+  public:
+    HierarchicalTiff(const std::string& path);
+
+    virtual ~HierarchicalTiff()
+    {
+      Finalize();
+    }
+
+    virtual unsigned int GetLevelCount() const
+    {
+      return levels_.size();
+    }
+
+    virtual unsigned int GetLevelWidth(unsigned int level) const;
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const;
+
+    virtual unsigned int GetTileWidth() const
+    {
+      return tileWidth_;
+    }
+
+    virtual unsigned int GetTileHeight() const
+    {
+      return tileHeight_;
+    }
+
+    virtual bool ReadRawTile(std::string& tile,
+                             unsigned int level,
+                             unsigned int tileX,
+                             unsigned int tileY);
+
+    virtual ImageCompression GetImageCompression() const
+    {
+      return compression_;
+    }
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return pixelFormat_;
+    }
+  };
+}
diff --git a/Framework/Inputs/ITiledPyramid.h b/Framework/Inputs/ITiledPyramid.h
new file mode 100644
index 0000000..7efa495
--- /dev/null
+++ b/Framework/Inputs/ITiledPyramid.h
@@ -0,0 +1,68 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include "../Orthanc/Core/Images/ImageAccessor.h"
+
+#include <boost/noncopyable.hpp>
+#include <string>
+
+
+namespace OrthancWSI
+{
+  /**
+   * Class that represents a whole-slide image. It is assumed to be
+   * thread-safe, which is the case of libtiff and openslide.
+   **/
+  class ITiledPyramid : public boost::noncopyable
+  {
+  public:
+    virtual ~ITiledPyramid()
+    {
+    }
+
+    virtual unsigned int GetLevelCount() const = 0;
+
+    virtual unsigned int GetLevelWidth(unsigned int level) const = 0;
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const = 0;
+
+    virtual unsigned int GetTileWidth() const = 0;
+
+    virtual unsigned int GetTileHeight() const = 0;
+
+    virtual bool ReadRawTile(std::string& tile,
+                             unsigned int level,
+                             unsigned int tileX,
+                             unsigned int tileY) = 0;
+
+    virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level,
+                                               unsigned int tileX,
+                                               unsigned int tileY) = 0;
+
+    // Only makes sense for images with raw access to tiles
+    virtual ImageCompression GetImageCompression() const = 0;
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const = 0;
+  };
+}
diff --git a/Framework/Inputs/OpenSlideLibrary.cpp b/Framework/Inputs/OpenSlideLibrary.cpp
new file mode 100644
index 0000000..b741f6c
--- /dev/null
+++ b/Framework/Inputs/OpenSlideLibrary.cpp
@@ -0,0 +1,234 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "OpenSlideLibrary.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/Images/Image.h"
+
+namespace OrthancWSI
+{
+  static std::auto_ptr<OpenSlideLibrary>  globalLibrary_;
+
+
+  OpenSlideLibrary::OpenSlideLibrary(const std::string& path) :
+    library_(path)
+  {
+    close_ = (FunctionClose) library_.GetFunction("openslide_close");
+    getLevelCount_ = (FunctionGetLevelCount) library_.GetFunction("openslide_get_level_count");
+    getLevelDimensions_ = (FunctionGetLevelDimensions) library_.GetFunction("openslide_get_level_dimensions");
+    getLevelDownsample_ = (FunctionGetLevelDownsample) library_.GetFunction("openslide_get_level_downsample");
+    open_ = (FunctionOpen) library_.GetFunction("openslide_open");
+    readRegion_ = (FunctionReadRegion) library_.GetFunction("openslide_read_region");
+  }
+
+
+  OpenSlideLibrary::Image::Level::Level() : 
+    width_(0),
+    height_(0), 
+    downsample_(1)
+  {
+  }
+
+
+  OpenSlideLibrary::Image::Level::Level(int64_t width,
+                                        int64_t height,
+                                        double downsample) :
+    width_(static_cast<unsigned int>(width)),
+    height_(static_cast<unsigned int>(height)),
+    downsample_(downsample)
+  {
+    if (width < 0 ||
+        height < 0 ||
+        downsample <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    if (static_cast<int64_t>(width_) != width ||
+        static_cast<int64_t>(height_) != height)
+    {
+      LOG(ERROR) << "The whole-slide image is too large";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void OpenSlideLibrary::Image::Initialize(const std::string& path)
+  {
+    handle_ = that_.open_(path.c_str());
+    if (handle_ == NULL)
+    {
+      LOG(ERROR) << "Cannot open an image with OpenSlide: " << path;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    try
+    {
+      LOG(INFO) << "Opening an image with OpenSlide: " << path;
+
+      int32_t tmp = that_.getLevelCount_(handle_);
+      if (tmp <= 0)
+      {
+        LOG(ERROR) << "Image with no pyramid level";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      levels_.resize(tmp);
+
+      for (int32_t level = 0; level < tmp; level++)
+      {
+        int64_t width, height;
+        that_.getLevelDimensions_(handle_, level, &width, &height);
+
+        double downsample = that_.getLevelDownsample_(handle_, level);
+
+        levels_[level] = Level(width, height, downsample);
+      }
+
+      for (size_t i = 1; i < levels_.size(); i++)
+      {
+        if (levels_[i].width_ >= levels_[i - 1].width_ ||
+            levels_[i].height_ >= levels_[i - 1].height_)
+        {
+          // This is not a pyramid with levels of decreasing sizes
+          // (level "0" must be the finest level)
+          LOG(ERROR) << "The pyramid does not have levels of strictly decreasing sizes";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      }
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      Close();
+      throw;
+    }
+  }
+
+
+  OpenSlideLibrary::Image::Image(OpenSlideLibrary& that,
+                                 const std::string& path) :
+    that_(that),
+    handle_(NULL)
+  {
+    Initialize(path);
+  }
+
+
+  OpenSlideLibrary::Image::Image(const std::string& path) :
+    that_(OpenSlideLibrary::GetInstance()),
+    handle_(NULL)
+  {
+    Initialize(path);
+  }
+
+
+  void OpenSlideLibrary::Image::Close()
+  {
+    if (handle_ != NULL)
+    {
+      that_.close_(handle_);
+      handle_ = NULL;
+    }
+  }
+
+
+  void OpenSlideLibrary::Image::CheckLevel(unsigned int level) const
+  {
+    if (level >= levels_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  double OpenSlideLibrary::Image::GetLevelDownsample(unsigned int level) const
+  {
+    CheckLevel(level);
+    return levels_[level].downsample_;
+  }
+
+
+  unsigned int OpenSlideLibrary::Image::GetLevelWidth(unsigned int level) const
+  {
+    CheckLevel(level);
+    return levels_[level].width_;
+  }
+
+
+  unsigned int OpenSlideLibrary::Image::GetLevelHeight(unsigned int level) const
+  {
+    CheckLevel(level);
+    return levels_[level].height_;
+  }
+
+
+  Orthanc::ImageAccessor* OpenSlideLibrary::Image::ReadRegion(unsigned int level,
+                                                              uint64_t x,
+                                                              uint64_t y,
+                                                              unsigned int width,
+                                                              unsigned int height)
+  {
+    CheckLevel(level);
+
+    // Create a new image, with minimal pitch so as to be compatible with OpenSlide API
+    std::auto_ptr<Orthanc::ImageAccessor> region(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, true));
+
+    if (region->GetWidth() != 0 &&
+        region->GetHeight() != 0)
+    {
+      double zoom = levels_[level].downsample_;
+      x = static_cast<uint64_t>(zoom * static_cast<double>(x));
+      y = static_cast<uint64_t>(zoom * static_cast<double>(y));
+          
+      that_.readRegion_(handle_, reinterpret_cast<uint32_t*>(region->GetBuffer()),
+                        x, y, level, region->GetWidth(), region->GetHeight());
+    }
+
+    return region.release();
+  }
+
+
+  OpenSlideLibrary& OpenSlideLibrary::GetInstance()
+  {
+    if (globalLibrary_.get() == NULL)
+    {
+      LOG(ERROR) << "OpenSlide has not been initialized, use the \"--openslide\" command-line option";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *globalLibrary_;
+    }
+  }
+
+
+  void OpenSlideLibrary::Initialize(const std::string& path)
+  {
+    globalLibrary_.reset(new OpenSlideLibrary(path));
+  }
+
+
+  void OpenSlideLibrary::Finalize()
+  {
+    globalLibrary_.reset(NULL);
+  }
+}
diff --git a/Framework/Inputs/OpenSlideLibrary.h b/Framework/Inputs/OpenSlideLibrary.h
new file mode 100644
index 0000000..ac77998
--- /dev/null
+++ b/Framework/Inputs/OpenSlideLibrary.h
@@ -0,0 +1,112 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Orthanc/Plugins/Engine/SharedLibrary.h"
+#include "../Orthanc/Core/Images/ImageAccessor.h"
+
+#include <vector>
+
+namespace OrthancWSI
+{
+  class OpenSlideLibrary : public boost::noncopyable
+  {
+  private:
+    typedef void*   (*FunctionClose) (void*);
+    typedef int32_t (*FunctionGetLevelCount) (void*);
+    typedef void    (*FunctionGetLevelDimensions) (void*, int32_t, int64_t*, int64_t*);
+    typedef double  (*FunctionGetLevelDownsample) (void*, int32_t);
+    typedef void*   (*FunctionOpen) (const char*);
+    typedef void    (*FunctionReadRegion) (void*, uint32_t*, int64_t, int64_t, int32_t, int64_t, int64_t);
+
+    Orthanc::SharedLibrary      library_;
+    FunctionClose               close_;
+    FunctionGetLevelCount       getLevelCount_;
+    FunctionGetLevelDimensions  getLevelDimensions_;
+    FunctionGetLevelDownsample  getLevelDownsample_;
+    FunctionOpen                open_;
+    FunctionReadRegion          readRegion_;
+
+  public:
+    OpenSlideLibrary(const std::string& path);
+
+    static OpenSlideLibrary& GetInstance();
+
+    static void Initialize(const std::string& path);
+
+    static void Finalize();
+
+    class Image : public boost::noncopyable
+    {
+    private:
+      struct Level
+      {
+        unsigned int  width_;
+        unsigned int  height_;
+        double        downsample_;
+
+        Level();
+
+        Level(int64_t width,
+              int64_t height,
+              double downsample);
+      };
+
+      OpenSlideLibrary&   that_;
+      void*               handle_;
+      std::vector<Level>  levels_;
+
+      void Initialize(const std::string& path);
+
+      void Close();
+
+      void CheckLevel(unsigned int level) const;
+
+    public:
+      Image(OpenSlideLibrary& that,
+            const std::string& path);
+
+      Image(const std::string& path);
+
+      ~Image()
+      {
+        Close();
+      }
+
+      unsigned int GetLevelCount() const
+      {
+        return levels_.size();
+      }
+
+      double GetLevelDownsample(unsigned int level) const;
+
+      unsigned int GetLevelWidth(unsigned int level) const;
+
+      unsigned int GetLevelHeight(unsigned int level) const;
+
+      Orthanc::ImageAccessor* ReadRegion(unsigned int level,
+                                         uint64_t x,
+                                         uint64_t y,
+                                         unsigned int width,
+                                         unsigned int height);
+    };
+  };
+}
diff --git a/Framework/Inputs/OpenSlidePyramid.cpp b/Framework/Inputs/OpenSlidePyramid.cpp
new file mode 100644
index 0000000..c816044
--- /dev/null
+++ b/Framework/Inputs/OpenSlidePyramid.cpp
@@ -0,0 +1,48 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "OpenSlidePyramid.h"
+
+#include "../Orthanc/Core/Images/ImageProcessing.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Logging.h"
+
+namespace OrthancWSI
+{
+  void OpenSlidePyramid::ReadRegion(Orthanc::ImageAccessor& target,
+                                    unsigned int level,
+                                    unsigned int x,
+                                    unsigned int y)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor> source(image_.ReadRegion(level, x, y, target.GetWidth(), target.GetHeight()));
+    Orthanc::ImageProcessing::Convert(target, *source);
+  }
+
+
+  OpenSlidePyramid::OpenSlidePyramid(const std::string& path,
+                                     unsigned int tileWidth,
+                                     unsigned int tileHeight) :
+    image_(path),
+    tileWidth_(tileWidth),
+    tileHeight_(tileHeight)
+  {
+  }
+}
diff --git a/Framework/Inputs/OpenSlidePyramid.h b/Framework/Inputs/OpenSlidePyramid.h
new file mode 100644
index 0000000..999c9a4
--- /dev/null
+++ b/Framework/Inputs/OpenSlidePyramid.h
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DecodedTiledPyramid.h"
+#include "OpenSlideLibrary.h"
+
+namespace OrthancWSI
+{
+  class OpenSlidePyramid : public DecodedTiledPyramid
+  {
+  private:
+    OpenSlideLibrary::Image  image_;
+    unsigned int             tileWidth_;
+    unsigned int             tileHeight_;
+
+  protected:
+    virtual void ReadRegion(Orthanc::ImageAccessor& target,
+                            unsigned int level,
+                            unsigned int x,
+                            unsigned int y);
+
+  public:
+    OpenSlidePyramid(const std::string& path,
+                     unsigned int tileWidth,
+                     unsigned int tileHeight);
+
+    virtual unsigned int GetTileWidth() const
+    {
+      return tileWidth_;
+    }
+
+    virtual unsigned int GetTileHeight() const
+    {
+      return tileHeight_;
+    }
+
+    virtual unsigned int GetLevelCount() const
+    {
+      return image_.GetLevelCount();
+    }
+
+    virtual unsigned int GetLevelWidth(unsigned int level) const
+    {
+      return image_.GetLevelWidth(level);
+    }
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const
+    {
+      return image_.GetLevelHeight(level);
+    }
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return Orthanc::PixelFormat_RGB24;
+    }
+  };
+}
diff --git a/Framework/Inputs/PyramidWithRawTiles.cpp b/Framework/Inputs/PyramidWithRawTiles.cpp
new file mode 100644
index 0000000..5342491
--- /dev/null
+++ b/Framework/Inputs/PyramidWithRawTiles.cpp
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "PyramidWithRawTiles.h"
+
+#include "../Orthanc/Core/Images/PngReader.h"
+#include "../Orthanc/Core/Images/JpegReader.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Jpeg2000Reader.h"
+
+namespace OrthancWSI
+{
+  Orthanc::ImageAccessor* PyramidWithRawTiles::DecodeTile(unsigned int level,
+                                                          unsigned int tileX,
+                                                          unsigned int tileY)
+  {
+    std::string tile;
+    if (!ReadRawTile(tile, level, tileX, tileY))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    std::auto_ptr<Orthanc::ImageAccessor> result;
+
+    switch (GetImageCompression())
+    {
+      case ImageCompression_None:
+        result.reset(new Orthanc::ImageAccessor);
+        result->AssignReadOnly(GetPixelFormat(), 
+                               GetTileWidth(),
+                               GetTileHeight(), 
+                               GetBytesPerPixel(GetPixelFormat()) * GetTileWidth(),
+                               tile.c_str());
+        break;
+
+      case ImageCompression_Jpeg:
+        result.reset(new Orthanc::JpegReader);
+        dynamic_cast<Orthanc::JpegReader&>(*result).ReadFromMemory(tile);
+        break;
+
+      case ImageCompression_Png:
+        result.reset(new Orthanc::PngReader);
+        dynamic_cast<Orthanc::PngReader&>(*result).ReadFromMemory(tile);
+        break;
+
+      case ImageCompression_Jpeg2000:
+        result.reset(new Jpeg2000Reader);
+        dynamic_cast<Jpeg2000Reader&>(*result).ReadFromMemory(tile);
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    return result.release();
+  }
+}
diff --git a/Framework/Inputs/PyramidWithRawTiles.h b/Framework/Inputs/PyramidWithRawTiles.h
new file mode 100644
index 0000000..7b7ef49
--- /dev/null
+++ b/Framework/Inputs/PyramidWithRawTiles.h
@@ -0,0 +1,34 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ITiledPyramid.h"
+
+namespace OrthancWSI
+{
+  class PyramidWithRawTiles : public ITiledPyramid
+  {
+  public:
+    virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level,
+                                               unsigned int tileX,
+                                               unsigned int tileY);
+  };
+}
diff --git a/Framework/Inputs/SingleLevelDecodedPyramid.cpp b/Framework/Inputs/SingleLevelDecodedPyramid.cpp
new file mode 100644
index 0000000..8bb55eb
--- /dev/null
+++ b/Framework/Inputs/SingleLevelDecodedPyramid.cpp
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "SingleLevelDecodedPyramid.h"
+#include "../ImageToolbox.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancWSI
+{
+  void SingleLevelDecodedPyramid::ReadRegion(Orthanc::ImageAccessor& target,
+                                             unsigned int level,
+                                             unsigned int x,
+                                             unsigned int y)
+  {
+    Orthanc::ImageAccessor region = image_.GetRegion(x, y, target.GetWidth(), target.GetHeight());
+    ImageToolbox::Copy(target, region);
+  }
+
+
+  unsigned int SingleLevelDecodedPyramid::GetLevelWidth(unsigned int level) const
+  {
+    if (level != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return image_.GetWidth();
+  }
+
+
+  unsigned int SingleLevelDecodedPyramid::GetLevelHeight(unsigned int level) const
+  {
+    if (level != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return image_.GetHeight();
+  }
+}
diff --git a/Framework/Inputs/SingleLevelDecodedPyramid.h b/Framework/Inputs/SingleLevelDecodedPyramid.h
new file mode 100644
index 0000000..ac3654f
--- /dev/null
+++ b/Framework/Inputs/SingleLevelDecodedPyramid.h
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DecodedTiledPyramid.h"
+
+namespace OrthancWSI
+{
+  class SingleLevelDecodedPyramid : public DecodedTiledPyramid
+  {
+  private:
+    Orthanc::ImageAccessor  image_;
+    unsigned int            tileWidth_;
+    unsigned int            tileHeight_;
+
+  protected:
+    void SetImage(const Orthanc::ImageAccessor& image)
+    {
+      image_ = image;
+    }
+
+    virtual void ReadRegion(Orthanc::ImageAccessor& target,
+                            unsigned int level,
+                            unsigned int x,
+                            unsigned int y);
+
+  public:
+    SingleLevelDecodedPyramid(unsigned int tileWidth,
+                              unsigned int tileHeight) :
+      tileWidth_(tileWidth),
+      tileHeight_(tileHeight)
+    {
+    }
+
+    virtual unsigned int GetTileWidth() const
+    {
+      return tileWidth_;
+    }
+
+    virtual unsigned int GetTileHeight() const
+    {
+      return tileHeight_;
+    }
+
+    virtual unsigned int GetLevelCount() const
+    {
+      return 1;
+    }
+
+    virtual unsigned int GetLevelWidth(unsigned int level) const;
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const;
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return image_.GetFormat();
+    }
+  };
+}
diff --git a/Framework/Inputs/TiledJpegImage.h b/Framework/Inputs/TiledJpegImage.h
new file mode 100644
index 0000000..4414d8d
--- /dev/null
+++ b/Framework/Inputs/TiledJpegImage.h
@@ -0,0 +1,44 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "SingleLevelDecodedPyramid.h"
+
+#include "../Orthanc/Core/Images/JpegReader.h"
+
+namespace OrthancWSI
+{
+  class TiledJpegImage : public SingleLevelDecodedPyramid
+  {
+  private:
+    Orthanc::JpegReader  reader_;
+
+  public:
+    TiledJpegImage(const std::string& path,
+                   unsigned int tileWidth,
+                   unsigned int tileHeight) :
+      SingleLevelDecodedPyramid(tileWidth, tileHeight)
+    {
+      reader_.ReadFromFile(path);
+      SetImage(reader_);
+    }
+  };
+}
diff --git a/Framework/Inputs/TiledPngImage.h b/Framework/Inputs/TiledPngImage.h
new file mode 100644
index 0000000..c8d1551
--- /dev/null
+++ b/Framework/Inputs/TiledPngImage.h
@@ -0,0 +1,44 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "SingleLevelDecodedPyramid.h"
+
+#include "../Orthanc/Core/Images/PngReader.h"
+
+namespace OrthancWSI
+{
+  class TiledPngImage : public SingleLevelDecodedPyramid
+  {
+  private:
+    Orthanc::PngReader  reader_;
+
+  public:
+    TiledPngImage(const std::string& path,
+                  unsigned int tileWidth,
+                  unsigned int tileHeight) :
+      SingleLevelDecodedPyramid(tileWidth, tileHeight)
+    {
+      reader_.ReadFromFile(path);
+      SetImage(reader_);
+    }
+  };
+}
diff --git a/Framework/Inputs/TiledPyramidStatistics.cpp b/Framework/Inputs/TiledPyramidStatistics.cpp
new file mode 100644
index 0000000..405506f
--- /dev/null
+++ b/Framework/Inputs/TiledPyramidStatistics.cpp
@@ -0,0 +1,74 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "TiledPyramidStatistics.h"
+
+#include "../Orthanc/Core/Logging.h"
+
+
+namespace OrthancWSI
+{
+  TiledPyramidStatistics::TiledPyramidStatistics(ITiledPyramid& source) :
+    source_(source),
+    countRawAccesses_(0),
+    countDecodedTiles_(0)
+  {
+  }
+
+
+  TiledPyramidStatistics::~TiledPyramidStatistics()
+  {
+    LOG(WARNING) << "Closing the input image (" 
+                 << countRawAccesses_ << " raw accesses to the tiles, "
+                 << countDecodedTiles_ << " decoded tiles)";
+  }
+
+
+  bool TiledPyramidStatistics::ReadRawTile(std::string& tile,
+                                           unsigned int level,
+                                           unsigned int tileX,
+                                           unsigned int tileY)
+  {
+    if (source_.ReadRawTile(tile, level, tileX, tileY))
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      countRawAccesses_++;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  Orthanc::ImageAccessor* TiledPyramidStatistics::DecodeTile(unsigned int level,
+                                                             unsigned int tileX,
+                                                             unsigned int tileY)
+  {
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      countDecodedTiles_++;
+    }
+
+    return source_.DecodeTile(level, tileX, tileY);
+  }
+}
diff --git a/Framework/Inputs/TiledPyramidStatistics.h b/Framework/Inputs/TiledPyramidStatistics.h
new file mode 100644
index 0000000..7b5917d
--- /dev/null
+++ b/Framework/Inputs/TiledPyramidStatistics.h
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ITiledPyramid.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancWSI
+{
+  class TiledPyramidStatistics : public ITiledPyramid
+  {
+  private:
+    boost::mutex   mutex_;
+    ITiledPyramid& source_;
+    unsigned int   countRawAccesses_;
+    unsigned int   countDecodedTiles_;
+
+  public:
+    TiledPyramidStatistics(ITiledPyramid& source);   // Takes ownership
+
+    virtual ~TiledPyramidStatistics();
+
+    virtual unsigned int GetLevelCount() const
+    {
+      return source_.GetLevelCount();
+    }
+
+    virtual unsigned int GetLevelWidth(unsigned int level)  const
+    {
+      return source_.GetLevelWidth(level);
+    }
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const
+    {
+      return source_.GetLevelHeight(level);
+    }
+
+    virtual unsigned int GetTileWidth() const
+    {
+      return source_.GetTileWidth();
+    }
+    
+    virtual unsigned int GetTileHeight() const
+    {
+      return source_.GetTileHeight();
+    }
+
+    virtual ImageCompression GetImageCompression() const
+    {
+      return source_.GetImageCompression();
+    }
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return source_.GetPixelFormat();
+    }
+
+    virtual bool ReadRawTile(std::string& tile,
+                             unsigned int level,
+                             unsigned int tileX,
+                             unsigned int tileY);
+
+    virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level,
+                                               unsigned int tileX,
+                                               unsigned int tileY);
+  };
+}
diff --git a/Framework/Jpeg2000Reader.cpp b/Framework/Jpeg2000Reader.cpp
new file mode 100644
index 0000000..41637e2
--- /dev/null
+++ b/Framework/Jpeg2000Reader.cpp
@@ -0,0 +1,491 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
+#include "Jpeg2000Reader.h"
+
+#include "Orthanc/Core/OrthancException.h"
+#include "Orthanc/Core/Toolbox.h"
+#include "ImageToolbox.h"
+
+#include <cassert>
+#include <string.h>
+#include <openjpeg.h>
+
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+#  define OPJ_CLRSPC_GRAY CLRSPC_GRAY
+#  define OPJ_CLRSPC_SRGB CLRSPC_SRGB
+#  define OPJ_CODEC_J2K   CODEC_J2K
+#  define OPJ_CODEC_JP2   CODEC_JP2
+typedef opj_dinfo_t opj_codec_t;
+typedef opj_cio_t opj_stream_t;
+#elif ORTHANC_OPENJPEG_MAJOR_VERSION == 2
+#else
+#error Unsupported version of OpenJpeg
+#endif
+
+
+namespace OrthancWSI
+{
+  namespace
+  {
+    // Check out opj_dparameters_t::decod_format
+    enum InputFormat
+    {
+      InputFormat_J2K = 0,
+      InputFormat_JP2 = 1,
+      InputFormat_JPT = 2
+    };
+
+    enum OutputFormat
+    {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+      OutputFormat_PGX = 1
+#else
+      OutputFormat_PGX = 11
+#endif
+    };
+
+    class OpenJpegDecoder : public boost::noncopyable
+    {
+    private:
+      opj_dparameters_t  parameters_;
+      opj_codec_t* dinfo_;
+
+      void SetupParameters(InputFormat format)
+      {
+        opj_set_default_decoder_parameters(&parameters_);
+
+        parameters_.decod_format = format;
+        parameters_.cod_format = OutputFormat_PGX;
+
+#if OPENJPEG_MAJOR_VERSION == 1
+        parameters_.cp_layer = 0;
+        parameters_.cp_reduce = 0;
+#endif
+      }
+
+      void Finalize()
+      {
+        if (dinfo_ != NULL)
+        {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+          opj_destroy_decompress(dinfo_);
+#else
+          opj_destroy_codec(dinfo_);
+#endif
+          dinfo_ = NULL;
+        }
+      }
+
+    public:
+      OpenJpegDecoder(Jpeg2000Format format) : dinfo_(NULL)
+      {
+        switch (format)
+        {
+          case Jpeg2000Format_J2K:
+            SetupParameters(InputFormat_J2K);
+            dinfo_ = opj_create_decompress(OPJ_CODEC_J2K);
+            break;
+
+          case Jpeg2000Format_JP2:
+            SetupParameters(InputFormat_JP2);
+            dinfo_ = opj_create_decompress(OPJ_CODEC_JP2);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        if (!dinfo_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+        opj_setup_decoder(dinfo_, &parameters_);
+#else
+        if (!opj_setup_decoder(dinfo_, &parameters_))
+        {
+          Finalize();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+#endif
+      }
+
+      ~OpenJpegDecoder()
+      {
+        Finalize();
+      } 
+
+      opj_codec_t* GetObject()
+      {
+        return dinfo_;
+      }
+
+      const opj_dparameters_t& GetParameters() const
+      {
+        return parameters_;
+      }
+    };
+
+
+    class OpenJpegInput : public boost::noncopyable
+    {
+    private:
+      opj_stream_t* cio_;
+
+      const uint8_t* buffer_;
+      size_t         size_;
+      size_t         position_;
+      
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 2
+      static void Free(void *userData)
+      {
+      }
+
+      static OPJ_SIZE_T Read(void *target, 
+                             OPJ_SIZE_T size, 
+                             void *userData)
+      {
+        OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData);
+        assert(that.position_ >= 0 && that.position_ <= that.size_);
+        assert(size >= 0);
+
+        if (that.position_ == that.size_)
+        {
+          // End of file
+          return -1;
+        }
+        else
+        {
+          if (that.position_ + size > that.size_)
+          {
+            size = that.size_ - that.position_;
+          }
+
+          if (size > 0)
+          {
+            memcpy(target, that.buffer_ + that.position_, size);
+          }
+
+          that.position_ += size;
+          return size;
+        }
+      }
+ 
+      static OPJ_OFF_T Skip(OPJ_OFF_T skip, 
+                            void *userData)
+      {
+        assert(skip >= 0);
+        OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData);
+
+        if (that.position_ == that.size_)
+        {
+          // End of file
+          return -1;
+        }
+        else if (that.position_ + skip > that.size_)
+        {
+          size_t offset = that.size_ - that.position_;
+          that.position_ = that.size_;
+          return offset;
+        }
+        else
+        {
+          that.position_ += skip;
+          return skip;
+        }
+      }
+ 
+      static OPJ_BOOL Seek(OPJ_OFF_T position, 
+                           void *userData)
+      {
+        assert(position >= 0);
+        OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData);
+        
+        if (static_cast<size_t>(position) > that.size_)
+        {
+          that.position_ = that.size_;
+          return false;
+        }
+        else
+        {
+          that.position_ = position;
+          return true;
+        }
+      }
+#endif
+
+    public:
+      OpenJpegInput(OpenJpegDecoder& decoder,
+                    const void* buffer,
+                    size_t size) :
+        buffer_(reinterpret_cast<const uint8_t*>(buffer)),
+        size_(size),
+        position_(0)
+      {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+        cio_ = opj_cio_open(reinterpret_cast<opj_common_ptr>(decoder.GetObject()), 
+                            reinterpret_cast<unsigned char*>(const_cast<void*>(buffer)),
+                            size);
+#else
+        cio_ = opj_stream_create(size_, 1 /* input stream */);
+        if (!cio_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        // http://openjpeg.narkive.com/zHqG2fMe/opj-stream-set-user-data-length
+        // "I'd suggest to precise in the documentation that the skip
+        // and read callback functions should return -1 on end of
+        // stream, and the seek callback function should return false
+        // on end of stream."
+
+        opj_stream_set_user_data(cio_, this, Free);
+        opj_stream_set_user_data_length(cio_, size);
+        opj_stream_set_read_function(cio_, Read);
+        opj_stream_set_skip_function(cio_, Skip);
+        opj_stream_set_seek_function(cio_, Seek);
+#endif
+    }
+
+      ~OpenJpegInput()
+      {
+        if (cio_)
+        {
+ #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+          opj_cio_close(cio_);
+#else
+          opj_stream_destroy(cio_);
+#endif
+          cio_ = NULL;
+        }
+      }
+
+      opj_stream_t* GetObject()
+      {
+        return cio_;
+      }
+    };
+
+
+    class OpenJpegImage
+    {
+    private:
+      opj_image_t* image_;
+
+      void CopyChannel(Orthanc::ImageAccessor& target,
+                       unsigned int channel,
+                       unsigned int targetIncrement)
+      {
+        int32_t* q = image_->comps[channel].data;
+        assert(q != NULL);
+
+        for (unsigned int y = 0; y < target.GetHeight(); y++)
+        {
+          uint8_t *p = reinterpret_cast<uint8_t*>(target.GetRow(y)) + channel;
+
+          for (unsigned int x = 0; x < target.GetWidth(); x++, p += targetIncrement)
+          {
+            *p = *q;
+            q++;
+          }
+        }        
+      }
+
+    public:
+      OpenJpegImage(OpenJpegDecoder& decoder,
+                    OpenJpegInput& input) :
+        image_(NULL)
+      {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+        image_ = opj_decode(decoder.GetObject(), input.GetObject());
+        if (image_ == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+#else
+        if (!opj_read_header(input.GetObject(), decoder.GetObject(), &image_) ||
+            image_ == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        if (!opj_set_decode_area(decoder.GetObject(), image_, 
+                                 static_cast<int32_t>(decoder.GetParameters().DA_x0),
+                                 static_cast<int32_t>(decoder.GetParameters().DA_y0),
+                                 static_cast<int32_t>(decoder.GetParameters().DA_x1),
+                                 static_cast<int32_t>(decoder.GetParameters().DA_y1)) ||  // Decode the whole image
+            !opj_decode(decoder.GetObject(), input.GetObject(), image_) ||
+            !opj_end_decompress(decoder.GetObject(), input.GetObject()))
+        {
+          opj_image_destroy(image_);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+#endif
+      }
+
+      ~OpenJpegImage()
+      {
+        if (image_ != NULL)
+        {
+          opj_image_destroy(image_);
+          image_ = NULL;
+        }
+      }
+
+      Orthanc::ImageAccessor* ProvideImage()
+      {
+        if (image_->x1 < 0 ||
+            image_->y1 < 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        if (image_->x0 != 0 ||
+            image_->y0 != 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+
+        for (unsigned int c = 0; c < static_cast<unsigned int>(image_->numcomps); c++)
+        {
+          if (image_->comps[c].dx != 1 ||
+              image_->comps[c].dy != 1 ||
+              image_->comps[c].x0 != 0 ||
+              image_->comps[c].y0 != 0 ||
+              image_->comps[c].w != image_->x1 ||
+              image_->comps[c].h != image_->y1 ||
+              image_->comps[c].prec != 8 ||
+              image_->comps[c].sgnd != 0)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          }
+        }
+
+        unsigned int width = static_cast<unsigned int>(image_->x1);
+        unsigned int height = static_cast<unsigned int>(image_->y1);
+
+        Orthanc::PixelFormat format;
+        if (image_->numcomps == 1 && image_->color_space != OPJ_CLRSPC_GRAY)
+        {
+          format = Orthanc::PixelFormat_Grayscale8;
+        }
+        else if (image_->numcomps == 3 && image_->color_space != OPJ_CLRSPC_SRGB)
+        {
+          format = Orthanc::PixelFormat_RGB24;
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+
+        std::auto_ptr<Orthanc::ImageAccessor> image(ImageToolbox::Allocate(format, width, height));
+        
+        switch (format)
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+          {
+            CopyChannel(*image, 0, 1);
+            break;
+          }
+
+          case Orthanc::PixelFormat_RGB24:
+          {
+            CopyChannel(*image, 0, 3);
+            CopyChannel(*image, 1, 3);
+            CopyChannel(*image, 2, 3);
+            break;
+          }
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        return image.release();
+      }
+
+    };
+  }
+
+
+  void Jpeg2000Reader::ReadFromMemory(const void* buffer,
+                                      size_t size)
+  {
+    OpenJpegDecoder decoder(DetectFormatFromMemory(buffer, size));
+    OpenJpegInput input(decoder, buffer, size);
+    OpenJpegImage image(decoder, input);
+    
+    image_.reset(image.ProvideImage());
+    AssignReadOnly(image_->GetFormat(), 
+                   image_->GetWidth(),
+                   image_->GetHeight(), 
+                   image_->GetPitch(), 
+                   image_->GetConstBuffer());
+  }
+
+
+  void Jpeg2000Reader::ReadFromMemory(const std::string& buffer)
+  {
+    if (buffer.empty())
+    {
+      ReadFromMemory(NULL, 0);
+    }
+    else
+    {
+      ReadFromMemory(buffer.c_str(), buffer.size());
+    }
+  }
+
+  void Jpeg2000Reader::ReadFromFile(const std::string& filename)
+  {
+    // TODO Use opj_stream_create_file_stream() ?
+
+    std::string content;
+    Orthanc::Toolbox::ReadFile(content, filename);
+  }
+
+
+  Jpeg2000Format Jpeg2000Reader::DetectFormatFromMemory(const void* buffer,
+                                                        size_t size)
+  {
+    static const char JP2_RFC3745_HEADER[] = "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a";
+    static const char JP2_HEADER[] = "\x0d\x0a\x87\x0a";
+    static const char J2K_HEADER[] = "\xff\x4f\xff\x51";
+
+    if (size < sizeof(JP2_RFC3745_HEADER) - 1)
+    {
+      return Jpeg2000Format_Unknown;
+    }
+     
+    if (memcmp(buffer, JP2_RFC3745_HEADER, sizeof(JP2_RFC3745_HEADER) - 1) == 0 || 
+        memcmp(buffer, JP2_HEADER, sizeof(JP2_HEADER) - 1) == 0)
+    {
+      return Jpeg2000Format_JP2;
+    }
+    else if (memcmp(buffer, J2K_HEADER, sizeof(J2K_HEADER) - 1) == 0)
+    {
+      return Jpeg2000Format_J2K;
+    }
+    
+    return Jpeg2000Format_Unknown;
+  }
+}
diff --git a/Framework/Jpeg2000Reader.h b/Framework/Jpeg2000Reader.h
new file mode 100644
index 0000000..dd59358
--- /dev/null
+++ b/Framework/Jpeg2000Reader.h
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Orthanc/Core/Images/Image.h"
+#include <memory>
+
+namespace OrthancWSI
+{
+  enum Jpeg2000Format
+  {
+    Jpeg2000Format_J2K,
+    Jpeg2000Format_JP2,
+    Jpeg2000Format_Unknown
+  };
+
+  class Jpeg2000Reader : 
+    public Orthanc::ImageAccessor,
+    public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<Orthanc::ImageAccessor> image_;
+
+  public:
+    void ReadFromFile(const std::string& filename);
+
+    void ReadFromMemory(const void* buffer,
+                        size_t size);
+
+    void ReadFromMemory(const std::string& buffer);
+
+    static Jpeg2000Format DetectFormatFromMemory(const void* buffer,
+                                                 size_t size);
+  };
+}
diff --git a/Framework/Jpeg2000Writer.cpp b/Framework/Jpeg2000Writer.cpp
new file mode 100644
index 0000000..2b0a977
--- /dev/null
+++ b/Framework/Jpeg2000Writer.cpp
@@ -0,0 +1,365 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
+#include "Jpeg2000Writer.h"
+
+#include "Orthanc/Core/ChunkedBuffer.h"
+#include "Orthanc/Core/OrthancException.h"
+
+#include <openjpeg.h>
+#include <string.h>
+#include <vector>
+
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+#  define OPJ_CLRSPC_GRAY CLRSPC_GRAY
+#  define OPJ_CLRSPC_SRGB CLRSPC_SRGB
+#  define OPJ_CODEC_J2K   CODEC_J2K
+typedef opj_cinfo_t opj_codec_t;
+typedef opj_cio_t opj_stream_t;
+#elif ORTHANC_OPENJPEG_MAJOR_VERSION == 2
+#else
+#error Unsupported version of OpenJpeg
+#endif
+
+namespace OrthancWSI
+{
+  namespace
+  {
+    class OpenJpegImage : public boost::noncopyable
+    {
+    private:
+      std::vector<opj_image_cmptparm_t> components_;
+      COLOR_SPACE colorspace_;
+      opj_image_t* image_;
+
+      void SetupComponents(unsigned int width,
+                           unsigned int height,
+                           Orthanc::PixelFormat format)
+      {
+        switch (format)
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+            colorspace_ = OPJ_CLRSPC_GRAY;
+            components_.resize(1);
+            break;
+
+          case Orthanc::PixelFormat_RGB24:
+            colorspace_ = OPJ_CLRSPC_SRGB;
+            components_.resize(3);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        for (size_t i = 0; i < components_.size(); i++)
+        {
+          memset(&components_[i], 0, sizeof(opj_image_cmptparm_t));
+          components_[i].dx = 1;
+          components_[i].dy = 1;
+          components_[i].x0 = 0;
+          components_[i].y0 = 0;
+          components_[i].w = width;
+          components_[i].h = height;
+          components_[i].prec = 8;
+          components_[i].bpp = 8;
+          components_[i].sgnd = 0;
+        }
+      }
+
+      void CopyRGB24(unsigned int width,
+                     unsigned int height,
+                     unsigned int pitch,
+                     const void* buffer)
+      {
+        int32_t* r = image_->comps[0].data;
+        int32_t* g = image_->comps[1].data;
+        int32_t* b = image_->comps[2].data;
+
+        for (unsigned int y = 0; y < height; y++)
+        {
+          const uint8_t *p = reinterpret_cast<const uint8_t*>(buffer) + y * pitch;
+
+          for (unsigned int x = 0; x < width; x++)
+          {
+            *r = p[0];
+            *g = p[1];
+            *b = p[2];
+            p += 3;
+            r++;
+            g++;
+            b++;
+          }
+        }
+      }
+
+      void CopyGrayscale8(unsigned int width,
+                          unsigned int height,
+                          unsigned int pitch,
+                          const void* buffer)
+      {
+        int32_t* q = image_->comps[0].data;
+
+        for (unsigned int y = 0; y < height; y++)
+        {
+          const uint8_t *p = reinterpret_cast<const uint8_t*>(buffer) + y * pitch;
+
+          for (unsigned int x = 0; x < width; x++)
+          {
+            *q = *p;
+            p++;
+            q++;
+          }
+        }        
+      }
+
+    public:
+      OpenJpegImage(unsigned int width,
+                    unsigned int height,
+                    unsigned int pitch,
+                    Orthanc::PixelFormat format,
+                    const void* buffer) : image_(NULL)
+      {
+        SetupComponents(width, height, format);
+
+        image_ = opj_image_create(components_.size(), &components_[0], colorspace_);
+        if (!image_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        image_->x0 = 0;
+        image_->y0 = 0;
+        image_->x1 = width;
+        image_->y1 = height;
+
+        switch (format)
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+            CopyGrayscale8(width, height, pitch, buffer);
+            break;
+
+          case Orthanc::PixelFormat_RGB24:
+            CopyRGB24(width, height, pitch, buffer);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+      }
+
+
+      ~OpenJpegImage()
+      {
+        if (image_)
+        {
+          opj_image_destroy(image_);
+          image_ = NULL;
+        }
+      }
+
+
+      opj_image_t* GetObject()
+      {
+        return image_;
+      }
+    };
+
+
+    class OpenJpegEncoder : public boost::noncopyable
+    {
+    private:
+      opj_codec_t* cinfo_;
+
+    public:
+      OpenJpegEncoder(opj_cparameters_t& parameters,
+                      OpenJpegImage& image) : cinfo_(NULL)
+      {
+        cinfo_ = opj_create_compress(OPJ_CODEC_J2K);
+        if (!cinfo_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        opj_setup_encoder(cinfo_, &parameters, image.GetObject());
+      }
+
+      ~OpenJpegEncoder()
+      {
+        if (cinfo_ != NULL)
+        {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+          opj_destroy_compress(cinfo_);
+#else
+          opj_destroy_codec(cinfo_);
+#endif
+          cinfo_ = NULL;
+        }
+      } 
+
+      opj_codec_t* GetObject()
+      {
+        return cinfo_;
+      }
+    };
+
+
+    class OpenJpegOutput : public boost::noncopyable
+    {
+    private:
+      opj_stream_t* cio_;
+
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 2
+      Orthanc::ChunkedBuffer buffer_;
+
+      static void Free(void *userData)
+      {
+      }
+
+      static OPJ_SIZE_T Write(void *buffer, 
+                              OPJ_SIZE_T size,
+                              void *userData)
+      {
+        OpenJpegOutput* that = reinterpret_cast<OpenJpegOutput*>(userData);
+        that->buffer_.AddChunk(reinterpret_cast<const char*>(buffer), size);
+        return size;
+      }
+#endif
+
+    public:
+      OpenJpegOutput(OpenJpegEncoder& encoder) : cio_(NULL)
+      {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+        cio_ = opj_cio_open(reinterpret_cast<opj_common_ptr>(encoder.GetObject()), NULL, 0);
+#else
+        cio_ = opj_stream_default_create(0 /* output stream */);
+        if (!cio_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        opj_stream_set_user_data(cio_, this, Free);
+        opj_stream_set_write_function(cio_, Write);
+#endif
+      }
+
+      ~OpenJpegOutput()
+      {
+        if (cio_)
+        {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+          opj_cio_close(cio_);
+#else
+          opj_stream_destroy(cio_);
+#endif
+          cio_ = NULL;
+        }
+      }
+
+      opj_stream_t* GetObject()
+      {
+        return cio_;
+      }
+
+      void Flatten(std::string& target)
+      {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+          target.assign(reinterpret_cast<const char*>(cio_->buffer), cio_tell(cio_));
+#else
+          buffer_.Flatten(target);
+#endif
+      }
+    };
+  }
+
+
+  static void SetupParameters(opj_cparameters_t& parameters,
+                              Orthanc::PixelFormat format,
+                              bool isLossless)
+  {
+    opj_set_default_encoder_parameters(&parameters);
+    parameters.cp_disto_alloc = 1;
+
+    if (isLossless)
+    {
+      parameters.tcp_numlayers = 1;
+      parameters.tcp_rates[0] = 0;
+    }
+    else
+    {
+      parameters.tcp_numlayers = 5;
+      parameters.tcp_rates[0] = 1920;
+      parameters.tcp_rates[1] = 480;
+      parameters.tcp_rates[2] = 120;
+      parameters.tcp_rates[3] = 30;
+      parameters.tcp_rates[4] = 10;
+      parameters.irreversible = 1;
+
+      if (format == Orthanc::PixelFormat_RGB24 ||
+          format == Orthanc::PixelFormat_RGBA32)
+      {
+        // This must be set to 1 if the number of color channels is >= 3
+        parameters.tcp_mct = 1;
+      }
+    }
+
+    parameters.cp_comment = const_cast<char*>("");
+  }
+
+
+  void Jpeg2000Writer::WriteToMemoryInternal(std::string& compressed,
+                                             unsigned int width,
+                                             unsigned int height,
+                                             unsigned int pitch,
+                                             Orthanc::PixelFormat format,
+                                             const void* buffer)
+  {
+    if (format != Orthanc::PixelFormat_Grayscale8 &&
+        format != Orthanc::PixelFormat_RGB24 &&
+        format != Orthanc::PixelFormat_RGBA32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    opj_cparameters_t parameters;
+    SetupParameters(parameters, format, isLossless_);
+
+    OpenJpegImage image(width, height, pitch, format, buffer);
+    OpenJpegEncoder encoder(parameters, image);
+    OpenJpegOutput output(encoder);
+
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+    if (!opj_encode(encoder.GetObject(), output.GetObject(), image.GetObject(), parameters.index))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+#else
+    if (!opj_start_compress(encoder.GetObject(), image.GetObject(), output.GetObject()) ||
+        !opj_encode(encoder.GetObject(), output.GetObject()) ||
+        !opj_end_compress(encoder.GetObject(), output.GetObject()))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+#endif
+
+    output.Flatten(compressed);
+  }
+}
diff --git a/Framework/Jpeg2000Writer.h b/Framework/Jpeg2000Writer.h
new file mode 100644
index 0000000..268a9b3
--- /dev/null
+++ b/Framework/Jpeg2000Writer.h
@@ -0,0 +1,55 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Orthanc/Core/Images/IImageWriter.h"
+
+namespace OrthancWSI
+{
+  class Jpeg2000Writer : public Orthanc::IImageWriter
+  {
+  protected:
+    virtual void WriteToMemoryInternal(std::string& compressed,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       Orthanc::PixelFormat format,
+                                       const void* buffer);
+
+  private:
+    bool  isLossless_;
+
+  public:
+    Jpeg2000Writer() : isLossless_(true)
+    {
+    }
+
+    void SetLossless(bool isLossless)
+    {
+      isLossless_ = isLossless;
+    }
+
+    bool IsLossless() const
+    {
+      return isLossless_;
+    }
+  };
+}
diff --git a/Framework/Messaging/CurlOrthancConnection.cpp b/Framework/Messaging/CurlOrthancConnection.cpp
new file mode 100644
index 0000000..55c669f
--- /dev/null
+++ b/Framework/Messaging/CurlOrthancConnection.cpp
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "CurlOrthancConnection.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancWSI
+{
+  void CurlOrthancConnection::ApplyGet(std::string& result,
+                                       const std::string& uri)
+  {
+    Orthanc::HttpClient client(parameters_, uri);
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client.SetRedirectionFollowed(false);  
+   
+    if (!client.Apply(result))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+  void CurlOrthancConnection::ApplyPost(std::string& result,
+                                        const std::string& uri,
+                                        const std::string& body)
+  {
+    Orthanc::HttpClient client(parameters_, uri);
+
+    client.SetMethod(Orthanc::HttpMethod_Post);
+    client.SetBody(body);
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client.SetRedirectionFollowed(false);  
+   
+    if (!client.Apply(result))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+}
diff --git a/Framework/Messaging/CurlOrthancConnection.h b/Framework/Messaging/CurlOrthancConnection.h
new file mode 100644
index 0000000..e9333a0
--- /dev/null
+++ b/Framework/Messaging/CurlOrthancConnection.h
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OrthancConnectionBase.h"
+
+#include "../Orthanc/Core/HttpClient.h"
+
+namespace OrthancWSI
+{
+  class CurlOrthancConnection : public OrthancConnectionBase
+  {
+  private:
+    Orthanc::WebServiceParameters  parameters_;
+
+  protected:
+    virtual void ApplyGet(std::string& result,
+                          const std::string& uri);
+
+    virtual void ApplyPost(std::string& result,
+                           const std::string& uri,
+                           const std::string& body);
+
+  public:
+    CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) :
+      parameters_(parameters)
+    {
+    }
+
+    const Orthanc::WebServiceParameters& GetParameters() const
+    {
+      return parameters_;
+    }
+  };
+}
diff --git a/Framework/Messaging/FolderTarget.cpp b/Framework/Messaging/FolderTarget.cpp
new file mode 100644
index 0000000..a293a9c
--- /dev/null
+++ b/Framework/Messaging/FolderTarget.cpp
@@ -0,0 +1,45 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "FolderTarget.h"
+
+#include "../Orthanc/Core/Toolbox.h"
+#include "../Orthanc/Core/Logging.h"
+
+#include <stdio.h>
+
+namespace OrthancWSI
+{
+  void FolderTarget::Write(const std::string& file)
+  {
+    std::string path;
+    path.resize(pattern_.size() + 16);
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      sprintf(&path[0], pattern_.c_str(), count_);
+      count_ += 1;
+    }
+
+    LOG(INFO) << "Writing file " << path;
+    Orthanc::Toolbox::WriteFile(file, path);
+  }
+}
diff --git a/Framework/Messaging/FolderTarget.h b/Framework/Messaging/FolderTarget.h
new file mode 100644
index 0000000..7feba07
--- /dev/null
+++ b/Framework/Messaging/FolderTarget.h
@@ -0,0 +1,45 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IFileTarget.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancWSI
+{
+  class FolderTarget : public IFileTarget
+  {
+  private:
+    boost::mutex  mutex_;
+    unsigned int  count_;
+    std::string   pattern_;
+
+  public:
+    FolderTarget(const std::string& pattern) : 
+      count_(0),
+      pattern_(pattern)
+    {
+    }
+
+    virtual void Write(const std::string& file);
+  };
+}
diff --git a/Framework/Messaging/IFileTarget.h b/Framework/Messaging/IFileTarget.h
new file mode 100644
index 0000000..cee7b35
--- /dev/null
+++ b/Framework/Messaging/IFileTarget.h
@@ -0,0 +1,37 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+#include <string>
+
+namespace OrthancWSI
+{
+  class IFileTarget : public boost::noncopyable
+  {
+  public:
+    virtual ~IFileTarget()
+    {
+    }
+
+    virtual void Write(const std::string& file) = 0;
+  };
+}
diff --git a/Framework/Messaging/IOrthancConnection.cpp b/Framework/Messaging/IOrthancConnection.cpp
new file mode 100644
index 0000000..d414cfa
--- /dev/null
+++ b/Framework/Messaging/IOrthancConnection.cpp
@@ -0,0 +1,63 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "IOrthancConnection.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+#include <json/reader.h>
+
+
+namespace OrthancWSI
+{
+  void IOrthancConnection::RestApiGet(Json::Value& result,
+                                      IOrthancConnection& orthanc,
+                                      const std::string& uri)
+  {
+    std::string content;
+    orthanc.RestApiGet(content, uri);
+
+    Json::Reader reader;
+    if (!reader.parse(content, result))
+    {
+      LOG(ERROR) << "Cannot parse a JSON file";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void IOrthancConnection::RestApiPost(Json::Value& result,
+                                       IOrthancConnection& orthanc,
+                                       const std::string& uri,
+                                       const std::string& body)
+  {
+    std::string content;
+    orthanc.RestApiPost(content, uri, body);
+
+    Json::Reader reader;
+    if (!reader.parse(content, result))
+    {
+      LOG(ERROR) << "Cannot parse a JSON file";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+}
diff --git a/Framework/Messaging/IOrthancConnection.h b/Framework/Messaging/IOrthancConnection.h
new file mode 100644
index 0000000..8b9fc84
--- /dev/null
+++ b/Framework/Messaging/IOrthancConnection.h
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+#include <json/value.h>
+
+namespace OrthancWSI
+{
+  // Derived classes must be thread-safe
+  class IOrthancConnection : public boost::noncopyable
+  {
+  public:
+    virtual ~IOrthancConnection()
+    {
+    }
+
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri) = 0;
+
+    virtual void RestApiPost(std::string& result, 
+                             const std::string& uri,
+                             const std::string& body) = 0;
+
+    static void RestApiGet(Json::Value& result,
+                           IOrthancConnection& orthanc,
+                           const std::string& uri);
+
+    static void RestApiPost(Json::Value& result,
+                            IOrthancConnection& orthanc,
+                            const std::string& uri,
+                            const std::string& body);
+  };
+}
diff --git a/Framework/Messaging/OrthancConnectionBase.cpp b/Framework/Messaging/OrthancConnectionBase.cpp
new file mode 100644
index 0000000..35fdb20
--- /dev/null
+++ b/Framework/Messaging/OrthancConnectionBase.cpp
@@ -0,0 +1,41 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "OrthancConnectionBase.h"
+
+namespace OrthancWSI
+{
+  void OrthancConnectionBase::RestApiGet(std::string& result,
+                                         const std::string& uri)
+  {
+    boost::mutex::scoped_lock  lock_;
+    ApplyGet(result, uri);
+  }
+
+
+  void OrthancConnectionBase::RestApiPost(std::string& result,
+                                          const std::string& uri,
+                                          const std::string& body)
+  {
+    boost::mutex::scoped_lock  lock_;
+    ApplyPost(result, uri, body);
+  }
+}
diff --git a/Framework/Messaging/OrthancConnectionBase.h b/Framework/Messaging/OrthancConnectionBase.h
new file mode 100644
index 0000000..0a71373
--- /dev/null
+++ b/Framework/Messaging/OrthancConnectionBase.h
@@ -0,0 +1,51 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IOrthancConnection.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancWSI
+{
+  class OrthancConnectionBase : public IOrthancConnection
+  {
+  private:
+    boost::mutex  mutex_;
+
+  protected:
+    // Will be invoked in mutual exclusion
+    virtual void ApplyGet(std::string& result,
+                          const std::string& uri) = 0;
+
+    virtual void ApplyPost(std::string& result,
+                           const std::string& uri,
+                           const std::string& body) = 0;
+
+  public:
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri);
+
+    virtual void RestApiPost(std::string& result, 
+                             const std::string& uri,
+                             const std::string& body);
+  };
+}
diff --git a/Framework/Messaging/OrthancTarget.cpp b/Framework/Messaging/OrthancTarget.cpp
new file mode 100644
index 0000000..68e4295
--- /dev/null
+++ b/Framework/Messaging/OrthancTarget.cpp
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "OrthancTarget.h"
+
+#include "CurlOrthancConnection.h"
+#include "../DicomToolbox.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Logging.h"
+
+namespace OrthancWSI
+{
+  OrthancTarget::OrthancTarget(const Orthanc::WebServiceParameters& parameters) :
+    orthanc_(new CurlOrthancConnection(parameters)),
+    first_(true)
+  {
+  }
+
+
+  void OrthancTarget::Write(const std::string& file)
+  {
+    Json::Value result;
+    IOrthancConnection::RestApiPost(result, *orthanc_, "/instances", file);
+
+    std::string instanceId = DicomToolbox::GetMandatoryStringTag(result, "ID");
+
+    if (first_)
+    {
+      Json::Value instance;
+      IOrthancConnection::RestApiGet(instance, *orthanc_, "/instances/" + instanceId);
+
+      std::string seriesId = DicomToolbox::GetMandatoryStringTag(instance, "ParentSeries");
+
+      LOG(WARNING) << "ID of the whole-slide image series in Orthanc: " << seriesId;
+      first_ = false;
+    }
+
+    LOG(INFO) << "New instance was added to Orthanc: " << instanceId;
+  }
+}
diff --git a/Framework/Messaging/OrthancTarget.h b/Framework/Messaging/OrthancTarget.h
new file mode 100644
index 0000000..cc81671
--- /dev/null
+++ b/Framework/Messaging/OrthancTarget.h
@@ -0,0 +1,48 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IFileTarget.h"
+#include "IOrthancConnection.h"
+#include "../Orthanc/Core/WebServiceParameters.h"
+
+#include <memory>
+
+namespace OrthancWSI
+{
+  class OrthancTarget : public IFileTarget
+  {
+  private:
+    std::auto_ptr<IOrthancConnection>  orthanc_;
+    bool                               first_;
+
+  public:
+    OrthancTarget(const Orthanc::WebServiceParameters& parameters);
+
+    OrthancTarget(IOrthancConnection* orthanc) :   // Takes ownership
+      orthanc_(orthanc),
+      first_(true)
+    {
+    }
+
+    virtual void Write(const std::string& file);
+  };
+}
diff --git a/Framework/Messaging/PluginOrthancConnection.cpp b/Framework/Messaging/PluginOrthancConnection.cpp
new file mode 100644
index 0000000..262cfab
--- /dev/null
+++ b/Framework/Messaging/PluginOrthancConnection.cpp
@@ -0,0 +1,133 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "PluginOrthancConnection.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancWSI
+{
+  class PluginOrthancConnection::MemoryBuffer : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*      context_;
+    OrthancPluginMemoryBuffer  buffer_;
+
+    void Clear()
+    {
+      if (buffer_.data != NULL)
+      {
+        OrthancPluginFreeMemoryBuffer(context_, &buffer_);
+        buffer_.data = NULL;
+        buffer_.size = 0;
+      }
+    }
+
+
+  public:
+    MemoryBuffer(OrthancPluginContext* context) : 
+      context_(context)
+    {
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+
+    ~MemoryBuffer()
+    {
+      Clear();
+    }
+
+    void RestApiGet(const std::string& uri)
+    {
+      Clear();
+
+      OrthancPluginErrorCode error = OrthancPluginRestApiGet(context_, &buffer_, uri.c_str());
+
+      if (error == OrthancPluginErrorCode_Success)
+      {
+        // OK, success
+      }
+      else if (error == OrthancPluginErrorCode_UnknownResource ||
+               error == OrthancPluginErrorCode_InexistentItem)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+      }
+    }
+
+    void RestApiPost(const std::string& uri,
+                     const std::string& body)
+    {
+      Clear();
+
+      OrthancPluginErrorCode error = OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body.c_str(), body.size());
+
+      if (error == OrthancPluginErrorCode_Success)
+      {
+        // OK, success
+      }
+      else if (error == OrthancPluginErrorCode_UnknownResource ||
+               error == OrthancPluginErrorCode_InexistentItem)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+      }
+    }
+
+    void ToString(std::string& target) const
+    {
+      if (buffer_.size == 0)
+      {
+        target.clear();
+      }
+      else
+      {
+        target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
+      }
+    }
+  };
+
+
+
+  void PluginOrthancConnection::ApplyGet(std::string& result,
+                                         const std::string& uri)
+  {
+    MemoryBuffer buffer(context_);
+    buffer.RestApiGet(uri);
+    buffer.ToString(result);
+  }
+
+    
+  void PluginOrthancConnection::ApplyPost(std::string& result,
+                                          const std::string& uri,
+                                          const std::string& body)
+  {
+    MemoryBuffer buffer(context_);
+    buffer.RestApiPost(uri, body);
+    buffer.ToString(result);
+  }
+}
diff --git a/Framework/Messaging/PluginOrthancConnection.h b/Framework/Messaging/PluginOrthancConnection.h
new file mode 100644
index 0000000..d2bdf43
--- /dev/null
+++ b/Framework/Messaging/PluginOrthancConnection.h
@@ -0,0 +1,50 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OrthancConnectionBase.h"
+
+#include <orthanc/OrthancCPlugin.h>
+
+namespace OrthancWSI
+{
+  class PluginOrthancConnection : public OrthancConnectionBase
+  {
+  private:
+    class MemoryBuffer;
+
+    OrthancPluginContext*   context_;
+
+  protected:
+    virtual void ApplyGet(std::string& result,
+                          const std::string& uri);
+
+    virtual void ApplyPost(std::string& result,
+                           const std::string& uri,
+                           const std::string& body);
+
+  public:
+    PluginOrthancConnection(OrthancPluginContext* context) :
+      context_(context)
+    {
+    }
+  };
+}
diff --git a/Framework/Orthanc/Core/ChunkedBuffer.cpp b/Framework/Orthanc/Core/ChunkedBuffer.cpp
new file mode 100644
index 0000000..5d2c2c8
--- /dev/null
+++ b/Framework/Orthanc/Core/ChunkedBuffer.cpp
@@ -0,0 +1,102 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "ChunkedBuffer.h"
+
+#include <cassert>
+#include <string.h>
+
+
+namespace Orthanc
+{
+  void ChunkedBuffer::Clear()
+  {
+    numBytes_ = 0;
+
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void ChunkedBuffer::AddChunk(const void* chunkData,
+                               size_t chunkSize)
+  {
+    if (chunkSize == 0)
+    {
+      return;
+    }
+    else
+    {
+      assert(chunkData != NULL);
+      chunks_.push_back(new std::string(reinterpret_cast<const char*>(chunkData), chunkSize));
+      numBytes_ += chunkSize;
+    }
+  }
+
+
+  void ChunkedBuffer::AddChunk(const std::string& chunk)
+  {
+    if (chunk.size() > 0)
+    {
+      AddChunk(&chunk[0], chunk.size());
+    }
+  }
+
+
+  void ChunkedBuffer::Flatten(std::string& result)
+  {
+    result.resize(numBytes_);
+
+    size_t pos = 0;
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      assert(*it != NULL);
+
+      size_t s = (*it)->size();
+      if (s != 0)
+      {
+        memcpy(&result[pos], (*it)->c_str(), s);
+        pos += s;
+      }
+
+      delete *it;
+    }
+
+    chunks_.clear();
+    numBytes_ = 0;
+  }
+}
diff --git a/Framework/Orthanc/Core/ChunkedBuffer.h b/Framework/Orthanc/Core/ChunkedBuffer.h
new file mode 100644
index 0000000..552c1ec
--- /dev/null
+++ b/Framework/Orthanc/Core/ChunkedBuffer.h
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <list>
+#include <string>
+
+namespace Orthanc
+{
+  class ChunkedBuffer
+  {
+  private:
+    typedef std::list<std::string*>  Chunks;
+    size_t numBytes_;
+    Chunks chunks_;
+  
+    void Clear();
+
+  public:
+    ChunkedBuffer() : numBytes_(0)
+    {
+    }
+
+    ~ChunkedBuffer()
+    {
+      Clear();
+    }
+
+    size_t GetNumBytes() const
+    {
+      return numBytes_;
+    }
+
+    void AddChunk(const void* chunkData,
+                  size_t chunkSize);
+
+    void AddChunk(const std::string& chunk);
+
+    void Flatten(std::string& result);
+  };
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.cpp b/Framework/Orthanc/Core/DicomFormat/DicomArray.cpp
new file mode 100644
index 0000000..4900419
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomArray.cpp
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomArray.h"
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  DicomArray::DicomArray(const DicomMap& map)
+  {
+    elements_.reserve(map.map_.size());
+    
+    for (DicomMap::Map::const_iterator it = 
+           map.map_.begin(); it != map.map_.end(); ++it)
+    {
+      elements_.push_back(new DicomElement(it->first, *it->second));
+    }
+  }
+
+
+  DicomArray::~DicomArray()
+  {
+    for (size_t i = 0; i < elements_.size(); i++)
+    {
+      delete elements_[i];
+    }
+  }
+
+
+  void DicomArray::Print(FILE* fp) const
+  {
+    for (size_t  i = 0; i < elements_.size(); i++)
+    {
+      DicomTag t = elements_[i]->GetTag();
+      const DicomValue& v = elements_[i]->GetValue();
+      std::string s = v.IsNull() ? "(null)" : v.GetContent();
+      printf("0x%04x 0x%04x [%s]\n", t.GetGroup(), t.GetElement(), s.c_str());
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.h b/Framework/Orthanc/Core/DicomFormat/DicomArray.h
new file mode 100644
index 0000000..a223685
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomArray.h
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomElement.h"
+#include "DicomMap.h"
+
+#include <vector>
+
+namespace Orthanc
+{
+  class DicomArray : public boost::noncopyable
+  {
+  private:
+    typedef std::vector<DicomElement*>  Elements;
+
+    Elements  elements_;
+
+  public:
+    DicomArray(const DicomMap& map);
+
+    ~DicomArray();
+
+    size_t GetSize() const
+    {
+      return elements_.size();
+    }
+
+    const DicomElement& GetElement(size_t i) const
+    {
+      return *elements_[i];
+    }
+
+    void Print(FILE* fp) const;
+  };
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomElement.h b/Framework/Orthanc/Core/DicomFormat/DicomElement.h
new file mode 100644
index 0000000..c6f47e5
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomElement.h
@@ -0,0 +1,92 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomValue.h"
+#include "DicomTag.h"
+
+namespace Orthanc
+{
+  class DicomElement : public boost::noncopyable
+  {
+  private:
+    DicomTag tag_;
+    DicomValue* value_;
+
+  public:
+    DicomElement(uint16_t group,
+                 uint16_t element,
+                 const DicomValue& value) :
+      tag_(group, element),
+      value_(value.Clone())
+    {
+    }
+
+    DicomElement(const DicomTag& tag,
+                 const DicomValue& value) :
+      tag_(tag),
+      value_(value.Clone())
+    {
+    }
+
+    ~DicomElement()
+    {
+      delete value_;
+    }
+
+    const DicomTag& GetTag() const
+    {
+      return tag_;
+    }
+
+    const DicomValue& GetValue() const
+    {
+      return *value_;
+    }
+
+    uint16_t GetTagGroup() const
+    {
+      return tag_.GetGroup();
+    }
+
+    uint16_t GetTagElement() const
+    {
+      return tag_.GetElement();
+    }
+
+    bool operator< (const DicomElement& other) const
+    {
+      return GetTag() < other.GetTag();
+    }
+  };
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomMap.cpp b/Framework/Orthanc/Core/DicomFormat/DicomMap.cpp
new file mode 100644
index 0000000..cd72d09
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomMap.cpp
@@ -0,0 +1,783 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomMap.h"
+
+#include <stdio.h>
+#include <memory>
+#include "../Endianness.h"
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  static DicomTag patientTags[] =
+  {
+    //DicomTag(0x0010, 0x1010), // PatientAge
+    //DicomTag(0x0010, 0x1040)  // PatientAddress
+    DicomTag(0x0010, 0x0010),   // PatientName
+    DicomTag(0x0010, 0x0030),   // PatientBirthDate
+    DicomTag(0x0010, 0x0040),   // PatientSex
+    DicomTag(0x0010, 0x1000),   // OtherPatientIDs
+    DICOM_TAG_PATIENT_ID
+  };
+
+  static DicomTag studyTags[] =
+  {
+    //DicomTag(0x0010, 0x1020), // PatientSize
+    //DicomTag(0x0010, 0x1030)  // PatientWeight
+    DICOM_TAG_STUDY_DATE,
+    DicomTag(0x0008, 0x0030),   // StudyTime
+    DicomTag(0x0020, 0x0010),   // StudyID
+    DICOM_TAG_STUDY_DESCRIPTION,
+    DICOM_TAG_ACCESSION_NUMBER,
+    DICOM_TAG_STUDY_INSTANCE_UID,
+    DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION,   // New in db v6
+    DICOM_TAG_INSTITUTION_NAME,                  // New in db v6
+    DICOM_TAG_REQUESTING_PHYSICIAN,              // New in db v6
+    DICOM_TAG_REFERRING_PHYSICIAN_NAME           // New in db v6
+  };
+
+  static DicomTag seriesTags[] =
+  {
+    //DicomTag(0x0010, 0x1080), // MilitaryRank
+    DicomTag(0x0008, 0x0021),   // SeriesDate
+    DicomTag(0x0008, 0x0031),   // SeriesTime
+    DICOM_TAG_MODALITY,
+    DicomTag(0x0008, 0x0070),   // Manufacturer
+    DicomTag(0x0008, 0x1010),   // StationName
+    DICOM_TAG_SERIES_DESCRIPTION,
+    DicomTag(0x0018, 0x0015),   // BodyPartExamined
+    DicomTag(0x0018, 0x0024),   // SequenceName
+    DicomTag(0x0018, 0x1030),   // ProtocolName
+    DicomTag(0x0020, 0x0011),   // SeriesNumber
+    DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES,
+    DICOM_TAG_IMAGES_IN_ACQUISITION,
+    DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS,
+    DICOM_TAG_NUMBER_OF_SLICES,
+    DICOM_TAG_NUMBER_OF_TIME_SLICES,
+    DICOM_TAG_SERIES_INSTANCE_UID,
+    DICOM_TAG_IMAGE_ORIENTATION_PATIENT,                  // New in db v6
+    DICOM_TAG_SERIES_TYPE,                                // New in db v6
+    DICOM_TAG_OPERATOR_NAME,                              // New in db v6
+    DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION,       // New in db v6
+    DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION,  // New in db v6
+    DICOM_TAG_CONTRAST_BOLUS_AGENT                        // New in db v6
+  };
+
+  static DicomTag instanceTags[] =
+  {
+    DicomTag(0x0008, 0x0012),   // InstanceCreationDate
+    DicomTag(0x0008, 0x0013),   // InstanceCreationTime
+    DicomTag(0x0020, 0x0012),   // AcquisitionNumber
+    DICOM_TAG_IMAGE_INDEX,
+    DICOM_TAG_INSTANCE_NUMBER,
+    DICOM_TAG_NUMBER_OF_FRAMES,
+    DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
+    DICOM_TAG_SOP_INSTANCE_UID,
+    DICOM_TAG_IMAGE_POSITION_PATIENT,    // New in db v6
+    DICOM_TAG_IMAGE_COMMENTS             // New in db v6
+  };
+
+
+  void DicomMap::LoadMainDicomTags(const DicomTag*& tags,
+                                   size_t& size,
+                                   ResourceType level)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void DicomMap::SetValue(uint16_t group, 
+                          uint16_t element, 
+                          DicomValue* value)
+  {
+    DicomTag tag(group, element);
+    Map::iterator it = map_.find(tag);
+
+    if (it != map_.end())
+    {
+      delete it->second;
+      it->second = value;
+    }
+    else
+    {
+      map_.insert(std::make_pair(tag, value));
+    }
+  }
+
+  void DicomMap::SetValue(DicomTag tag, 
+                          DicomValue* value)
+  {
+    SetValue(tag.GetGroup(), tag.GetElement(), value);
+  }
+
+
+
+
+  void DicomMap::Clear()
+  {
+    for (Map::iterator it = map_.begin(); it != map_.end(); ++it)
+    {
+      delete it->second;
+    }
+
+    map_.clear();
+  }
+
+
+  void DicomMap::ExtractTags(DicomMap& result,
+                             const DicomTag* tags,
+                             size_t count) const
+  {
+    result.Clear();
+
+    for (unsigned int i = 0; i < count; i++)
+    {
+      Map::const_iterator it = map_.find(tags[i]);
+      if (it != map_.end())
+      {
+        result.SetValue(it->first, it->second->Clone());
+      }
+    }
+  }
+
+
+  void DicomMap::ExtractPatientInformation(DicomMap& result) const
+  {
+    ExtractTags(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractStudyInformation(DicomMap& result) const
+  {
+    ExtractTags(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractSeriesInformation(DicomMap& result) const
+  {
+    ExtractTags(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractInstanceInformation(DicomMap& result) const
+  {
+    ExtractTags(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+  }
+
+
+
+  DicomMap* DicomMap::Clone() const
+  {
+    std::auto_ptr<DicomMap> result(new DicomMap);
+
+    for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it)
+    {
+      result->map_.insert(std::make_pair(it->first, it->second->Clone()));
+    }
+
+    return result.release();
+  }
+
+
+  void DicomMap::Assign(const DicomMap& other)
+  {
+    Clear();
+
+    for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it)
+    {
+      map_.insert(std::make_pair(it->first, it->second->Clone()));
+    }
+  }
+
+
+  const DicomValue& DicomMap::GetValue(const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value)
+    {
+      return *value;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InexistentTag);
+    }
+  }
+
+
+  const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const
+  {
+    Map::const_iterator it = map_.find(tag);
+
+    if (it == map_.end())
+    {
+      return NULL;
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  void DicomMap::Remove(const DicomTag& tag) 
+  {
+    Map::iterator it = map_.find(tag);
+    if (it != map_.end())
+    {
+      delete it->second;
+      map_.erase(it);
+    }
+  }
+
+
+  static void SetupFindTemplate(DicomMap& result,
+                                const DicomTag* tags,
+                                size_t count) 
+  {
+    result.Clear();
+
+    for (size_t i = 0; i < count; i++)
+    {
+      result.SetValue(tags[i], "", false);
+    }
+  }
+
+  void DicomMap::SetupFindPatientTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::SetupFindStudyTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+
+    // These main DICOM tags are only indirectly related to the
+    // General Study Module, remove them
+    result.Remove(DICOM_TAG_INSTITUTION_NAME);
+    result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN);
+    result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION);
+  }
+
+  void DicomMap::SetupFindSeriesTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
+
+    // These tags are considered as "main" by Orthanc, but are not in the Series module
+    result.Remove(DicomTag(0x0008, 0x0070));  // Manufacturer
+    result.Remove(DicomTag(0x0008, 0x1010));  // Station name
+    result.Remove(DicomTag(0x0018, 0x0024));  // Sequence name
+    result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES);
+    result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION);
+    result.Remove(DICOM_TAG_NUMBER_OF_SLICES);
+    result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS);
+    result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES);
+    result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
+    result.Remove(DICOM_TAG_SERIES_TYPE);
+    result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION);
+    result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT);
+  }
+
+  void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
+    result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false);
+  }
+
+
+  void DicomMap::CopyTagIfExists(const DicomMap& source,
+                                 const DicomTag& tag)
+  {
+    if (source.HasTag(tag))
+    {
+      SetValue(tag, source.GetValue(tag));
+    }
+  }
+
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level)
+  {
+    DicomTag *tags = NULL;
+    size_t size;
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      if (tags[i] == tag)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag)
+  {
+    return (IsMainDicomTag(tag, ResourceType_Patient) ||
+            IsMainDicomTag(tag, ResourceType_Study) ||
+            IsMainDicomTag(tag, ResourceType_Series) ||
+            IsMainDicomTag(tag, ResourceType_Instance));
+  }
+
+
+  void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level)
+  {
+    DicomTag *tags = NULL;
+    size_t size;
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      result.insert(tags[i]);
+    }
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result, ResourceType level)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, level);
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, ResourceType_Patient);
+    GetMainDicomTagsInternal(result, ResourceType_Study);
+    GetMainDicomTagsInternal(result, ResourceType_Series);
+    GetMainDicomTagsInternal(result, ResourceType_Instance);
+  }
+
+
+  void DicomMap::GetTags(std::set<DicomTag>& tags) const
+  {
+    tags.clear();
+
+    for (Map::const_iterator it = map_.begin();
+         it != map_.end(); ++it)
+    {
+      tags.insert(it->first);
+    }
+  }
+
+
+  static uint16_t ReadUnsignedInteger16(const char* dicom)
+  {
+    return le16toh(*reinterpret_cast<const uint16_t*>(dicom));
+  }
+
+
+  static uint32_t ReadUnsignedInteger32(const char* dicom)
+  {
+    return le32toh(*reinterpret_cast<const uint32_t*>(dicom));
+  }
+
+
+  static bool ValidateTag(const ValueRepresentation& vr,
+                          const std::string& value)
+  {
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:
+        return value.size() <= 16;
+
+      case ValueRepresentation_AgeString:
+        return (value.size() == 4 &&
+                isdigit(value[0]) &&
+                isdigit(value[1]) &&
+                isdigit(value[2]) &&
+                (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y'));
+
+      case ValueRepresentation_AttributeTag:
+        return value.size() == 4;
+
+      case ValueRepresentation_CodeString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_Date:
+        return value.size() <= 18;
+
+      case ValueRepresentation_DecimalString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_DateTime:
+        return value.size() <= 54;
+
+      case ValueRepresentation_FloatingPointSingle:
+        return value.size() == 4;
+
+      case ValueRepresentation_FloatingPointDouble:
+        return value.size() == 8;
+
+      case ValueRepresentation_IntegerString:
+        return value.size() <= 12;
+
+      case ValueRepresentation_LongString:
+        return value.size() <= 64;
+
+      case ValueRepresentation_LongText:
+        return value.size() <= 10240;
+
+      case ValueRepresentation_OtherByte:
+        return true;
+      
+      case ValueRepresentation_OtherDouble:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 8;
+
+      case ValueRepresentation_OtherFloat:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 4;
+
+      case ValueRepresentation_OtherLong:
+        return true;
+
+      case ValueRepresentation_OtherWord:
+        return true;
+
+      case ValueRepresentation_PersonName:
+        return true;
+
+      case ValueRepresentation_ShortString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_SignedLong:
+        return value.size() == 4;
+
+      case ValueRepresentation_Sequence:
+        return true;
+
+      case ValueRepresentation_SignedShort:
+        return value.size() == 2;
+
+      case ValueRepresentation_ShortText:
+        return value.size() <= 1024;
+
+      case ValueRepresentation_Time:
+        return value.size() <= 28;
+
+      case ValueRepresentation_UnlimitedCharacters:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
+
+      case ValueRepresentation_UniqueIdentifier:
+        return value.size() <= 64;
+
+      case ValueRepresentation_UnsignedLong:
+        return value.size() == 4;
+
+      case ValueRepresentation_Unknown:
+        return true;
+
+      case ValueRepresentation_UniversalResource:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
+
+      case ValueRepresentation_UnsignedShort:
+        return value.size() == 2;
+
+      case ValueRepresentation_UnlimitedText:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
+
+      default:
+        // Assume unsupported tags are OK
+        return true;
+    }
+  }
+
+
+  static void RemoveTagPadding(std::string& value,
+                               const ValueRepresentation& vr)
+  {
+    /**
+     * Remove padding from character strings, if need be. For the time
+     * being, only the UI VR is supported.
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+     **/
+
+    switch (vr)
+    {
+      case ValueRepresentation_UniqueIdentifier:
+      {
+        /**
+         * "Values with a VR of UI shall be padded with a single
+         * trailing NULL (00H) character when necessary to achieve even
+         * length."
+         **/
+
+        if (!value.empty() &&
+            value[value.size() - 1] == '\0')
+        {
+          value.resize(value.size() - 1);
+        }
+
+        break;
+      }
+
+      /**
+       * TODO implement other VR
+       **/
+
+      default:
+        // No padding is applicable to this VR
+        break;
+    }
+  }
+
+
+  static bool ReadNextTag(DicomTag& tag,
+                          ValueRepresentation& vr,
+                          std::string& value,
+                          const char* dicom,
+                          size_t size,
+                          size_t& position)
+  {
+    /**
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2
+     * This function reads a data element with Explicit VR encoded using Little-Endian.
+     **/
+
+    if (position + 6 > size)
+    {
+      return false;
+    }
+
+    tag = DicomTag(ReadUnsignedInteger16(dicom + position),
+                   ReadUnsignedInteger16(dicom + position + 2));
+
+    vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true);
+    if (vr == ValueRepresentation_NotSupported)
+    {
+      return false;
+    }
+
+    if (vr == ValueRepresentation_OtherByte ||
+        vr == ValueRepresentation_OtherDouble ||
+        vr == ValueRepresentation_OtherFloat ||
+        vr == ValueRepresentation_OtherLong ||
+        vr == ValueRepresentation_OtherWord ||
+        vr == ValueRepresentation_Sequence ||
+        vr == ValueRepresentation_UnlimitedCharacters ||
+        vr == ValueRepresentation_UniversalResource ||
+        vr == ValueRepresentation_UnlimitedText ||
+        vr == ValueRepresentation_Unknown)    // Note that "UN" should never appear in the Meta Information
+    {
+      if (position + 12 > size)
+      {
+        return false;
+      }
+
+      uint32_t length = ReadUnsignedInteger32(dicom + position + 8);
+
+      if (position + 12 + length > size)
+      {
+        return false;
+      }
+
+      value.assign(dicom + position + 12, length);
+      position += (12 + length);
+    }
+    else
+    {
+      if (position + 8 > size)
+      {
+        return false;
+      }
+
+      uint16_t length = ReadUnsignedInteger16(dicom + position + 6);
+
+      if (position + 8 + length > size)
+      {
+        return false;
+      }
+
+      value.assign(dicom + position + 8, length);
+      position += (8 + length);
+    }
+
+    if (!ValidateTag(vr, value))
+    {
+      return false;
+    }
+
+    RemoveTagPadding(value, vr);
+
+    return true;
+  }
+
+
+  bool DicomMap::ParseDicomMetaInformation(DicomMap& result,
+                                           const char* dicom,
+                                           size_t size)
+  {
+    /**
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html
+     * According to Table 7.1-1, besides the "DICM" DICOM prefix, the
+     * file preamble (i.e. dicom[0..127]) should not be taken into
+     * account to determine whether the file is or is not a DICOM file.
+     **/
+
+    if (size < 132 ||
+        dicom[128] != 'D' ||
+        dicom[129] != 'I' ||
+        dicom[130] != 'C' ||
+        dicom[131] != 'M')
+    {
+      return false;
+    }
+
+
+    /**
+     * The DICOM File Meta Information must be encoded using the
+     * Explicit VR Little Endian Transfer Syntax
+     * (UID=1.2.840.10008.1.2.1).
+     **/
+
+    result.Clear();
+
+    // First, we read the "File Meta Information Group Length" tag
+    // (0002,0000) to know where to stop reading the meta header
+    size_t position = 132;
+
+    DicomTag tag(0x0000, 0x0000);  // Dummy initialization
+    ValueRepresentation vr;
+    std::string value;
+    if (!ReadNextTag(tag, vr, value, dicom, size, position) ||
+        tag.GetGroup() != 0x0002 ||
+        tag.GetElement() != 0x0000 ||
+        vr != ValueRepresentation_UnsignedLong ||
+        value.size() != 4)
+    {
+      return false;
+    }
+
+    size_t stopPosition = position + ReadUnsignedInteger32(value.c_str());
+    if (stopPosition > size)
+    {
+      return false;
+    }
+
+    while (position < stopPosition)
+    {
+      if (ReadNextTag(tag, vr, value, dicom, size, position))
+      {
+        result.SetValue(tag, value, IsBinaryValueRepresentation(vr));
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomMap.h b/Framework/Orthanc/Core/DicomFormat/DicomMap.h
new file mode 100644
index 0000000..58e50b8
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomMap.h
@@ -0,0 +1,184 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomTag.h"
+#include "DicomValue.h"
+#include "../Enumerations.h"
+
+#include <set>
+#include <map>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class DicomMap : public boost::noncopyable
+  {
+  private:
+    friend class DicomArray;
+    friend class FromDcmtkBridge;
+    friend class ToDcmtkBridge;
+
+    typedef std::map<DicomTag, DicomValue*>  Map;
+
+    Map map_;
+
+    // Warning: This takes the ownership of "value"
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  DicomValue* value);
+
+    void SetValue(DicomTag tag, 
+                  DicomValue* value);
+
+    void ExtractTags(DicomMap& source,
+                     const DicomTag* tags,
+                     size_t count) const;
+   
+    static void GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level);
+
+  public:
+    DicomMap()
+    {
+    }
+
+    ~DicomMap()
+    {
+      Clear();
+    }
+
+    size_t GetSize() const
+    {
+      return map_.size();
+    }
+    
+    DicomMap* Clone() const;
+
+    void Assign(const DicomMap& other);
+
+    void Clear();
+
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  const DicomValue& value)
+    {
+      SetValue(group, element, value.Clone());
+    }
+
+    void SetValue(const DicomTag& tag,
+                  const DicomValue& value)
+    {
+      SetValue(tag, value.Clone());
+    }
+
+    void SetValue(const DicomTag& tag,
+                  const std::string& str,
+                  bool isBinary)
+    {
+      SetValue(tag, new DicomValue(str, isBinary));
+    }
+
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  const std::string& str,
+                  bool isBinary)
+    {
+      SetValue(group, element, new DicomValue(str, isBinary));
+    }
+
+    bool HasTag(uint16_t group, uint16_t element) const
+    {
+      return HasTag(DicomTag(group, element));
+    }
+
+    bool HasTag(const DicomTag& tag) const
+    {
+      return map_.find(tag) != map_.end();
+    }
+
+    const DicomValue& GetValue(uint16_t group, uint16_t element) const
+    {
+      return GetValue(DicomTag(group, element));
+    }
+
+    const DicomValue& GetValue(const DicomTag& tag) const;
+
+    // DO NOT delete the returned value!
+    const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const
+    {
+      return TestAndGetValue(DicomTag(group, element));
+    }       
+
+    // DO NOT delete the returned value!
+    const DicomValue* TestAndGetValue(const DicomTag& tag) const;
+
+    void Remove(const DicomTag& tag);
+
+    void ExtractPatientInformation(DicomMap& result) const;
+
+    void ExtractStudyInformation(DicomMap& result) const;
+
+    void ExtractSeriesInformation(DicomMap& result) const;
+
+    void ExtractInstanceInformation(DicomMap& result) const;
+
+    static void SetupFindPatientTemplate(DicomMap& result);
+
+    static void SetupFindStudyTemplate(DicomMap& result);
+
+    static void SetupFindSeriesTemplate(DicomMap& result);
+
+    static void SetupFindInstanceTemplate(DicomMap& result);
+
+    void CopyTagIfExists(const DicomMap& source,
+                         const DicomTag& tag);
+
+    static bool IsMainDicomTag(const DicomTag& tag, ResourceType level);
+
+    static bool IsMainDicomTag(const DicomTag& tag);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result, ResourceType level);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result);
+
+    void GetTags(std::set<DicomTag>& tags) const;
+
+    static void LoadMainDicomTags(const DicomTag*& tags,
+                                  size_t& size,
+                                  ResourceType level);
+
+    static bool ParseDicomMetaInformation(DicomMap& result,
+                                          const char* dicom,
+                                          size_t size);
+  };
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomTag.cpp b/Framework/Orthanc/Core/DicomFormat/DicomTag.cpp
new file mode 100644
index 0000000..12ae7a4
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomTag.cpp
@@ -0,0 +1,252 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomTag.h"
+
+#include "../OrthancException.h"
+
+#include <iostream>
+#include <iomanip>
+#include <stdio.h>
+
+namespace Orthanc
+{
+  bool DicomTag::operator< (const DicomTag& other) const
+  {
+    if (group_ < other.group_)
+      return true;
+
+    if (group_ > other.group_)
+      return false;
+
+    return element_ < other.element_;
+  }
+
+
+  std::ostream& operator<< (std::ostream& o, const DicomTag& tag)
+  {
+    using namespace std;
+    ios_base::fmtflags state = o.flags();
+    o.flags(ios::right | ios::hex);
+    o << "(" << setfill('0') << setw(4) << tag.GetGroup()
+      << "," << setw(4) << tag.GetElement() << ")";
+    o.flags(state);
+    return o;
+  }
+
+
+  std::string DicomTag::Format() const
+  {
+    char b[16];
+    sprintf(b, "%04x,%04x", group_, element_);
+    return std::string(b);
+  }
+
+
+  const char* DicomTag::GetMainTagsName() const
+  {
+    if (*this == DICOM_TAG_ACCESSION_NUMBER)
+      return "AccessionNumber";
+
+    if (*this == DICOM_TAG_SOP_INSTANCE_UID)
+      return "SOPInstanceUID";
+
+    if (*this == DICOM_TAG_PATIENT_ID)
+      return "PatientID";
+
+    if (*this == DICOM_TAG_SERIES_INSTANCE_UID)
+      return "SeriesInstanceUID";
+
+    if (*this == DICOM_TAG_STUDY_INSTANCE_UID)
+      return "StudyInstanceUID"; 
+
+    if (*this == DICOM_TAG_PIXEL_DATA)
+      return "PixelData";
+
+    if (*this == DICOM_TAG_IMAGE_INDEX)
+      return "ImageIndex";
+
+    if (*this == DICOM_TAG_INSTANCE_NUMBER)
+      return "InstanceNumber";
+
+    if (*this == DICOM_TAG_NUMBER_OF_SLICES)
+      return "NumberOfSlices";
+
+    if (*this == DICOM_TAG_NUMBER_OF_FRAMES)
+      return "NumberOfFrames";
+
+    if (*this == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)
+      return "CardiacNumberOfImages";
+
+    if (*this == DICOM_TAG_IMAGES_IN_ACQUISITION)
+      return "ImagesInAcquisition";
+
+    if (*this == DICOM_TAG_PATIENT_NAME)
+      return "PatientName";
+
+    if (*this == DICOM_TAG_IMAGE_POSITION_PATIENT)
+      return "ImagePositionPatient";
+
+    if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT)
+      return "ImageOrientationPatient";
+
+    return "";
+  }
+
+
+  void DicomTag::AddTagsForModule(std::set<DicomTag>& target,
+                                  DicomModule module)
+  {
+    // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions
+
+    switch (module)
+    {
+      case DicomModule_Patient:
+        // This is Table C.7-1 "Patient Module Attributes" (p. 373)
+        target.insert(DicomTag(0x0010, 0x0010));   // Patient's name
+        target.insert(DicomTag(0x0010, 0x0020));   // Patient ID
+        target.insert(DicomTag(0x0010, 0x0030));   // Patient's birth date
+        target.insert(DicomTag(0x0010, 0x0040));   // Patient's sex
+        target.insert(DicomTag(0x0008, 0x1120));   // Referenced patient sequence
+        target.insert(DicomTag(0x0010, 0x0032));   // Patient's birth time
+        target.insert(DicomTag(0x0010, 0x1000));   // Other patient IDs
+        target.insert(DicomTag(0x0010, 0x1002));   // Other patient IDs sequence
+        target.insert(DicomTag(0x0010, 0x1001));   // Other patient names
+        target.insert(DicomTag(0x0010, 0x2160));   // Ethnic group
+        target.insert(DicomTag(0x0010, 0x4000));   // Patient comments
+        target.insert(DicomTag(0x0010, 0x2201));   // Patient species description
+        target.insert(DicomTag(0x0010, 0x2202));   // Patient species code sequence
+        target.insert(DicomTag(0x0010, 0x2292));   // Patient breed description
+        target.insert(DicomTag(0x0010, 0x2293));   // Patient breed code sequence
+        target.insert(DicomTag(0x0010, 0x2294));   // Breed registration sequence
+        target.insert(DicomTag(0x0010, 0x2297));   // Responsible person
+        target.insert(DicomTag(0x0010, 0x2298));   // Responsible person role
+        target.insert(DicomTag(0x0010, 0x2299));   // Responsible organization
+        target.insert(DicomTag(0x0012, 0x0062));   // Patient identity removed
+        target.insert(DicomTag(0x0012, 0x0063));   // De-identification method
+        target.insert(DicomTag(0x0012, 0x0064));   // De-identification method code sequence
+
+        // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112)
+        target.insert(DicomTag(0x0010, 0x0021));   // Issuer of Patient ID
+        target.insert(DicomTag(0x0010, 0x0024));   // Issuer of Patient ID qualifiers sequence
+        break;
+
+      case DicomModule_Study:
+        // This is Table C.7-3 "General Study Module Attributes" (p. 378)
+        target.insert(DicomTag(0x0020, 0x000d));   // Study instance UID
+        target.insert(DicomTag(0x0008, 0x0020));   // Study date
+        target.insert(DicomTag(0x0008, 0x0030));   // Study time
+        target.insert(DicomTag(0x0008, 0x0090));   // Referring physician's name
+        target.insert(DicomTag(0x0008, 0x0096));   // Referring physician identification sequence
+        target.insert(DicomTag(0x0020, 0x0010));   // Study ID
+        target.insert(DicomTag(0x0008, 0x0050));   // Accession number
+        target.insert(DicomTag(0x0008, 0x0051));   // Issuer of accession number sequence
+        target.insert(DicomTag(0x0008, 0x1030));   // Study description
+        target.insert(DicomTag(0x0008, 0x1048));   // Physician(s) of record
+        target.insert(DicomTag(0x0008, 0x1049));   // Physician(s) of record identification sequence
+        target.insert(DicomTag(0x0008, 0x1060));   // Name of physician(s) reading study
+        target.insert(DicomTag(0x0008, 0x1062));   // Physician(s) reading study identification sequence
+        target.insert(DicomTag(0x0032, 0x1034));   // Requesting service code sequence
+        target.insert(DicomTag(0x0008, 0x1110));   // Referenced study sequence
+        target.insert(DicomTag(0x0008, 0x1032));   // Procedure code sequence
+        target.insert(DicomTag(0x0040, 0x1012));   // Reason for performed procedure code sequence
+        break;
+
+      case DicomModule_Series:
+        // This is Table C.7-5 "General Series Module Attributes" (p. 385)
+        target.insert(DicomTag(0x0008, 0x0060));   // Modality 
+        target.insert(DicomTag(0x0020, 0x000e));   // Series Instance UID 
+        target.insert(DicomTag(0x0020, 0x0011));   // Series Number 
+        target.insert(DicomTag(0x0020, 0x0060));   // Laterality 
+        target.insert(DicomTag(0x0008, 0x0021));   // Series Date 
+        target.insert(DicomTag(0x0008, 0x0031));   // Series Time 
+        target.insert(DicomTag(0x0008, 0x1050));   // Performing Physicians’ Name 
+        target.insert(DicomTag(0x0008, 0x1052));   // Performing Physician Identification Sequence 
+        target.insert(DicomTag(0x0018, 0x1030));   // Protocol Name
+        target.insert(DicomTag(0x0008, 0x103e));   // Series Description 
+        target.insert(DicomTag(0x0008, 0x103f));   // Series Description Code Sequence 
+        target.insert(DicomTag(0x0008, 0x1070));   // Operators' Name 
+        target.insert(DicomTag(0x0008, 0x1072));   // Operator Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x1111));   // Referenced Performed Procedure Step Sequence
+        target.insert(DicomTag(0x0008, 0x1250));   // Related Series Sequence
+        target.insert(DicomTag(0x0018, 0x0015));   // Body Part Examined
+        target.insert(DicomTag(0x0018, 0x5100));   // Patient Position
+        target.insert(DicomTag(0x0028, 0x0108));   // Smallest Pixel Value in Series 
+        target.insert(DicomTag(0x0029, 0x0109));   // Largest Pixel Value in Series 
+        target.insert(DicomTag(0x0040, 0x0275));   // Request Attributes Sequence 
+        target.insert(DicomTag(0x0010, 0x2210));   // Anatomical Orientation Type
+
+        // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES
+        target.insert(DicomTag(0x0040, 0x0253));   // Performed Procedure Step ID 
+        target.insert(DicomTag(0x0040, 0x0244));   // Performed Procedure Step Start Date 
+        target.insert(DicomTag(0x0040, 0x0245));   // Performed Procedure Step Start Time 
+        target.insert(DicomTag(0x0040, 0x0254));   // Performed Procedure Step Description 
+        target.insert(DicomTag(0x0040, 0x0260));   // Performed Protocol Code Sequence 
+        target.insert(DicomTag(0x0040, 0x0280));   // Comments on the Performed Procedure Step
+        break;
+
+      case DicomModule_Instance:
+        // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207)
+        target.insert(DicomTag(0x0008, 0x0016));   // SOP Class UID
+        target.insert(DicomTag(0x0008, 0x0018));   // SOP Instance UID 
+        target.insert(DicomTag(0x0008, 0x0005));   // Specific Character Set 
+        target.insert(DicomTag(0x0008, 0x0012));   // Instance Creation Date 
+        target.insert(DicomTag(0x0008, 0x0013));   // Instance Creation Time 
+        target.insert(DicomTag(0x0008, 0x0014));   // Instance Creator UID 
+        target.insert(DicomTag(0x0008, 0x001a));   // Related General SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x001b));   // Original Specialized SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x0110));   // Coding Scheme Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x0201));   // Timezone Offset From UTC 
+        target.insert(DicomTag(0x0018, 0xa001));   // Contributing Equipment Sequence
+        target.insert(DicomTag(0x0020, 0x0013));   // Instance Number 
+        target.insert(DicomTag(0x0100, 0x0410));   // SOP Instance Status 
+        target.insert(DicomTag(0x0100, 0x0420));   // SOP Authorization DateTime 
+        target.insert(DicomTag(0x0100, 0x0424));   // SOP Authorization Comment 
+        target.insert(DicomTag(0x0100, 0x0426));   // Authorization Equipment Certification Number
+        target.insert(DicomTag(0x0400, 0x0500));   // Encrypted Attributes Sequence
+        target.insert(DicomTag(0x0400, 0x0561));   // Original Attributes Sequence 
+        target.insert(DicomTag(0x0040, 0xa390));   // HL7 Structured Document Reference Sequence
+        target.insert(DicomTag(0x0028, 0x0303));   // Longitudinal Temporal Information Modified 
+
+        // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216)
+        target.insert(DicomTag(0x4ffe, 0x0001));   // MAC Parameters sequence
+        target.insert(DicomTag(0xfffa, 0xfffa));   // Digital signatures sequence
+        break;
+
+        // TODO IMAGE MODULE?
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomTag.h b/Framework/Orthanc/Core/DicomFormat/DicomTag.h
new file mode 100644
index 0000000..25f4259
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomTag.h
@@ -0,0 +1,182 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <set>
+#include <stdint.h>
+
+#include "../Enumerations.h"
+
+namespace Orthanc
+{
+  class DicomTag
+  {
+    // This must stay a POD (plain old data structure) 
+
+  private:
+    uint16_t group_;
+    uint16_t element_;
+
+  public:
+    DicomTag(uint16_t group,
+             uint16_t element) :
+      group_(group),
+      element_(element)
+    {
+    }
+
+    uint16_t GetGroup() const
+    {
+      return group_;
+    }
+
+    uint16_t GetElement() const
+    {
+      return element_;
+    }
+
+    bool IsPrivate() const
+    {
+      return group_ % 2 == 1;
+    }
+
+    const char* GetMainTagsName() const;
+
+    bool operator< (const DicomTag& other) const;
+
+    bool operator== (const DicomTag& other) const
+    {
+      return group_ == other.group_ && element_ == other.element_;
+    }
+
+    bool operator!= (const DicomTag& other) const
+    {
+      return !(*this == other);
+    }
+
+    std::string Format() const;
+
+    friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
+
+    static void AddTagsForModule(std::set<DicomTag>& target,
+                                 DicomModule module);
+  };
+
+  // Aliases for the most useful tags
+  static const DicomTag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
+  static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
+  static const DicomTag DICOM_TAG_PATIENT_ID(0x0010, 0x0020);
+  static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
+  static const DicomTag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
+  static const DicomTag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010);
+
+  static const DicomTag DICOM_TAG_IMAGE_INDEX(0x0054, 0x1330);
+  static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013);
+
+  static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081);
+  static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101);
+  static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090);
+  static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002);
+  static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010);
+  static const DicomTag DICOM_TAG_ENCAPSULATED_DOCUMENT(0x0042, 0x0011);
+
+  static const DicomTag DICOM_TAG_STUDY_DESCRIPTION(0x0008, 0x1030);
+  static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e);
+  static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060);
+
+  // The following is used for "modify/anonymize" operations
+  static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
+  static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002);
+  static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003);
+  static const DicomTag DICOM_TAG_DEIDENTIFICATION_METHOD(0x0012, 0x0063);
+
+  // DICOM tags used for fMRI (thanks to Will Ryder)
+  static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105);
+  static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100);
+
+  // Tags for C-FIND and C-MOVE
+  static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
+  static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052);
+  static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061);
+
+  // Tags for images
+  static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
+  static const DicomTag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100);
+  static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101);
+  static const DicomTag DICOM_TAG_HIGH_BIT(0x0028, 0x0102);
+  static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006);
+  static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+  static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
+  static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
+
+  // Tags related to date and time
+  static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022);
+  static const DicomTag DICOM_TAG_ACQUISITION_TIME(0x0008, 0x0032);
+  static const DicomTag DICOM_TAG_CONTENT_DATE(0x0008, 0x0023);
+  static const DicomTag DICOM_TAG_CONTENT_TIME(0x0008, 0x0033);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_DATE(0x0008, 0x0012);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_TIME(0x0008, 0x0013);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_TIME(0x0010, 0x0032);
+  static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021);
+  static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031);
+  static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020);
+  static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030);
+
+  // Various tags
+  static const DicomTag DICOM_TAG_SERIES_TYPE(0x0054, 0x1000);
+  static const DicomTag DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION(0x0032, 0x1060);
+  static const DicomTag DICOM_TAG_INSTITUTION_NAME(0x0008, 0x0080);
+  static const DicomTag DICOM_TAG_REQUESTING_PHYSICIAN(0x0032, 0x1032);
+  static const DicomTag DICOM_TAG_REFERRING_PHYSICIAN_NAME(0x0008, 0x0090);
+  static const DicomTag DICOM_TAG_OPERATOR_NAME(0x0008, 0x1070);
+  static const DicomTag DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION(0x0040, 0x0254);
+  static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000);
+  static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400);
+  static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010);
+
+  // Counting patients, studies and series
+  // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209);  
+  static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062);  
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp b/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
new file mode 100644
index 0000000..32a17b5
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
@@ -0,0 +1,93 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomValue.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  DicomValue::DicomValue(const DicomValue& other) : 
+    type_(other.type_),
+    content_(other.content_)
+  {
+  }
+
+
+  DicomValue::DicomValue(const std::string& content,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String),
+    content_(content)
+  {
+  }
+  
+  
+  DicomValue::DicomValue(const char* data,
+                         size_t size,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String)
+  {
+    content_.assign(data, size);
+  }
+    
+  
+  const std::string& DicomValue::GetContent() const
+  {
+    if (type_ == Type_Null)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else
+    {
+      return content_;
+    }
+  }
+
+
+  DicomValue* DicomValue::Clone() const
+  {
+    return new DicomValue(*this);
+  }
+
+  
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+  void DicomValue::FormatDataUriScheme(std::string& target,
+                                       const std::string& mime) const
+  {
+    Toolbox::EncodeBase64(target, GetContent());
+    target.insert(0, "data:" + mime + ";base64,");
+  }
+#endif
+
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomValue.h b/Framework/Orthanc/Core/DicomFormat/DicomValue.h
new file mode 100644
index 0000000..ad0ec54
--- /dev/null
+++ b/Framework/Orthanc/Core/DicomFormat/DicomValue.h
@@ -0,0 +1,91 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class DicomValue : public boost::noncopyable
+  {
+  private:
+    enum Type
+    {
+      Type_Null,
+      Type_String,
+      Type_Binary
+    };
+
+    Type         type_;
+    std::string  content_;
+
+    DicomValue(const DicomValue& other);
+
+  public:
+    DicomValue() : type_(Type_Null)
+    {
+    }
+    
+    DicomValue(const std::string& content,
+               bool isBinary);
+    
+    DicomValue(const char* data,
+               size_t size,
+               bool isBinary);
+    
+    const std::string& GetContent() const;
+
+    bool IsNull() const
+    {
+      return type_ == Type_Null;
+    }
+
+    bool IsBinary() const
+    {
+      return type_ == Type_Binary;
+    }
+    
+    DicomValue* Clone() const;
+
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+    void FormatDataUriScheme(std::string& target,
+                             const std::string& mime) const;
+
+    void FormatDataUriScheme(std::string& target) const
+    {
+      FormatDataUriScheme(target, "application/octet-stream");
+    }
+#endif
+  };
+}
diff --git a/Framework/Orthanc/Core/Endianness.h b/Framework/Orthanc/Core/Endianness.h
new file mode 100644
index 0000000..a68fd83
--- /dev/null
+++ b/Framework/Orthanc/Core/Endianness.h
@@ -0,0 +1,157 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+
+/********************************************************************
+ ** LINUX ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__linux__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <endian.h>
+#endif
+
+
+/********************************************************************
+ ** WINDOWS ARCHITECTURES
+ **
+ ** On Windows x86, "host" will always be little-endian ("le").
+ ********************************************************************/
+
+#if defined(_WIN32)
+#  if defined(_MSC_VER)
+//   Visual Studio - http://msdn.microsoft.com/en-us/library/a3140177.aspx
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) _byteswap_ushort(x)
+#    define be32toh(x) _byteswap_ulong(x)
+#    define be64toh(x) _byteswap_uint64(x)
+#  elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
+//   MinGW >= 4.3 - Use builtin intrinsic for byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) __builtin_bswap16(x)
+#    define be32toh(x) __builtin_bswap32(x)
+#    define be64toh(x) __builtin_bswap64(x)
+#  else
+//   MinGW <= 4.2, we must manually implement the byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0
+#    define be16toh(x) __orthanc_bswap16(x)
+#    define be32toh(x) __orthanc_bswap32(x)
+#    define be64toh(x) __orthanc_bswap64(x)
+#  endif
+
+#  define htobe16(x) be16toh(x)
+#  define htobe32(x) be32toh(x)
+#  define htobe64(x) be64toh(x)
+
+#  define htole16(x) x
+#  define htole32(x) x
+#  define htole64(x) x
+
+#  define le16toh(x) x
+#  define le32toh(x) x
+#  define le64toh(x) x
+#endif
+
+
+/********************************************************************
+ ** FREEBSD ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <arpa/inet.h>
+#endif
+
+
+/********************************************************************
+ ** APPLE ARCHITECTURES (including OS X)
+ ********************************************************************/
+
+#if defined(__APPLE__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <libkern/OSByteOrder.h>
+#  define be16toh(x) OSSwapBigToHostInt16(x)
+#  define be32toh(x) OSSwapBigToHostInt32(x)
+#  define be64toh(x) OSSwapBigToHostInt64(x)
+
+#  define htobe16(x) OSSwapHostToBigInt16(x)
+#  define htobe32(x) OSSwapHostToBigInt32(x)
+#  define htobe64(x) OSSwapHostToBigInt64(x)
+
+#  define htole16(x) OSSwapHostToLittleInt16(x)
+#  define htole32(x) OSSwapHostToLittleInt32(x)
+#  define htole64(x) OSSwapHostToLittleInt64(x)
+
+#  define le16toh(x) OSSwapLittleToHostInt16(x)
+#  define le32toh(x) OSSwapLittleToHostInt32(x)
+#  define le64toh(x) OSSwapLittleToHostInt64(x)
+#endif
+
+
+/********************************************************************
+ ** PORTABLE (BUT SLOW) IMPLEMENTATION OF BYTE-SWAPPING
+ ********************************************************************/
+
+#if ORTHANC_HAS_BUILTIN_BYTE_SWAP != 1
+
+#include <stdint.h>
+
+static inline uint16_t __orthanc_bswap16(uint16_t a)
+{
+  return (a << 8) | (a >> 8);
+}
+
+static inline uint32_t __orthanc_bswap32(uint32_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint32_t>(p[0]) << 24 |
+          static_cast<uint32_t>(p[1]) << 16 |
+          static_cast<uint32_t>(p[2]) << 8 |
+          static_cast<uint32_t>(p[3]));
+}
+
+static inline uint64_t __orthanc_bswap64(uint64_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint64_t>(p[0]) << 56 |
+          static_cast<uint64_t>(p[1]) << 48 |
+          static_cast<uint64_t>(p[2]) << 40 |
+          static_cast<uint64_t>(p[3]) << 32 |
+          static_cast<uint64_t>(p[4]) << 24 |
+          static_cast<uint64_t>(p[5]) << 16 |
+          static_cast<uint64_t>(p[6]) << 8 |
+          static_cast<uint64_t>(p[7]));
+}
+
+#endif
diff --git a/Framework/Orthanc/Core/EnumerationDictionary.h b/Framework/Orthanc/Core/EnumerationDictionary.h
new file mode 100644
index 0000000..9479401
--- /dev/null
+++ b/Framework/Orthanc/Core/EnumerationDictionary.h
@@ -0,0 +1,125 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OrthancException.h"
+
+#include "Toolbox.h"
+#include <boost/lexical_cast.hpp>
+#include <string>
+#include <map>
+
+namespace Orthanc
+{
+  namespace Toolbox
+  {
+    template <typename Enumeration>
+    class EnumerationDictionary
+    {
+    private:
+      typedef std::map<Enumeration, std::string>  EnumerationToString;
+      typedef std::map<std::string, Enumeration>  StringToEnumeration;
+
+      EnumerationToString enumerationToString_;
+      StringToEnumeration stringToEnumeration_;
+
+    public:
+      void Clear()
+      {
+        enumerationToString_.clear();
+        stringToEnumeration_.clear();
+      }
+
+      bool Contains(Enumeration value) const
+      {
+        return enumerationToString_.find(value) != enumerationToString_.end();
+      }
+
+      void Add(Enumeration value, const std::string& str)
+      {
+        // Check if these values are free
+        if (enumerationToString_.find(value) != enumerationToString_.end() ||
+            stringToEnumeration_.find(str) != stringToEnumeration_.end() ||
+            Toolbox::IsInteger(str) /* Prevent the registration of a number */)
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+
+        // OK, the string is free and is not a number
+        enumerationToString_[value] = str;
+        stringToEnumeration_[str] = value;
+        stringToEnumeration_[boost::lexical_cast<std::string>(static_cast<int>(value))] = value;
+      }
+
+      Enumeration Translate(const std::string& str) const
+      {
+        try
+        {
+          int value = boost::lexical_cast<int>(str);
+          return static_cast<Enumeration>(value);
+        }
+        catch (boost::bad_lexical_cast)
+        {
+        }
+
+        typename StringToEnumeration::const_iterator
+          found = stringToEnumeration_.find(str);
+
+        if (found == stringToEnumeration_.end())
+        {
+          throw OrthancException(ErrorCode_InexistentItem);
+        }
+        else
+        {
+          return found->second;
+        }
+      }
+
+      std::string Translate(Enumeration e) const
+      {
+        typename EnumerationToString::const_iterator
+          found = enumerationToString_.find(e);
+
+        if (found == enumerationToString_.end())
+        {
+          // No name for this item
+          return boost::lexical_cast<std::string>(static_cast<int>(e));
+        }
+        else
+        {
+          return found->second;
+        }
+      }
+    };
+  }
+}
diff --git a/Framework/Orthanc/Core/Enumerations.cpp b/Framework/Orthanc/Core/Enumerations.cpp
new file mode 100644
index 0000000..8ed74c2
--- /dev/null
+++ b/Framework/Orthanc/Core/Enumerations.cpp
@@ -0,0 +1,1396 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "Enumerations.h"
+
+#include "OrthancException.h"
+#include "Toolbox.h"
+#include "Logging.h"
+
+#include <string.h>
+#include <cassert>
+
+namespace Orthanc
+{
+  // This function is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  const char* EnumerationToString(ErrorCode error)
+  {
+    switch (error)
+    {
+      case ErrorCode_InternalError:
+        return "Internal error";
+
+      case ErrorCode_Success:
+        return "Success";
+
+      case ErrorCode_Plugin:
+        return "Error encountered within the plugin engine";
+
+      case ErrorCode_NotImplemented:
+        return "Not implemented yet";
+
+      case ErrorCode_ParameterOutOfRange:
+        return "Parameter out of range";
+
+      case ErrorCode_NotEnoughMemory:
+        return "Not enough memory";
+
+      case ErrorCode_BadParameterType:
+        return "Bad type for a parameter";
+
+      case ErrorCode_BadSequenceOfCalls:
+        return "Bad sequence of calls";
+
+      case ErrorCode_InexistentItem:
+        return "Accessing an inexistent item";
+
+      case ErrorCode_BadRequest:
+        return "Bad request";
+
+      case ErrorCode_NetworkProtocol:
+        return "Error in the network protocol";
+
+      case ErrorCode_SystemCommand:
+        return "Error while calling a system command";
+
+      case ErrorCode_Database:
+        return "Error with the database engine";
+
+      case ErrorCode_UriSyntax:
+        return "Badly formatted URI";
+
+      case ErrorCode_InexistentFile:
+        return "Inexistent file";
+
+      case ErrorCode_CannotWriteFile:
+        return "Cannot write to file";
+
+      case ErrorCode_BadFileFormat:
+        return "Bad file format";
+
+      case ErrorCode_Timeout:
+        return "Timeout";
+
+      case ErrorCode_UnknownResource:
+        return "Unknown resource";
+
+      case ErrorCode_IncompatibleDatabaseVersion:
+        return "Incompatible version of the database";
+
+      case ErrorCode_FullStorage:
+        return "The file storage is full";
+
+      case ErrorCode_CorruptedFile:
+        return "Corrupted file (e.g. inconsistent MD5 hash)";
+
+      case ErrorCode_InexistentTag:
+        return "Inexistent tag";
+
+      case ErrorCode_ReadOnly:
+        return "Cannot modify a read-only data structure";
+
+      case ErrorCode_IncompatibleImageFormat:
+        return "Incompatible format of the images";
+
+      case ErrorCode_IncompatibleImageSize:
+        return "Incompatible size of the images";
+
+      case ErrorCode_SharedLibrary:
+        return "Error while using a shared library (plugin)";
+
+      case ErrorCode_UnknownPluginService:
+        return "Plugin invoking an unknown service";
+
+      case ErrorCode_UnknownDicomTag:
+        return "Unknown DICOM tag";
+
+      case ErrorCode_BadJson:
+        return "Cannot parse a JSON document";
+
+      case ErrorCode_Unauthorized:
+        return "Bad credentials were provided to an HTTP request";
+
+      case ErrorCode_BadFont:
+        return "Badly formatted font file";
+
+      case ErrorCode_DatabasePlugin:
+        return "The plugin implementing a custom database back-end does not fulfill the proper interface";
+
+      case ErrorCode_StorageAreaPlugin:
+        return "Error in the plugin implementing a custom storage area";
+
+      case ErrorCode_EmptyRequest:
+        return "The request is empty";
+
+      case ErrorCode_NotAcceptable:
+        return "Cannot send a response which is acceptable according to the Accept HTTP header";
+
+      case ErrorCode_SQLiteNotOpened:
+        return "SQLite: The database is not opened";
+
+      case ErrorCode_SQLiteAlreadyOpened:
+        return "SQLite: Connection is already open";
+
+      case ErrorCode_SQLiteCannotOpen:
+        return "SQLite: Unable to open the database";
+
+      case ErrorCode_SQLiteStatementAlreadyUsed:
+        return "SQLite: This cached statement is already being referred to";
+
+      case ErrorCode_SQLiteExecute:
+        return "SQLite: Cannot execute a command";
+
+      case ErrorCode_SQLiteRollbackWithoutTransaction:
+        return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)";
+
+      case ErrorCode_SQLiteCommitWithoutTransaction:
+        return "SQLite: Committing a nonexistent transaction";
+
+      case ErrorCode_SQLiteRegisterFunction:
+        return "SQLite: Unable to register a function";
+
+      case ErrorCode_SQLiteFlush:
+        return "SQLite: Unable to flush the database";
+
+      case ErrorCode_SQLiteCannotRun:
+        return "SQLite: Cannot run a cached statement";
+
+      case ErrorCode_SQLiteCannotStep:
+        return "SQLite: Cannot step over a cached statement";
+
+      case ErrorCode_SQLiteBindOutOfRange:
+        return "SQLite: Bing a value while out of range (serious error)";
+
+      case ErrorCode_SQLitePrepareStatement:
+        return "SQLite: Cannot prepare a cached statement";
+
+      case ErrorCode_SQLiteTransactionAlreadyStarted:
+        return "SQLite: Beginning the same transaction twice";
+
+      case ErrorCode_SQLiteTransactionCommit:
+        return "SQLite: Failure when committing the transaction";
+
+      case ErrorCode_SQLiteTransactionBegin:
+        return "SQLite: Cannot start a transaction";
+
+      case ErrorCode_DirectoryOverFile:
+        return "The directory to be created is already occupied by a regular file";
+
+      case ErrorCode_FileStorageCannotWrite:
+        return "Unable to create a subdirectory or a file in the file storage";
+
+      case ErrorCode_DirectoryExpected:
+        return "The specified path does not point to a directory";
+
+      case ErrorCode_HttpPortInUse:
+        return "The TCP port of the HTTP server is privileged or already in use";
+
+      case ErrorCode_DicomPortInUse:
+        return "The TCP port of the DICOM server is privileged or already in use";
+
+      case ErrorCode_BadHttpStatusInRest:
+        return "This HTTP status is not allowed in a REST API";
+
+      case ErrorCode_RegularFileExpected:
+        return "The specified path does not point to a regular file";
+
+      case ErrorCode_PathToExecutable:
+        return "Unable to get the path to the executable";
+
+      case ErrorCode_MakeDirectory:
+        return "Cannot create a directory";
+
+      case ErrorCode_BadApplicationEntityTitle:
+        return "An application entity title (AET) cannot be empty or be longer than 16 characters";
+
+      case ErrorCode_NoCFindHandler:
+        return "No request handler factory for DICOM C-FIND SCP";
+
+      case ErrorCode_NoCMoveHandler:
+        return "No request handler factory for DICOM C-MOVE SCP";
+
+      case ErrorCode_NoCStoreHandler:
+        return "No request handler factory for DICOM C-STORE SCP";
+
+      case ErrorCode_NoApplicationEntityFilter:
+        return "No application entity filter";
+
+      case ErrorCode_NoSopClassOrInstance:
+        return "DicomUserConnection: Unable to find the SOP class and instance";
+
+      case ErrorCode_NoPresentationContext:
+        return "DicomUserConnection: No acceptable presentation context for modality";
+
+      case ErrorCode_DicomFindUnavailable:
+        return "DicomUserConnection: The C-FIND command is not supported by the remote SCP";
+
+      case ErrorCode_DicomMoveUnavailable:
+        return "DicomUserConnection: The C-MOVE command is not supported by the remote SCP";
+
+      case ErrorCode_CannotStoreInstance:
+        return "Cannot store an instance";
+
+      case ErrorCode_CreateDicomNotString:
+        return "Only string values are supported when creating DICOM instances";
+
+      case ErrorCode_CreateDicomOverrideTag:
+        return "Trying to override a value inherited from a parent module";
+
+      case ErrorCode_CreateDicomUseContent:
+        return "Use \"Content\" to inject an image into a new DICOM instance";
+
+      case ErrorCode_CreateDicomNoPayload:
+        return "No payload is present for one instance in the series";
+
+      case ErrorCode_CreateDicomUseDataUriScheme:
+        return "The payload of the DICOM instance must be specified according to Data URI scheme";
+
+      case ErrorCode_CreateDicomBadParent:
+        return "Trying to attach a new DICOM instance to an inexistent resource";
+
+      case ErrorCode_CreateDicomParentIsInstance:
+        return "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)";
+
+      case ErrorCode_CreateDicomParentEncoding:
+        return "Unable to get the encoding of the parent resource";
+
+      case ErrorCode_UnknownModality:
+        return "Unknown modality";
+
+      case ErrorCode_BadJobOrdering:
+        return "Bad ordering of filters in a job";
+
+      case ErrorCode_JsonToLuaTable:
+        return "Cannot convert the given JSON object to a Lua table";
+
+      case ErrorCode_CannotCreateLua:
+        return "Cannot create the Lua context";
+
+      case ErrorCode_CannotExecuteLua:
+        return "Cannot execute a Lua command";
+
+      case ErrorCode_LuaAlreadyExecuted:
+        return "Arguments cannot be pushed after the Lua function is executed";
+
+      case ErrorCode_LuaBadOutput:
+        return "The Lua function does not give the expected number of outputs";
+
+      case ErrorCode_NotLuaPredicate:
+        return "The Lua function is not a predicate (only true/false outputs allowed)";
+
+      case ErrorCode_LuaReturnsNoString:
+        return "The Lua function does not return a string";
+
+      case ErrorCode_StorageAreaAlreadyRegistered:
+        return "Another plugin has already registered a custom storage area";
+
+      case ErrorCode_DatabaseBackendAlreadyRegistered:
+        return "Another plugin has already registered a custom database back-end";
+
+      case ErrorCode_DatabaseNotInitialized:
+        return "Plugin trying to call the database during its initialization";
+
+      case ErrorCode_SslDisabled:
+        return "Orthanc has been built without SSL support";
+
+      case ErrorCode_CannotOrderSlices:
+        return "Unable to order the slices of the series";
+
+      case ErrorCode_NoWorklistHandler:
+        return "No request handler factory for DICOM C-Find Modality SCP";
+
+      case ErrorCode_AlreadyExistingTag:
+        return "Cannot override the value of a tag that already exists";
+
+      default:
+        if (error >= ErrorCode_START_PLUGINS)
+        {
+          return "Error encountered within some plugin";
+        }
+        else
+        {
+          return "Unknown error code";
+        }
+    }
+  }
+
+
+  const char* EnumerationToString(HttpMethod method)
+  {
+    switch (method)
+    {
+      case HttpMethod_Get:
+        return "GET";
+
+      case HttpMethod_Post:
+        return "POST";
+
+      case HttpMethod_Delete:
+        return "DELETE";
+
+      case HttpMethod_Put:
+        return "PUT";
+
+      default:
+        return "?";
+    }
+  }
+
+
+  const char* EnumerationToString(HttpStatus status)
+  {
+    switch (status)
+    {
+    case HttpStatus_100_Continue:
+      return "Continue";
+
+    case HttpStatus_101_SwitchingProtocols:
+      return "Switching Protocols";
+
+    case HttpStatus_102_Processing:
+      return "Processing";
+
+    case HttpStatus_200_Ok:
+      return "OK";
+
+    case HttpStatus_201_Created:
+      return "Created";
+
+    case HttpStatus_202_Accepted:
+      return "Accepted";
+
+    case HttpStatus_203_NonAuthoritativeInformation:
+      return "Non-Authoritative Information";
+
+    case HttpStatus_204_NoContent:
+      return "No Content";
+
+    case HttpStatus_205_ResetContent:
+      return "Reset Content";
+
+    case HttpStatus_206_PartialContent:
+      return "Partial Content";
+
+    case HttpStatus_207_MultiStatus:
+      return "Multi-Status";
+
+    case HttpStatus_208_AlreadyReported:
+      return "Already Reported";
+
+    case HttpStatus_226_IMUsed:
+      return "IM Used";
+
+    case HttpStatus_300_MultipleChoices:
+      return "Multiple Choices";
+
+    case HttpStatus_301_MovedPermanently:
+      return "Moved Permanently";
+
+    case HttpStatus_302_Found:
+      return "Found";
+
+    case HttpStatus_303_SeeOther:
+      return "See Other";
+
+    case HttpStatus_304_NotModified:
+      return "Not Modified";
+
+    case HttpStatus_305_UseProxy:
+      return "Use Proxy";
+
+    case HttpStatus_307_TemporaryRedirect:
+      return "Temporary Redirect";
+
+    case HttpStatus_400_BadRequest:
+      return "Bad Request";
+
+    case HttpStatus_401_Unauthorized:
+      return "Unauthorized";
+
+    case HttpStatus_402_PaymentRequired:
+      return "Payment Required";
+
+    case HttpStatus_403_Forbidden:
+      return "Forbidden";
+
+    case HttpStatus_404_NotFound:
+      return "Not Found";
+
+    case HttpStatus_405_MethodNotAllowed:
+      return "Method Not Allowed";
+
+    case HttpStatus_406_NotAcceptable:
+      return "Not Acceptable";
+
+    case HttpStatus_407_ProxyAuthenticationRequired:
+      return "Proxy Authentication Required";
+
+    case HttpStatus_408_RequestTimeout:
+      return "Request Timeout";
+
+    case HttpStatus_409_Conflict:
+      return "Conflict";
+
+    case HttpStatus_410_Gone:
+      return "Gone";
+
+    case HttpStatus_411_LengthRequired:
+      return "Length Required";
+
+    case HttpStatus_412_PreconditionFailed:
+      return "Precondition Failed";
+
+    case HttpStatus_413_RequestEntityTooLarge:
+      return "Request Entity Too Large";
+
+    case HttpStatus_414_RequestUriTooLong:
+      return "Request-URI Too Long";
+
+    case HttpStatus_415_UnsupportedMediaType:
+      return "Unsupported Media Type";
+
+    case HttpStatus_416_RequestedRangeNotSatisfiable:
+      return "Requested Range Not Satisfiable";
+
+    case HttpStatus_417_ExpectationFailed:
+      return "Expectation Failed";
+
+    case HttpStatus_422_UnprocessableEntity:
+      return "Unprocessable Entity";
+
+    case HttpStatus_423_Locked:
+      return "Locked";
+
+    case HttpStatus_424_FailedDependency:
+      return "Failed Dependency";
+
+    case HttpStatus_426_UpgradeRequired:
+      return "Upgrade Required";
+
+    case HttpStatus_500_InternalServerError:
+      return "Internal Server Error";
+
+    case HttpStatus_501_NotImplemented:
+      return "Not Implemented";
+
+    case HttpStatus_502_BadGateway:
+      return "Bad Gateway";
+
+    case HttpStatus_503_ServiceUnavailable:
+      return "Service Unavailable";
+
+    case HttpStatus_504_GatewayTimeout:
+      return "Gateway Timeout";
+
+    case HttpStatus_505_HttpVersionNotSupported:
+      return "HTTP Version Not Supported";
+
+    case HttpStatus_506_VariantAlsoNegotiates:
+      return "Variant Also Negotiates";
+
+    case HttpStatus_507_InsufficientStorage:
+      return "Insufficient Storage";
+
+    case HttpStatus_509_BandwidthLimitExceeded:
+      return "Bandwidth Limit Exceeded";
+
+    case HttpStatus_510_NotExtended:
+      return "Not Extended";
+
+    default:
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "Patient";
+
+      case ResourceType_Study:
+        return "Study";
+
+      case ResourceType_Series:
+        return "Series";
+
+      case ResourceType_Instance:
+        return "Instance";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ImageFormat format)
+  {
+    switch (format)
+    {
+      case ImageFormat_Png:
+        return "Png";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(Encoding encoding)
+  {
+    switch (encoding)
+    {
+      case Encoding_Ascii:
+        return "Ascii";
+
+      case Encoding_Utf8:
+        return "Utf8";
+
+      case Encoding_Latin1:
+        return "Latin1";
+
+      case Encoding_Latin2:
+        return "Latin2";
+
+      case Encoding_Latin3:
+        return "Latin3";
+
+      case Encoding_Latin4:
+        return "Latin4";
+
+      case Encoding_Latin5:
+        return "Latin5";
+
+      case Encoding_Cyrillic:
+        return "Cyrillic";
+
+      case Encoding_Windows1251:
+        return "Windows1251";
+
+      case Encoding_Arabic:
+        return "Arabic";
+
+      case Encoding_Greek:
+        return "Greek";
+
+      case Encoding_Hebrew:
+        return "Hebrew";
+
+      case Encoding_Thai:
+        return "Thai";
+
+      case Encoding_Japanese:
+        return "Japanese";
+
+      case Encoding_Chinese:
+        return "Chinese";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(PhotometricInterpretation photometric)
+  {
+    switch (photometric)
+    {
+      case PhotometricInterpretation_RGB:
+        return "RGB";
+
+      case PhotometricInterpretation_Monochrome1:
+        return "Monochrome1";
+
+      case PhotometricInterpretation_Monochrome2:
+        return "Monochrome2";
+
+      case PhotometricInterpretation_ARGB:
+        return "ARGB";
+
+      case PhotometricInterpretation_CMYK:
+        return "CMYK";
+
+      case PhotometricInterpretation_HSV:
+        return "HSV";
+
+      case PhotometricInterpretation_Palette:
+        return "Palette color";
+
+      case PhotometricInterpretation_YBRFull:
+        return "YBR full";
+
+      case PhotometricInterpretation_YBRFull422:
+        return "YBR full 422";
+
+      case PhotometricInterpretation_YBRPartial420:
+        return "YBR partial 420"; 
+
+      case PhotometricInterpretation_YBRPartial422:
+        return "YBR partial 422"; 
+
+      case PhotometricInterpretation_YBR_ICT:
+        return "YBR ICT"; 
+
+      case PhotometricInterpretation_YBR_RCT:
+        return "YBR RCT"; 
+
+      case PhotometricInterpretation_Unknown:
+        return "Unknown";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(RequestOrigin origin)
+  {
+    switch (origin)
+    {
+      case RequestOrigin_Unknown:
+        return "Unknown";
+
+      case RequestOrigin_DicomProtocol:
+        return "DicomProtocol";
+
+      case RequestOrigin_RestApi:
+        return "RestApi";
+
+      case RequestOrigin_Plugins:
+        return "Plugins";
+
+      case RequestOrigin_Lua:
+        return "Lua";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(LogLevel level)
+  {
+    switch (level)
+    {
+      case LogLevel_Error:
+        return "ERROR";
+
+      case LogLevel_Warning:
+        return "WARNING";
+
+      case LogLevel_Info:
+        return "INFO";
+
+      case LogLevel_Trace:
+        return "TRACE";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+        return "RGB24";
+
+      case PixelFormat_RGBA32:
+        return "RGBA32";
+
+      case PixelFormat_BGRA32:
+        return "BGRA32";
+
+      case PixelFormat_Grayscale8:
+        return "Grayscale (unsigned 8bpp)";
+
+      case PixelFormat_Grayscale16:
+        return "Grayscale (unsigned 16bpp)";
+
+      case PixelFormat_SignedGrayscale16:
+        return "Grayscale (signed 16bpp)";
+
+      case PixelFormat_Float32:
+        return "Grayscale (float 32bpp)";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  Encoding StringToEncoding(const char* encoding)
+  {
+    std::string s(encoding);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "UTF8")
+    {
+      return Encoding_Utf8;
+    }
+
+    if (s == "ASCII")
+    {
+      return Encoding_Ascii;
+    }
+
+    if (s == "LATIN1")
+    {
+      return Encoding_Latin1;
+    }
+
+    if (s == "LATIN2")
+    {
+      return Encoding_Latin2;
+    }
+
+    if (s == "LATIN3")
+    {
+      return Encoding_Latin3;
+    }
+
+    if (s == "LATIN4")
+    {
+      return Encoding_Latin4;
+    }
+
+    if (s == "LATIN5")
+    {
+      return Encoding_Latin5;
+    }
+
+    if (s == "CYRILLIC")
+    {
+      return Encoding_Cyrillic;
+    }
+
+    if (s == "WINDOWS1251")
+    {
+      return Encoding_Windows1251;
+    }
+
+    if (s == "ARABIC")
+    {
+      return Encoding_Arabic;
+    }
+
+    if (s == "GREEK")
+    {
+      return Encoding_Greek;
+    }
+
+    if (s == "HEBREW")
+    {
+      return Encoding_Hebrew;
+    }
+
+    if (s == "THAI")
+    {
+      return Encoding_Thai;
+    }
+
+    if (s == "JAPANESE")
+    {
+      return Encoding_Japanese;
+    }
+
+    if (s == "CHINESE")
+    {
+      return Encoding_Chinese;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ResourceType StringToResourceType(const char* type)
+  {
+    std::string s(type);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PATIENT" || s == "PATIENTS")
+    {
+      return ResourceType_Patient;
+    }
+    else if (s == "STUDY" || s == "STUDIES")
+    {
+      return ResourceType_Study;
+    }
+    else if (s == "SERIES")
+    {
+      return ResourceType_Series;
+    }
+    else if (s == "INSTANCE"  || s == "IMAGE" || 
+             s == "INSTANCES" || s == "IMAGES")
+    {
+      return ResourceType_Instance;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ImageFormat StringToImageFormat(const char* format)
+  {
+    std::string s(format);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PNG")
+    {
+      return ImageFormat_Png;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  LogLevel StringToLogLevel(const char *level)
+  {
+    if (strcmp(level, "ERROR") == 0)
+    {
+      return LogLevel_Error;
+    }
+    else if (strcmp(level, "WARNING") == 0)
+    {
+      return LogLevel_Warning;
+    }
+    else if (strcmp(level, "INFO") == 0)
+    {
+      return LogLevel_Info;
+    }
+    else if (strcmp(level, "TRACE") == 0)
+    {
+      return LogLevel_Trace;
+    }
+    else 
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported)
+  {
+    if (vr == "AE")
+    {
+      return ValueRepresentation_ApplicationEntity;
+    }
+    else if (vr == "AS")
+    {
+      return ValueRepresentation_AgeString;
+    }
+    else if (vr == "AT")
+    {
+      return ValueRepresentation_AttributeTag;
+    }
+    else if (vr == "CS")
+    {
+      return ValueRepresentation_CodeString;
+    }
+    else if (vr == "DA")
+    {
+      return ValueRepresentation_Date;
+    }
+    else if (vr == "DS")
+    {
+      return ValueRepresentation_DecimalString;
+    }
+    else if (vr == "DT")
+    {
+      return ValueRepresentation_DateTime;
+    }
+    else if (vr == "FL")
+    {
+      return ValueRepresentation_FloatingPointSingle;
+    }
+    else if (vr == "FD")
+    {
+      return ValueRepresentation_FloatingPointDouble;
+    }
+    else if (vr == "IS")
+    {
+      return ValueRepresentation_IntegerString;
+    }
+    else if (vr == "LO")
+    {
+      return ValueRepresentation_LongString;
+    }
+    else if (vr == "LT")
+    {
+      return ValueRepresentation_LongText;
+    }
+    else if (vr == "OB")
+    {
+      return ValueRepresentation_OtherByte;
+    }
+    else if (vr == "OD")
+    {
+      return ValueRepresentation_OtherDouble;
+    }
+    else if (vr == "OF")
+    {
+      return ValueRepresentation_OtherFloat;
+    }
+    else if (vr == "OL")
+    {
+      return ValueRepresentation_OtherLong;
+    }
+    else if (vr == "OW")
+    {
+      return ValueRepresentation_OtherWord;
+    }
+    else if (vr == "PN")
+    {
+      return ValueRepresentation_PersonName;
+    }
+    else if (vr == "SH")
+    {
+      return ValueRepresentation_ShortString;
+    }
+    else if (vr == "SL")
+    {
+      return ValueRepresentation_SignedLong;
+    }
+    else if (vr == "SQ")
+    {
+      return ValueRepresentation_Sequence;
+    }
+    else if (vr == "SS")
+    {
+      return ValueRepresentation_SignedShort;
+    }
+    else if (vr == "ST")
+    {
+      return ValueRepresentation_ShortText;
+    }
+    else if (vr == "TM")
+    {
+      return ValueRepresentation_Time;
+    }
+    else if (vr == "UC")
+    {
+      return ValueRepresentation_UnlimitedCharacters;
+    }
+    else if (vr == "UI")
+    {
+      return ValueRepresentation_UniqueIdentifier;
+    }
+    else if (vr == "UL")
+    {
+      return ValueRepresentation_UnsignedLong;
+    }
+    else if (vr == "UN")
+    {
+      return ValueRepresentation_Unknown;
+    }
+    else if (vr == "UR")
+    {
+      return ValueRepresentation_UniversalResource;
+    }
+    else if (vr == "US")
+    {
+      return ValueRepresentation_UnsignedShort;
+    }
+    else if (vr == "UT")
+    {
+      return ValueRepresentation_UnlimitedText;
+    }
+    else
+    {
+      std::string s = "Unsupported value representation encountered: " + vr;
+
+      if (throwIfUnsupported)
+      {
+        LOG(ERROR) << s;
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        LOG(INFO) << s;
+        return ValueRepresentation_NotSupported;
+      }
+    }
+  }
+
+
+  unsigned int GetBytesPerPixel(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_Grayscale8:
+        return 1;
+
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        return 2;
+
+      case PixelFormat_RGB24:
+        return 3;
+
+      case PixelFormat_RGBA32:
+      case PixelFormat_BGRA32:
+        return 4;
+
+      case PixelFormat_Float32:
+        assert(sizeof(float) == 4);
+        return 4;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet)
+  {
+    std::string s = specificCharacterSet;
+    Toolbox::ToUpperCase(s);
+
+    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
+    if (s == "ISO_IR 6" ||
+        s == "ISO_IR 192" ||
+        s == "ISO 2022 IR 6")
+    {
+      encoding = Encoding_Utf8;
+    }
+    else if (s == "ISO_IR 100" ||
+             s == "ISO 2022 IR 100")
+    {
+      encoding = Encoding_Latin1;
+    }
+    else if (s == "ISO_IR 101" ||
+             s == "ISO 2022 IR 101")
+    {
+      encoding = Encoding_Latin2;
+    }
+    else if (s == "ISO_IR 109" ||
+             s == "ISO 2022 IR 109")
+    {
+      encoding = Encoding_Latin3;
+    }
+    else if (s == "ISO_IR 110" ||
+             s == "ISO 2022 IR 110")
+    {
+      encoding = Encoding_Latin4;
+    }
+    else if (s == "ISO_IR 148" ||
+             s == "ISO 2022 IR 148")
+    {
+      encoding = Encoding_Latin5;
+    }
+    else if (s == "ISO_IR 144" ||
+             s == "ISO 2022 IR 144")
+    {
+      encoding = Encoding_Cyrillic;
+    }
+    else if (s == "ISO_IR 127" ||
+             s == "ISO 2022 IR 127")
+    {
+      encoding = Encoding_Arabic;
+    }
+    else if (s == "ISO_IR 126" ||
+             s == "ISO 2022 IR 126")
+    {
+      encoding = Encoding_Greek;
+    }
+    else if (s == "ISO_IR 138" ||
+             s == "ISO 2022 IR 138")
+    {
+      encoding = Encoding_Hebrew;
+    }
+    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
+    {
+      encoding = Encoding_Thai;
+    }
+    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
+    {
+      encoding = Encoding_Japanese;
+    }
+    else if (s == "GB18030")
+    {
+      encoding = Encoding_Chinese;
+    }
+    /*
+      else if (s == "ISO 2022 IR 149")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 159")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 87")
+      {
+      TODO
+      }
+    */
+    else
+    {
+      return false;
+    }
+
+    // The encoding was properly detected
+    return true;
+  }
+
+
+  ResourceType GetChildResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return ResourceType_Study;
+
+      case ResourceType_Study:
+        return ResourceType_Series;
+        
+      case ResourceType_Series:
+        return ResourceType_Instance;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ResourceType GetParentResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Study:
+        return ResourceType_Patient;
+        
+      case ResourceType_Series:
+        return ResourceType_Study;
+
+      case ResourceType_Instance:
+        return ResourceType_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  DicomModule GetModule(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return DicomModule_Patient;
+
+      case ResourceType_Study:
+        return DicomModule_Study;
+        
+      case ResourceType_Series:
+        return DicomModule_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+
+  const char* GetDicomSpecificCharacterSet(Encoding encoding)
+  {
+    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+    switch (encoding)
+    {
+      case Encoding_Utf8:
+      case Encoding_Ascii:
+        return "ISO_IR 192";
+
+      case Encoding_Latin1:
+        return "ISO_IR 100";
+
+      case Encoding_Latin2:
+        return "ISO_IR 101";
+
+      case Encoding_Latin3:
+        return "ISO_IR 109";
+
+      case Encoding_Latin4:
+        return "ISO_IR 110";
+
+      case Encoding_Latin5:
+        return "ISO_IR 148";
+
+      case Encoding_Cyrillic:
+        return "ISO_IR 144";
+
+      case Encoding_Arabic:
+        return "ISO_IR 127";
+
+      case Encoding_Greek:
+        return "ISO_IR 126";
+
+      case Encoding_Hebrew:
+        return "ISO_IR 138";
+
+      case Encoding_Japanese:
+        return "ISO_IR 13";
+
+      case Encoding_Chinese:
+        return "GB18030";
+
+      case Encoding_Thai:
+        return "ISO_IR 166";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  // This function is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error)
+  {
+    switch (error)
+    {
+      case ErrorCode_Success:
+        return HttpStatus_200_Ok;
+
+      case ErrorCode_ParameterOutOfRange:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_BadParameterType:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_InexistentItem:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadRequest:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_UriSyntax:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_InexistentFile:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadFileFormat:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_UnknownResource:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_InexistentTag:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadJson:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_Unauthorized:
+        return HttpStatus_401_Unauthorized;
+
+      case ErrorCode_NotAcceptable:
+        return HttpStatus_406_NotAcceptable;
+
+      default:
+        return HttpStatus_500_InternalServerError;
+    }
+  }
+
+
+  bool IsUserContentType(FileContentType type)
+  {
+    return (type >= FileContentType_StartUser &&
+            type <= FileContentType_EndUser);
+  }
+
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr)
+  {
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:     // AE
+      case ValueRepresentation_AgeString:             // AS
+      case ValueRepresentation_CodeString:            // CS
+      case ValueRepresentation_Date:                  // DA
+      case ValueRepresentation_DecimalString:         // DS
+      case ValueRepresentation_DateTime:              // DT
+      case ValueRepresentation_IntegerString:         // IS
+      case ValueRepresentation_LongString:            // LO
+      case ValueRepresentation_LongText:              // LT
+      case ValueRepresentation_PersonName:            // PN
+      case ValueRepresentation_ShortString:           // SH
+      case ValueRepresentation_ShortText:             // ST
+      case ValueRepresentation_Time:                  // TM
+      case ValueRepresentation_UnlimitedCharacters:   // UC
+      case ValueRepresentation_UniqueIdentifier:      // UI (UID)
+      case ValueRepresentation_UniversalResource:     // UR (URI or URL)
+      case ValueRepresentation_UnlimitedText:         // UT
+      {
+        return false;
+      }
+
+      /**
+       * Below are all the VR whose character repertoire is tagged as
+       * "not applicable"
+       **/
+      case ValueRepresentation_AttributeTag:          // AT (2 x uint16_t)
+      case ValueRepresentation_FloatingPointSingle:   // FL (float)
+      case ValueRepresentation_FloatingPointDouble:   // FD (double)
+      case ValueRepresentation_OtherByte:             // OB
+      case ValueRepresentation_OtherDouble:           // OD
+      case ValueRepresentation_OtherFloat:            // OF
+      case ValueRepresentation_OtherLong:             // OL
+      case ValueRepresentation_OtherWord:             // OW
+      case ValueRepresentation_SignedLong:            // SL (int32_t)
+      case ValueRepresentation_Sequence:              // SQ
+      case ValueRepresentation_SignedShort:           // SS (int16_t)
+      case ValueRepresentation_UnsignedLong:          // UL (uint32_t)
+      case ValueRepresentation_Unknown:               // UN
+      case ValueRepresentation_UnsignedShort:         // US (uint16_t)
+      {
+        return true;
+      }
+
+      case ValueRepresentation_NotSupported:
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/Enumerations.h b/Framework/Orthanc/Core/Enumerations.h
new file mode 100644
index 0000000..50f64c7
--- /dev/null
+++ b/Framework/Orthanc/Core/Enumerations.h
@@ -0,0 +1,543 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+
+namespace Orthanc
+{
+  enum Endianness
+  {
+    Endianness_Unknown,
+    Endianness_Big,
+    Endianness_Little
+  };
+
+  // This enumeration is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  enum ErrorCode
+  {
+    ErrorCode_InternalError = -1    /*!< Internal error */,
+    ErrorCode_Success = 0    /*!< Success */,
+    ErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    ErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    ErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    ErrorCode_NotEnoughMemory = 4    /*!< Not enough memory */,
+    ErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    ErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    ErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    ErrorCode_BadRequest = 8    /*!< Bad request */,
+    ErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    ErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    ErrorCode_Database = 11    /*!< Error with the database engine */,
+    ErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    ErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    ErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    ErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    ErrorCode_Timeout = 16    /*!< Timeout */,
+    ErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    ErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    ErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    ErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    ErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    ErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    ErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    ErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    ErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    ErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    ErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    ErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    ErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    ErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    ErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    ErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    ErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    ErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    ErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    ErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    ErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    ErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    ErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    ErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    ErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    ErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    ErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    ErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    ErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    ErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    ErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    ErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    ErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    ErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    ErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    ErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    ErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    ErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
+    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
+    ErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    ErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    ErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    ErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    ErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    ErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    ErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    ErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    ErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    ErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    ErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    ErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    ErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    ErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    ErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    ErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    ErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    ErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    ErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    ErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    ErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    ErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    ErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    ErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    ErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    ErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    ErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    ErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    ErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    ErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    ErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    ErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    ErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+    ErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
+    ErrorCode_START_PLUGINS = 1000000
+  };
+
+  enum LogLevel
+  {
+    LogLevel_Error,
+    LogLevel_Warning,
+    LogLevel_Info,
+    LogLevel_Trace
+  };
+
+
+  /**
+   * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.}
+   **/
+  enum PixelFormat
+  {
+    /**
+     * {summary}{Color image in RGB24 format.}
+     * {description}{This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.}
+     **/
+    PixelFormat_RGB24 = 1,
+
+    /**
+     * {summary}{Color image in RGBA32 format.}
+     * {description}{This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.}
+     **/
+    PixelFormat_RGBA32 = 2,
+
+    /**
+     * {summary}{Graylevel 8bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.}
+     **/
+    PixelFormat_Grayscale8 = 3,
+      
+    /**
+     * {summary}{Graylevel, unsigned 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.}
+     **/
+    PixelFormat_Grayscale16 = 4,
+      
+    /**
+     * {summary}{Graylevel, signed 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.}
+     **/
+    PixelFormat_SignedGrayscale16 = 5,
+      
+    /**
+     * {summary}{Graylevel, floating-point image.}
+     * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.}
+     **/
+    PixelFormat_Float32 = 6,
+
+    // This is the memory layout for Cairo
+    PixelFormat_BGRA32 = 7
+  };
+
+
+  /**
+   * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.}
+   **/
+  enum ImageExtractionMode
+  {
+    /**
+     * {summary}{Rescaled to 8bpp.}
+     * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.}
+     **/
+    ImageExtractionMode_Preview = 1,
+
+    /**
+     * {summary}{Truncation to the [0, 255] range.}
+     **/
+    ImageExtractionMode_UInt8 = 2,
+
+    /**
+     * {summary}{Truncation to the [0, 65535] range.}
+     **/
+    ImageExtractionMode_UInt16 = 3,
+
+    /**
+     * {summary}{Truncation to the [-32768, 32767] range.}
+     **/
+    ImageExtractionMode_Int16 = 4
+  };
+
+
+  /**
+   * Most common, non-joke and non-experimental HTTP status codes
+   * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+   **/
+  enum HttpStatus
+  {
+    HttpStatus_None = -1,
+
+    // 1xx Informational
+    HttpStatus_100_Continue = 100,
+    HttpStatus_101_SwitchingProtocols = 101,
+    HttpStatus_102_Processing = 102,
+
+    // 2xx Success
+    HttpStatus_200_Ok = 200,
+    HttpStatus_201_Created = 201,
+    HttpStatus_202_Accepted = 202,
+    HttpStatus_203_NonAuthoritativeInformation = 203,
+    HttpStatus_204_NoContent = 204,
+    HttpStatus_205_ResetContent = 205,
+    HttpStatus_206_PartialContent = 206,
+    HttpStatus_207_MultiStatus = 207,
+    HttpStatus_208_AlreadyReported = 208,
+    HttpStatus_226_IMUsed = 226,
+
+    // 3xx Redirection
+    HttpStatus_300_MultipleChoices = 300,
+    HttpStatus_301_MovedPermanently = 301,
+    HttpStatus_302_Found = 302,
+    HttpStatus_303_SeeOther = 303,
+    HttpStatus_304_NotModified = 304,
+    HttpStatus_305_UseProxy = 305,
+    HttpStatus_307_TemporaryRedirect = 307,
+
+    // 4xx Client Error
+    HttpStatus_400_BadRequest = 400,
+    HttpStatus_401_Unauthorized = 401,
+    HttpStatus_402_PaymentRequired = 402,
+    HttpStatus_403_Forbidden = 403,
+    HttpStatus_404_NotFound = 404,
+    HttpStatus_405_MethodNotAllowed = 405,
+    HttpStatus_406_NotAcceptable = 406,
+    HttpStatus_407_ProxyAuthenticationRequired = 407,
+    HttpStatus_408_RequestTimeout = 408,
+    HttpStatus_409_Conflict = 409,
+    HttpStatus_410_Gone = 410,
+    HttpStatus_411_LengthRequired = 411,
+    HttpStatus_412_PreconditionFailed = 412,
+    HttpStatus_413_RequestEntityTooLarge = 413,
+    HttpStatus_414_RequestUriTooLong = 414,
+    HttpStatus_415_UnsupportedMediaType = 415,
+    HttpStatus_416_RequestedRangeNotSatisfiable = 416,
+    HttpStatus_417_ExpectationFailed = 417,
+    HttpStatus_422_UnprocessableEntity = 422,
+    HttpStatus_423_Locked = 423,
+    HttpStatus_424_FailedDependency = 424,
+    HttpStatus_426_UpgradeRequired = 426,
+
+    // 5xx Server Error
+    HttpStatus_500_InternalServerError = 500,
+    HttpStatus_501_NotImplemented = 501,
+    HttpStatus_502_BadGateway = 502,
+    HttpStatus_503_ServiceUnavailable = 503,
+    HttpStatus_504_GatewayTimeout = 504,
+    HttpStatus_505_HttpVersionNotSupported = 505,
+    HttpStatus_506_VariantAlsoNegotiates = 506,
+    HttpStatus_507_InsufficientStorage = 507,
+    HttpStatus_509_BandwidthLimitExceeded = 509,
+    HttpStatus_510_NotExtended = 510
+  };
+
+
+  enum HttpMethod
+  {
+    HttpMethod_Get = 0,
+    HttpMethod_Post = 1,
+    HttpMethod_Delete = 2,
+    HttpMethod_Put = 3
+  };
+
+
+  enum ImageFormat
+  {
+    ImageFormat_Png = 1
+  };
+
+
+  // https://en.wikipedia.org/wiki/HTTP_compression
+  enum HttpCompression
+  {
+    HttpCompression_None,
+    HttpCompression_Deflate,
+    HttpCompression_Gzip
+  };
+
+
+  // Specific Character Sets
+  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+  enum Encoding
+  {
+    Encoding_Ascii,
+    Encoding_Utf8,
+    Encoding_Latin1,
+    Encoding_Latin2,
+    Encoding_Latin3,
+    Encoding_Latin4,
+    Encoding_Latin5,                        // Turkish
+    Encoding_Cyrillic,
+    Encoding_Windows1251,                   // Windows-1251 (commonly used for Cyrillic)
+    Encoding_Arabic,
+    Encoding_Greek,
+    Encoding_Hebrew,
+    Encoding_Thai,                          // TIS 620-2533
+    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
+    Encoding_Chinese                        // GB18030 - Chinese simplified
+    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
+    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
+    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
+  };
+
+
+  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.2
+  enum PhotometricInterpretation
+  {
+    PhotometricInterpretation_ARGB,  // Retired
+    PhotometricInterpretation_CMYK,  // Retired
+    PhotometricInterpretation_HSV,   // Retired
+    PhotometricInterpretation_Monochrome1,
+    PhotometricInterpretation_Monochrome2,
+    PhotometricInterpretation_Palette,
+    PhotometricInterpretation_RGB,
+    PhotometricInterpretation_YBRFull,
+    PhotometricInterpretation_YBRFull422,
+    PhotometricInterpretation_YBRPartial420,
+    PhotometricInterpretation_YBRPartial422,
+    PhotometricInterpretation_YBR_ICT,
+    PhotometricInterpretation_YBR_RCT,
+    PhotometricInterpretation_Unknown
+  };
+
+  enum DicomModule
+  {
+    DicomModule_Patient,
+    DicomModule_Study,
+    DicomModule_Series,
+    DicomModule_Instance,
+    DicomModule_Image
+  };
+
+  enum RequestOrigin
+  {
+    RequestOrigin_Unknown,
+    RequestOrigin_DicomProtocol,
+    RequestOrigin_RestApi,
+    RequestOrigin_Plugins,
+    RequestOrigin_Lua
+  };
+
+  enum ServerBarrierEvent
+  {
+    ServerBarrierEvent_Stop,
+    ServerBarrierEvent_Reload  // SIGHUP signal: reload configuration file
+  };
+
+  enum FileMode
+  {
+    FileMode_ReadBinary,
+    FileMode_WriteBinary
+  };
+
+  /**
+   * The value representations Orthanc knows about. They correspond to
+   * the DICOM 2016b version of the standard.
+   * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+   **/
+  enum ValueRepresentation
+  {
+    ValueRepresentation_ApplicationEntity = 1,     // AE
+    ValueRepresentation_AgeString = 2,             // AS
+    ValueRepresentation_AttributeTag = 3,          // AT (2 x uint16_t)
+    ValueRepresentation_CodeString = 4,            // CS
+    ValueRepresentation_Date = 5,                  // DA
+    ValueRepresentation_DecimalString = 6,         // DS
+    ValueRepresentation_DateTime = 7,              // DT
+    ValueRepresentation_FloatingPointSingle = 8,   // FL (float)
+    ValueRepresentation_FloatingPointDouble = 9,   // FD (double)
+    ValueRepresentation_IntegerString = 10,        // IS
+    ValueRepresentation_LongString = 11,           // LO
+    ValueRepresentation_LongText = 12,             // LT
+    ValueRepresentation_OtherByte = 13,            // OB
+    ValueRepresentation_OtherDouble = 14,          // OD
+    ValueRepresentation_OtherFloat = 15,           // OF
+    ValueRepresentation_OtherLong = 16,            // OL
+    ValueRepresentation_OtherWord = 17,            // OW
+    ValueRepresentation_PersonName = 18,           // PN
+    ValueRepresentation_ShortString = 19,          // SH
+    ValueRepresentation_SignedLong = 20,           // SL (int32_t)
+    ValueRepresentation_Sequence = 21,             // SQ
+    ValueRepresentation_SignedShort = 22,          // SS (int16_t)
+    ValueRepresentation_ShortText = 23,            // ST
+    ValueRepresentation_Time = 24,                 // TM
+    ValueRepresentation_UnlimitedCharacters = 25,  // UC
+    ValueRepresentation_UniqueIdentifier = 26,     // UI (UID)
+    ValueRepresentation_UnsignedLong = 27,         // UL (uint32_t)
+    ValueRepresentation_Unknown = 28,              // UN
+    ValueRepresentation_UniversalResource = 29,    // UR (URI or URL)
+    ValueRepresentation_UnsignedShort = 30,        // US (uint16_t)
+    ValueRepresentation_UnlimitedText = 31,        // UT
+    ValueRepresentation_NotSupported               // Not supported by Orthanc, or tag not in dictionary
+  };
+
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum CompressionType
+  {
+    /**
+     * Buffer/file that is stored as-is, in a raw fashion, without
+     * compression.
+     **/
+    CompressionType_None = 1,
+
+    /**
+     * Buffer that is compressed using the "deflate" algorithm (RFC
+     * 1951), wrapped inside the zlib data format (RFC 1950), prefixed
+     * with a "uint64_t" (8 bytes) that encodes the size of the
+     * uncompressed buffer. If the compressed buffer is empty, its
+     * represents an empty uncompressed buffer. This format is
+     * internal to Orthanc. If the 8 first bytes are skipped AND the
+     * buffer is non-empty, the buffer is compatible with the
+     * "deflate" HTTP compression.
+     **/
+    CompressionType_ZlibWithSize = 2
+  };
+
+  enum FileContentType
+  {
+    // If you add a value below, insert it in "PluginStorageArea" in
+    // the file "Plugins/Engine/OrthancPlugins.cpp"
+    FileContentType_Unknown = 0,
+    FileContentType_Dicom = 1,
+    FileContentType_DicomAsJson = 2,
+
+    // Make sure that the value "65535" can be stored into this enumeration
+    FileContentType_StartUser = 1024,
+    FileContentType_EndUser = 65535
+  };
+
+  enum ResourceType
+  {
+    ResourceType_Patient = 1,
+    ResourceType_Study = 2,
+    ResourceType_Series = 3,
+    ResourceType_Instance = 4
+  };
+
+
+  const char* EnumerationToString(ErrorCode code);
+
+  const char* EnumerationToString(HttpMethod method);
+
+  const char* EnumerationToString(HttpStatus status);
+
+  const char* EnumerationToString(ResourceType type);
+
+  const char* EnumerationToString(ImageFormat format);
+
+  const char* EnumerationToString(Encoding encoding);
+
+  const char* EnumerationToString(PhotometricInterpretation photometric);
+
+  const char* EnumerationToString(LogLevel level);
+
+  const char* EnumerationToString(RequestOrigin origin);
+
+  const char* EnumerationToString(PixelFormat format);
+
+  Encoding StringToEncoding(const char* encoding);
+
+  ResourceType StringToResourceType(const char* type);
+
+  ImageFormat StringToImageFormat(const char* format);
+
+  LogLevel StringToLogLevel(const char* level);
+
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported);
+
+  unsigned int GetBytesPerPixel(PixelFormat format);
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet);
+
+  ResourceType GetChildResourceType(ResourceType type);
+
+  ResourceType GetParentResourceType(ResourceType type);
+
+  DicomModule GetModule(ResourceType type);
+
+  const char* GetDicomSpecificCharacterSet(Encoding encoding);
+
+  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error);
+
+  bool IsUserContentType(FileContentType type);
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr);
+}
diff --git a/Framework/Orthanc/Core/HttpClient.cpp b/Framework/Orthanc/Core/HttpClient.cpp
new file mode 100644
index 0000000..f028d30
--- /dev/null
+++ b/Framework/Orthanc/Core/HttpClient.cpp
@@ -0,0 +1,836 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "HttpClient.h"
+
+#include "Toolbox.h"
+#include "OrthancException.h"
+#include "Logging.h"
+#include "ChunkedBuffer.h"
+
+#include <string.h>
+#include <curl/curl.h>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/thread/mutex.hpp>
+
+
+#if ORTHANC_SSL_ENABLED == 1
+// For OpenSSL initialization and finalization
+#  include <openssl/conf.h>
+#  include <openssl/engine.h>
+#  include <openssl/err.h>
+#  include <openssl/evp.h>
+#  include <openssl/ssl.h>
+#endif
+
+
+#if ORTHANC_PKCS11_ENABLED == 1
+#  include "Pkcs11.h"
+#endif
+
+
+extern "C"
+{
+  static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status)
+  {
+    if (code == CURLE_OK)
+    {
+      code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status);
+      return code;
+    }
+    else
+    {
+      *status = 0;
+      return code;
+    }
+  }
+
+  // This is a dummy wrapper function to suppress any OpenSSL-related
+  // problem in valgrind. Inlining is prevented.
+#if defined(__GNUC__) || defined(__clang__)
+    __attribute__((noinline)) 
+#endif
+    static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status)
+  {
+    return GetHttpStatus(curl_easy_perform(curl), curl, status);
+  }
+}
+
+
+
+namespace Orthanc
+{
+  class HttpClient::GlobalParameters
+  {
+  private:
+    boost::mutex    mutex_;
+    bool            httpsVerifyPeers_;
+    std::string     httpsCACertificates_;
+    std::string     proxy_;
+    long            timeout_;
+
+    GlobalParameters() : 
+      httpsVerifyPeers_(true),
+      timeout_(0)
+    {
+    }
+
+  public:
+    // Singleton pattern
+    static GlobalParameters& GetInstance()
+    {
+      static GlobalParameters parameters;
+      return parameters;
+    }
+
+    void ConfigureSsl(bool httpsVerifyPeers,
+                      const std::string& httpsCACertificates)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      httpsVerifyPeers_ = httpsVerifyPeers;
+      httpsCACertificates_ = httpsCACertificates;
+    }
+
+    void GetSslConfiguration(bool& httpsVerifyPeers,
+                             std::string& httpsCACertificates)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      httpsVerifyPeers = httpsVerifyPeers_;
+      httpsCACertificates = httpsCACertificates_;
+    }
+
+    void SetDefaultProxy(const std::string& proxy)
+    {
+      LOG(INFO) << "Setting the default proxy for HTTP client connections: " << proxy;
+
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        proxy_ = proxy;
+      }
+    }
+
+    void GetDefaultProxy(std::string& target)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      target = proxy_;
+    }
+
+    void SetDefaultTimeout(long seconds)
+    {
+      LOG(INFO) << "Setting the default timeout for HTTP client connections: " << seconds << " seconds";
+
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        timeout_ = seconds;
+      }
+    }
+
+    long GetDefaultTimeout()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      return timeout_;
+    }
+
+#if ORTHANC_PKCS11_ENABLED == 1
+    bool IsPkcs11Initialized()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      return Pkcs11::IsInitialized();
+    }
+
+    void InitializePkcs11(const std::string& module,
+                          const std::string& pin,
+                          bool verbose)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      Pkcs11::Initialize(module, pin, verbose);
+    }
+#endif
+  };
+
+
+  struct HttpClient::PImpl
+  {
+    CURL* curl_;
+    struct curl_slist *defaultPostHeaders_;
+    struct curl_slist *userHeaders_;
+  };
+
+
+  static void ThrowException(HttpStatus status)
+  {
+    switch (status)
+    {
+      case HttpStatus_400_BadRequest:
+        throw OrthancException(ErrorCode_BadRequest);
+
+      case HttpStatus_401_Unauthorized:
+      case HttpStatus_403_Forbidden:
+        throw OrthancException(ErrorCode_Unauthorized);
+
+      case HttpStatus_404_NotFound:
+        throw OrthancException(ErrorCode_UnknownResource);
+
+      default:
+        throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+
+  static CURLcode CheckCode(CURLcode code)
+  {
+    if (code == CURLE_NOT_BUILT_IN)
+    {
+      LOG(ERROR) << "Your libcurl does not contain a required feature, "
+                 << "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF";
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (code != CURLE_OK)
+    {
+      LOG(ERROR) << "libCURL error: " + std::string(curl_easy_strerror(code));
+      throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+
+    return code;
+  }
+
+
+  static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload)
+  {
+    ChunkedBuffer& target = *(static_cast<ChunkedBuffer*>(payload));
+
+    size_t length = size * nmemb;
+    if (length == 0)
+    {
+      return 0;
+    }
+    else
+    {
+      target.AddChunk(buffer, length);
+      return length;
+    }
+  }
+
+
+  struct CurlHeaderParameters
+  {
+    bool lowerCase_;
+    HttpClient::HttpHeaders* headers_;
+  };
+
+
+  static size_t CurlHeaderCallback(void *buffer, size_t size, size_t nmemb, void *payload)
+  {
+    CurlHeaderParameters& parameters = *(static_cast<CurlHeaderParameters*>(payload));
+    assert(parameters.headers_ != NULL);
+
+    size_t length = size * nmemb;
+    if (length == 0)
+    {
+      return 0;
+    }
+    else
+    {
+      std::string s(reinterpret_cast<const char*>(buffer), length);
+      std::size_t colon = s.find(':');
+      std::size_t eol = s.find("\r\n");
+      if (colon != std::string::npos &&
+          eol != std::string::npos)
+      {
+        std::string tmp(s.substr(0, colon));
+
+        if (parameters.lowerCase_)
+        {
+          Toolbox::ToLowerCase(tmp);
+        }
+
+        std::string key = Toolbox::StripSpaces(tmp);
+
+        if (!key.empty())
+        {
+          std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol));
+          (*parameters.headers_) [key] = value;
+        }
+      }
+
+      return length;
+    }
+  }
+
+
+  void HttpClient::Setup()
+  {
+    pimpl_->userHeaders_ = NULL;
+    pimpl_->defaultPostHeaders_ = NULL;
+    if ((pimpl_->defaultPostHeaders_ = curl_slist_append(pimpl_->defaultPostHeaders_, "Expect:")) == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    pimpl_->curl_ = curl_easy_init();
+    if (!pimpl_->curl_)
+    {
+      curl_slist_free_all(pimpl_->defaultPostHeaders_);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1));
+
+    // This fixes the "longjmp causes uninitialized stack frame" crash
+    // that happens on modern Linux versions.
+    // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1));
+
+    url_ = "";
+    method_ = HttpMethod_Get;
+    lastStatus_ = HttpStatus_200_Ok;
+    isVerbose_ = false;
+    timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout();
+    GlobalParameters::GetInstance().GetDefaultProxy(proxy_);
+    GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_);
+  }
+
+
+  HttpClient::HttpClient() : 
+    pimpl_(new PImpl), 
+    verifyPeers_(true),
+    pkcs11Enabled_(false),
+    headersToLowerCase_(true),
+    redirectionFollowed_(true)
+  {
+    Setup();
+  }
+
+
+  HttpClient::HttpClient(const WebServiceParameters& service,
+                         const std::string& uri) : 
+    pimpl_(new PImpl), 
+    verifyPeers_(true),
+    headersToLowerCase_(true),
+    redirectionFollowed_(true)
+  {
+    Setup();
+
+    if (service.GetUsername().size() != 0 && 
+        service.GetPassword().size() != 0)
+    {
+      SetCredentials(service.GetUsername().c_str(), 
+                     service.GetPassword().c_str());
+    }
+
+    if (!service.GetCertificateFile().empty())
+    {
+      SetClientCertificate(service.GetCertificateFile(),
+                           service.GetCertificateKeyFile(),
+                           service.GetCertificateKeyPassword());
+    }
+
+    SetPkcs11Enabled(service.IsPkcs11Enabled());
+
+    SetUrl(service.GetUrl() + uri);
+  }
+
+
+  HttpClient::~HttpClient()
+  {
+    curl_easy_cleanup(pimpl_->curl_);
+    curl_slist_free_all(pimpl_->defaultPostHeaders_);
+    ClearHeaders();
+  }
+
+
+  void HttpClient::SetVerbose(bool isVerbose)
+  {
+    isVerbose_ = isVerbose;
+
+    if (isVerbose_)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1));
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0));
+    }
+  }
+
+
+  void HttpClient::AddHeader(const std::string& key,
+                             const std::string& value)
+  {
+    if (key.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    std::string s = key + ": " + value;
+
+    if ((pimpl_->userHeaders_ = curl_slist_append(pimpl_->userHeaders_, s.c_str())) == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
+
+  void HttpClient::ClearHeaders()
+  {
+    if (pimpl_->userHeaders_ != NULL)
+    {
+      curl_slist_free_all(pimpl_->userHeaders_);
+      pimpl_->userHeaders_ = NULL;
+    }
+  }
+
+
+  bool HttpClient::ApplyInternal(std::string& answerBody,
+                                 HttpHeaders* answerHeaders)
+  {
+    answerBody.clear();
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
+
+    CurlHeaderParameters headerParameters;
+
+    if (answerHeaders == NULL)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, NULL));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, NULL));
+    }
+    else
+    {
+      headerParameters.lowerCase_ = headersToLowerCase_;
+      headerParameters.headers_ = answerHeaders;
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters));
+    }
+
+#if ORTHANC_SSL_ENABLED == 1
+    // Setup HTTPS-related options
+
+    if (verifyPeers_)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2));  // libcurl default is strict verifyhost
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); 
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0)); 
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); 
+    }
+#endif
+
+    // Setup the HTTPS client certificate
+    if (!clientCertificateFile_.empty() &&
+        pkcs11Enabled_)
+    {
+      LOG(ERROR) << "Cannot enable both client certificates and PKCS#11 authentication";
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (pkcs11Enabled_)
+    {
+#if ORTHANC_PKCS11_ENABLED == 1
+      if (GlobalParameters::GetInstance().IsPkcs11Initialized())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier()));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG"));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG"));
+      }
+      else
+      {
+        LOG(ERROR) << "Cannot use PKCS#11 for a HTTPS request, because it has not been initialized";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+#else
+      LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11";
+      throw OrthancException(ErrorCode_InternalError);
+#endif
+    }
+    else if (!clientCertificateFile_.empty())
+    {
+#if ORTHANC_SSL_ENABLED == 1
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
+
+      if (!clientCertificateKeyPassword_.empty())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str()));
+      }
+
+      // NB: If no "clientKeyFile_" is provided, the key must be
+      // prepended to the certificate file
+      if (!clientCertificateKeyFile_.empty())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM"));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str()));
+      }
+#else
+      LOG(ERROR) << "This version of Orthanc is compiled without OpenSSL support, cannot use HTTPS client authentication";
+      throw OrthancException(ErrorCode_InternalError);
+#endif
+    }
+
+    // Reset the parameters from previous calls to Apply()
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL));
+
+    if (redirectionFollowed_)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1L));
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 0L));
+    }
+
+    // Set timeouts
+    if (timeout_ <= 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, 10));  /* default: 10 seconds */
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, 10));  /* default: 10 seconds */
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, timeout_));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, timeout_));
+    }
+
+    if (credentials_.size() != 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str()));
+    }
+
+    if (proxy_.size() != 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str()));
+    }
+
+    switch (method_)
+    {
+    case HttpMethod_Get:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L));
+      break;
+
+    case HttpMethod_Post:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L));
+
+      if (pimpl_->userHeaders_ == NULL)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_));
+      }
+
+      break;
+
+    case HttpMethod_Delete:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE"));
+      break;
+
+    case HttpMethod_Put:
+      // http://stackoverflow.com/a/7570281/881731: Don't use
+      // CURLOPT_PUT if there is a body
+
+      // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L));
+
+      curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */
+
+      if (pimpl_->userHeaders_ == NULL)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_));
+      }
+
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+
+    if (method_ == HttpMethod_Post ||
+        method_ == HttpMethod_Put)
+    {
+      if (body_.size() > 0)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str()));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size()));
+      }
+      else
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0));
+      }
+    }
+
+
+    // Do the actual request
+    CURLcode code;
+    long status = 0;
+
+    ChunkedBuffer buffer;
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &buffer));
+
+    if (boost::starts_with(url_, "https://"))
+    {
+      code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status);
+    }
+    else
+    {
+      code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status);
+    }
+
+    CheckCode(code);
+
+    if (status == 0)
+    {
+      // This corresponds to a call to an inexistent host
+      lastStatus_ = HttpStatus_500_InternalServerError;
+    }
+    else
+    {
+      lastStatus_ = static_cast<HttpStatus>(status);
+    }
+
+    bool success = (status >= 200 && status < 300);
+
+    if (success)
+    {
+      buffer.Flatten(answerBody);
+    }
+    else
+    {
+      answerBody.clear();
+      LOG(INFO) << "Error in HTTP request, received HTTP status " << status 
+                << " (" << EnumerationToString(lastStatus_) << ")";
+    }
+
+    return success;
+  }
+
+
+  bool HttpClient::ApplyInternal(Json::Value& answerBody,
+                                 HttpClient::HttpHeaders* answerHeaders)
+  {
+    std::string s;
+    if (ApplyInternal(s, answerHeaders))
+    {
+      Json::Reader reader;
+      return reader.parse(s, answerBody);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void HttpClient::SetCredentials(const char* username,
+                                  const char* password)
+  {
+    credentials_ = std::string(username) + ":" + std::string(password);
+  }
+
+
+  void HttpClient::ConfigureSsl(bool httpsVerifyPeers,
+                                const std::string& httpsVerifyCertificates)
+  {
+#if ORTHANC_SSL_ENABLED == 1
+    if (httpsVerifyPeers)
+    {
+      if (httpsVerifyCertificates.empty())
+      {
+        LOG(WARNING) << "No certificates are provided to validate peers, "
+                     << "set \"HttpsCACertificates\" if you need to do HTTPS requests";
+      }
+      else
+      {
+        LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << httpsVerifyCertificates;
+      }
+    }
+    else
+    {
+      LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled";
+    }
+#endif
+
+    GlobalParameters::GetInstance().ConfigureSsl(httpsVerifyPeers, httpsVerifyCertificates);
+  }
+
+  
+  void HttpClient::GlobalInitialize()
+  {
+#if ORTHANC_SSL_ENABLED == 1
+    CheckCode(curl_global_init(CURL_GLOBAL_ALL));
+#else
+    CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL));
+#endif
+  }
+
+
+  void HttpClient::GlobalFinalize()
+  {
+    curl_global_cleanup();
+
+#if ORTHANC_PKCS11_ENABLED == 1
+    Pkcs11::Finalize();
+#endif
+  }
+  
+
+  void HttpClient::SetDefaultProxy(const std::string& proxy)
+  {
+    GlobalParameters::GetInstance().SetDefaultProxy(proxy);
+  }
+
+
+  void HttpClient::SetDefaultTimeout(long timeout)
+  {
+    GlobalParameters::GetInstance().SetDefaultTimeout(timeout);
+  }
+
+
+  void HttpClient::ApplyAndThrowException(std::string& answerBody)
+  {
+    if (!Apply(answerBody))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+
+  
+  void HttpClient::ApplyAndThrowException(Json::Value& answerBody)
+  {
+    if (!Apply(answerBody))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+
+
+  void HttpClient::ApplyAndThrowException(std::string& answerBody,
+                                          HttpHeaders& answerHeaders)
+  {
+    if (!Apply(answerBody, answerHeaders))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+  
+
+  void HttpClient::ApplyAndThrowException(Json::Value& answerBody,
+                                          HttpHeaders& answerHeaders)
+  {
+    if (!Apply(answerBody, answerHeaders))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+
+
+  void HttpClient::SetClientCertificate(const std::string& certificateFile,
+                                        const std::string& certificateKeyFile,
+                                        const std::string& certificateKeyPassword)
+  {
+    if (certificateFile.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (!Toolbox::IsRegularFile(certificateFile))
+    {
+      LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    if (!certificateKeyFile.empty() && 
+        !Toolbox::IsRegularFile(certificateKeyFile))
+    {
+      LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    clientCertificateFile_ = certificateFile;
+    clientCertificateKeyFile_ = certificateKeyFile;
+    clientCertificateKeyPassword_ = certificateKeyPassword;
+  }
+
+
+  void HttpClient::InitializePkcs11(const std::string& module,
+                                    const std::string& pin,
+                                    bool verbose)
+  {
+#if ORTHANC_PKCS11_ENABLED == 1
+    LOG(INFO) << "Initializing PKCS#11 using " << module 
+              << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)");
+    GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose);    
+#else
+    LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11";
+    throw OrthancException(ErrorCode_InternalError);
+#endif
+  }
+
+
+  void HttpClient::InitializeOpenSsl()
+  {
+#if ORTHANC_SSL_ENABLED == 1
+    // https://wiki.openssl.org/index.php/Library_Initialization
+    SSL_library_init();
+    SSL_load_error_strings();
+    OpenSSL_add_all_algorithms();
+    ERR_load_crypto_strings();
+#endif
+  }
+
+
+  void HttpClient::FinalizeOpenSsl()
+  {
+ #if ORTHANC_SSL_ENABLED == 1
+    // Finalize OpenSSL
+    // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup
+    FIPS_mode_set(0);
+    ENGINE_cleanup();
+    CONF_modules_unload(1);
+    EVP_cleanup();
+    CRYPTO_cleanup_all_ex_data();
+    ERR_remove_state(0);
+    ERR_free_strings();
+#endif
+  }
+}
diff --git a/Framework/Orthanc/Core/HttpClient.h b/Framework/Orthanc/Core/HttpClient.h
new file mode 100644
index 0000000..c699dc9
--- /dev/null
+++ b/Framework/Orthanc/Core/HttpClient.h
@@ -0,0 +1,286 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Enumerations.h"
+#include "WebServiceParameters.h"
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class HttpClient
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+  private:
+    class GlobalParameters;
+
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    std::string url_;
+    std::string credentials_;
+    HttpMethod method_;
+    HttpStatus lastStatus_;
+    std::string body_;  // This only makes sense for POST and PUT requests
+    bool isVerbose_;
+    long timeout_;
+    std::string proxy_;
+    bool verifyPeers_;
+    std::string caCertificates_;
+    std::string clientCertificateFile_;
+    std::string clientCertificateKeyFile_;
+    std::string clientCertificateKeyPassword_;
+    bool pkcs11Enabled_;
+    bool headersToLowerCase_;
+    bool redirectionFollowed_;
+
+    void Setup();
+
+    void operator= (const HttpClient&);  // Assignment forbidden
+    HttpClient(const HttpClient& base);  // Copy forbidden
+
+    bool ApplyInternal(std::string& answerBody,
+                       HttpHeaders* answerHeaders);
+
+    bool ApplyInternal(Json::Value& answerBody,
+                       HttpHeaders* answerHeaders);
+
+  public:
+    HttpClient();
+
+    HttpClient(const WebServiceParameters& service,
+               const std::string& uri);
+
+    ~HttpClient();
+
+    void SetUrl(const char* url)
+    {
+      url_ = std::string(url);
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetMethod(HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    HttpMethod GetMethod() const
+    {
+      return method_;
+    }
+
+    void SetTimeout(long seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    long GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void SetBody(const std::string& data)
+    {
+      body_ = data;
+    }
+
+    std::string& GetBody()
+    {
+      return body_;
+    }
+
+    const std::string& GetBody() const
+    {
+      return body_;
+    }
+
+    void SetVerbose(bool isVerbose);
+
+    bool IsVerbose() const
+    {
+      return isVerbose_;
+    }
+
+    void AddHeader(const std::string& key,
+                   const std::string& value);
+
+    void ClearHeaders();
+
+    bool Apply(std::string& answerBody)
+    {
+      return ApplyInternal(answerBody, NULL);
+    }
+
+    bool Apply(Json::Value& answerBody)
+    {
+      return ApplyInternal(answerBody, NULL);
+    }
+
+    bool Apply(std::string& answerBody,
+               HttpHeaders& answerHeaders)
+    {
+      return ApplyInternal(answerBody, &answerHeaders);
+    }
+
+    bool Apply(Json::Value& answerBody,
+               HttpHeaders& answerHeaders)
+    {
+      return ApplyInternal(answerBody, &answerHeaders);
+    }
+
+    HttpStatus GetLastStatus() const
+    {
+      return lastStatus_;
+    }
+
+    void SetCredentials(const char* username,
+                        const char* password);
+
+    void SetProxy(const std::string& proxy)
+    {
+      proxy_ = proxy;
+    }
+
+    void SetHttpsVerifyPeers(bool verify)
+    {
+      verifyPeers_ = verify;
+    }
+
+    bool IsHttpsVerifyPeers() const
+    {
+      return verifyPeers_;
+    }
+
+    void SetHttpsCACertificates(const std::string& certificates)
+    {
+      caCertificates_ = certificates;
+    }
+
+    const std::string& GetHttpsCACertificates() const
+    {
+      return caCertificates_;
+    }
+
+    void SetClientCertificate(const std::string& certificateFile,
+                              const std::string& certificateKeyFile,
+                              const std::string& certificateKeyPassword);
+
+    void SetPkcs11Enabled(bool enabled)
+    {
+      pkcs11Enabled_ = enabled;
+    }
+
+    bool IsPkcs11Enabled() const
+    {
+      return pkcs11Enabled_;
+    }
+
+    const std::string& GetClientCertificateFile() const
+    {
+      return clientCertificateFile_;
+    }
+
+    const std::string& GetClientCertificateKeyFile() const
+    {
+      return clientCertificateKeyFile_;
+    }
+
+    const std::string& GetClientCertificateKeyPassword() const
+    {
+      return clientCertificateKeyPassword_;
+    }
+
+    void SetConvertHeadersToLowerCase(bool lowerCase)
+    {
+      headersToLowerCase_ = lowerCase;
+    }
+
+    bool IsConvertHeadersToLowerCase() const
+    {
+      return headersToLowerCase_;
+    }
+
+    void SetRedirectionFollowed(bool follow)
+    {
+      redirectionFollowed_ = follow;
+    }
+
+    bool IsRedirectionFollowed() const
+    {
+      return redirectionFollowed_;
+    }
+
+    static void GlobalInitialize();
+  
+    static void GlobalFinalize();
+
+    static void InitializeOpenSsl();
+
+    static void FinalizeOpenSsl();
+
+    static void InitializePkcs11(const std::string& module,
+                                 const std::string& pin,
+                                 bool verbose);
+
+    static void ConfigureSsl(bool httpsVerifyPeers,
+                             const std::string& httpsCACertificates);
+
+    static void SetDefaultProxy(const std::string& proxy);
+
+    static void SetDefaultTimeout(long timeout);
+
+    void ApplyAndThrowException(std::string& answerBody);
+
+    void ApplyAndThrowException(Json::Value& answerBody);
+
+    void ApplyAndThrowException(std::string& answerBody,
+                                HttpHeaders& answerHeaders);
+
+    void ApplyAndThrowException(Json::Value& answerBody,
+                                HttpHeaders& answerHeaders);
+  };
+}
diff --git a/Framework/Orthanc/Core/ICommand.h b/Framework/Orthanc/Core/ICommand.h
new file mode 100644
index 0000000..a0805b9
--- /dev/null
+++ b/Framework/Orthanc/Core/ICommand.h
@@ -0,0 +1,48 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IDynamicObject.h"
+
+namespace Orthanc
+{
+  /**
+   * This class is the base class for the "Command" design pattern.
+   * http://en.wikipedia.org/wiki/Command_pattern
+   **/
+  class ICommand : public IDynamicObject
+  {
+  public:
+    virtual bool Execute() = 0;
+  };
+}
diff --git a/Framework/Orthanc/Core/IDynamicObject.h b/Framework/Orthanc/Core/IDynamicObject.h
new file mode 100644
index 0000000..8c36617
--- /dev/null
+++ b/Framework/Orthanc/Core/IDynamicObject.h
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  /**
+   * This class should be the ancestor to any class whose type is
+   * determined at the runtime, and that can be dynamically allocated.
+   * Being a child of IDynamicObject only implies the existence of a
+   * virtual destructor.
+   **/
+  class IDynamicObject : public boost::noncopyable
+  {
+  public:
+    virtual ~IDynamicObject()
+    {
+    }
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/IImageWriter.cpp b/Framework/Orthanc/Core/Images/IImageWriter.cpp
new file mode 100644
index 0000000..096adb3
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/IImageWriter.cpp
@@ -0,0 +1,55 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "IImageWriter.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  void IImageWriter::WriteToFileInternal(const std::string& path,
+                                         unsigned int width,
+                                         unsigned int height,
+                                         unsigned int pitch,
+                                         PixelFormat format,
+                                         const void* buffer)
+  {
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    std::string compressed;
+    WriteToMemoryInternal(compressed, width, height, pitch, format, buffer);
+    Toolbox::WriteFile(compressed, path);
+#else
+    throw OrthancException(ErrorCode_CannotWriteFile);  // Unavailable in sandboxed environments
+#endif
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/IImageWriter.h b/Framework/Orthanc/Core/Images/IImageWriter.h
new file mode 100644
index 0000000..f2d1c97
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/IImageWriter.h
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ImageAccessor.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IImageWriter : public boost::noncopyable
+  {
+  protected:
+    virtual void WriteToMemoryInternal(std::string& compressed,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer) = 0;
+
+    virtual void WriteToFileInternal(const std::string& path,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     PixelFormat format,
+                                     const void* buffer);
+
+  public:
+    virtual ~IImageWriter()
+    {
+    }
+
+    virtual void WriteToMemory(std::string& compressed,
+                               const ImageAccessor& accessor)
+    {
+      WriteToMemoryInternal(compressed, accessor.GetWidth(), accessor.GetHeight(),
+                            accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+
+    virtual void WriteToFile(const std::string& path,
+                             const ImageAccessor& accessor)
+    {
+      WriteToFileInternal(path, accessor.GetWidth(), accessor.GetHeight(),
+                          accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/Image.cpp b/Framework/Orthanc/Core/Images/Image.cpp
new file mode 100644
index 0000000..4212f6d
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/Image.cpp
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Image.h"
+
+#include "ImageProcessing.h"
+
+#include <memory>
+
+namespace Orthanc
+{
+  Image::Image(PixelFormat format,
+               unsigned int width,
+               unsigned int height,
+               bool forceMinimalPitch) :
+    image_(format, width, height, forceMinimalPitch)
+  {
+    ImageAccessor accessor = image_.GetAccessor();
+    AssignWritable(format, width, height, accessor.GetPitch(), accessor.GetBuffer());
+  }
+
+
+  Image* Image::Clone(const ImageAccessor& source)
+  {
+    std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false));
+    ImageProcessing::Copy(*target, source);
+    return target.release();
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/Image.h b/Framework/Orthanc/Core/Images/Image.h
new file mode 100644
index 0000000..eba83c7
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/Image.h
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ImageAccessor.h"
+#include "ImageBuffer.h"
+
+namespace Orthanc
+{
+  class Image : public ImageAccessor
+  {
+  private:
+    ImageBuffer  image_;
+
+  public:
+    Image(PixelFormat format,
+          unsigned int width,
+          unsigned int height,
+          bool forceMinimalPitch);
+
+    static Image* Clone(const ImageAccessor& source);
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/ImageAccessor.cpp b/Framework/Orthanc/Core/Images/ImageAccessor.cpp
new file mode 100644
index 0000000..eff8623
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/ImageAccessor.cpp
@@ -0,0 +1,296 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "ImageAccessor.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
+
+#include <stdint.h>
+#include <cassert>
+#include <boost/lexical_cast.hpp>
+
+
+
+namespace Orthanc
+{
+  template <typename PixelType>
+  static void ToMatlabStringInternal(ChunkedBuffer& target,
+                                     const ImageAccessor& source)
+  {
+    target.AddChunk("double([ ");
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
+
+      std::string s;
+      if (y > 0)
+      {
+        s = "; ";
+      }
+
+      s.reserve(source.GetWidth() * 8);
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, p++)
+      {
+        s += boost::lexical_cast<std::string>(static_cast<double>(*p)) + " ";
+      }
+
+      target.AddChunk(s);
+    }
+
+    target.AddChunk("])");
+  }
+
+
+  static void RGB24ToMatlabString(ChunkedBuffer& target,
+                                  const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    target.AddChunk("double(permute(reshape([ ");
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+      
+      std::string s;
+      s.reserve(source.GetWidth() * 3 * 8);
+      
+      for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++)
+      {
+        s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " ";
+      }
+      
+      target.AddChunk(s);
+    }
+
+    target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) +
+                    " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))");
+  }
+
+
+  void* ImageAccessor::GetBuffer() const
+  {
+    if (readOnly_)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Trying to write on a read-only image";
+#endif
+
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    return buffer_;
+  }
+
+
+  const void* ImageAccessor::GetConstRow(unsigned int y) const
+  {
+    if (buffer_ != NULL)
+    {
+      return buffer_ + y * pitch_;
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void* ImageAccessor::GetRow(unsigned int y) const
+  {
+    if (readOnly_)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Trying to write on a read-only image";
+#endif
+
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    if (buffer_ != NULL)
+    {
+      return buffer_ + y * pitch_;
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void ImageAccessor::AssignEmpty(PixelFormat format)
+  {
+    readOnly_ = false;
+    format_ = format;
+    width_ = 0;
+    height_ = 0;
+    pitch_ = 0;
+    buffer_ = NULL;
+  }
+
+
+  void ImageAccessor::AssignReadOnly(PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     const void *buffer)
+  {
+    readOnly_ = true;
+    format_ = format;
+    width_ = width;
+    height_ = height;
+    pitch_ = pitch;
+    buffer_ = reinterpret_cast<uint8_t*>(const_cast<void*>(buffer));
+
+    if (GetBytesPerPixel() * width_ > pitch_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void ImageAccessor::AssignWritable(PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     void *buffer)
+  {
+    readOnly_ = false;
+    format_ = format;
+    width_ = width;
+    height_ = height;
+    pitch_ = pitch;
+    buffer_ = reinterpret_cast<uint8_t*>(buffer);
+
+    if (GetBytesPerPixel() * width_ > pitch_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void ImageAccessor::ToMatlabString(std::string& target) const
+  {
+    ChunkedBuffer buffer;
+
+    switch (GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        ToMatlabStringInternal<uint8_t>(buffer, *this);
+        break;
+
+      case PixelFormat_Grayscale16:
+        ToMatlabStringInternal<uint16_t>(buffer, *this);
+        break;
+
+      case PixelFormat_SignedGrayscale16:
+        ToMatlabStringInternal<int16_t>(buffer, *this);
+        break;
+
+      case PixelFormat_Float32:
+        ToMatlabStringInternal<float>(buffer, *this);
+        break;
+
+      case PixelFormat_RGB24:
+        RGB24ToMatlabString(buffer, *this);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }   
+
+    buffer.Flatten(target);
+  }
+
+
+
+  ImageAccessor ImageAccessor::GetRegion(unsigned int x,
+                                         unsigned int y,
+                                         unsigned int width,
+                                         unsigned int height) const
+  {
+    if (x + width > width_ ||
+        y + height > height_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    
+    ImageAccessor result;
+
+    if (width == 0 ||
+        height == 0)
+    {
+      result.AssignWritable(format_, 0, 0, 0, NULL);
+    }
+    else
+    {
+      uint8_t* p = (buffer_ + 
+                    y * pitch_ + 
+                    x * GetBytesPerPixel());
+
+      if (readOnly_)
+      {
+        result.AssignReadOnly(format_, width, height, pitch_, p);
+      }
+      else
+      {
+        result.AssignWritable(format_, width, height, pitch_, p);
+      }
+    }
+
+    return result;
+  }
+
+
+  void ImageAccessor::SetFormat(PixelFormat format)
+  {
+    if (readOnly_)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Trying to modify the format of a read-only image";
+#endif
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    if (::Orthanc::GetBytesPerPixel(format) != ::Orthanc::GetBytesPerPixel(format_))
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+
+    format_ = format;
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/ImageAccessor.h b/Framework/Orthanc/Core/Images/ImageAccessor.h
new file mode 100644
index 0000000..77e79c2
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/ImageAccessor.h
@@ -0,0 +1,131 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <string>
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class ImageAccessor
+  {
+  private:
+    bool readOnly_;
+    PixelFormat format_;
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int pitch_;
+    uint8_t *buffer_;
+
+  public:
+    ImageAccessor()
+    {
+      AssignEmpty(PixelFormat_Grayscale8);
+    }
+
+    virtual ~ImageAccessor()
+    {
+    }
+
+    bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetPitch() const
+    {
+      return pitch_;
+    }
+
+    unsigned int GetSize() const
+    {
+      return GetHeight() * GetPitch();
+    }
+
+    const void* GetConstBuffer() const
+    {
+      return buffer_;
+    }
+
+    void* GetBuffer() const;
+
+    const void* GetConstRow(unsigned int y) const;
+
+    void* GetRow(unsigned int y) const;
+
+    void AssignEmpty(PixelFormat format);
+
+    void AssignReadOnly(PixelFormat format,
+                        unsigned int width,
+                        unsigned int height,
+                        unsigned int pitch,
+                        const void *buffer);
+
+    void AssignWritable(PixelFormat format,
+                        unsigned int width,
+                        unsigned int height,
+                        unsigned int pitch,
+                        void *buffer);
+
+    void ToMatlabString(std::string& target) const; 
+
+    ImageAccessor GetRegion(unsigned int x,
+                            unsigned int y,
+                            unsigned int width,
+                            unsigned int height) const;
+
+    void SetFormat(PixelFormat format);
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/ImageBuffer.cpp b/Framework/Orthanc/Core/Images/ImageBuffer.cpp
new file mode 100644
index 0000000..71364dc
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/ImageBuffer.cpp
@@ -0,0 +1,184 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "ImageBuffer.h"
+
+#include "../OrthancException.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Orthanc
+{
+  void ImageBuffer::Allocate()
+  {
+    if (changed_)
+    {
+      Deallocate();
+
+      /*
+        if (forceMinimalPitch_)
+        {
+        TODO: Align pitch and memory buffer to optimal size for SIMD.
+        }
+      */
+
+      pitch_ = GetBytesPerPixel() * width_;
+      size_t size = pitch_ * height_;
+
+      if (size == 0)
+      {
+        buffer_ = NULL;
+      }
+      else
+      {
+        buffer_ = malloc(size);
+        if (buffer_ == NULL)
+        {
+          throw OrthancException(ErrorCode_NotEnoughMemory);
+        }
+      }
+
+      changed_ = false;
+    }
+  }
+
+
+  void ImageBuffer::Deallocate()
+  {
+    if (buffer_ != NULL)
+    {
+      free(buffer_);
+      buffer_ = NULL;
+      changed_ = true;
+    }
+  }
+
+
+  ImageBuffer::ImageBuffer(PixelFormat format,
+                           unsigned int width,
+                           unsigned int height,
+                           bool forceMinimalPitch) :
+    forceMinimalPitch_(forceMinimalPitch)
+  {
+    Initialize();
+    SetWidth(width);
+    SetHeight(height);
+    SetFormat(format);
+  }
+
+
+  void ImageBuffer::Initialize()
+  {
+    changed_ = false;
+    forceMinimalPitch_ = true;
+    format_ = PixelFormat_Grayscale8;
+    width_ = 0;
+    height_ = 0;
+    pitch_ = 0;
+    buffer_ = NULL;
+  }
+
+
+  void ImageBuffer::SetFormat(PixelFormat format)
+  {
+    if (format != format_)
+    {
+      changed_ = true;
+      format_ = format;
+    }
+  }
+
+
+  void ImageBuffer::SetWidth(unsigned int width)
+  {
+    if (width != width_)
+    {
+      changed_ = true;
+      width_ = width;     
+    }
+  }
+
+
+  void ImageBuffer::SetHeight(unsigned int height)
+  {
+    if (height != height_)
+    {
+      changed_ = true;
+      height_ = height;     
+    }
+  }
+
+
+  ImageAccessor ImageBuffer::GetAccessor()
+  {
+    Allocate();
+
+    ImageAccessor accessor;
+    accessor.AssignWritable(format_, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+
+
+  ImageAccessor ImageBuffer::GetConstAccessor()
+  {
+    Allocate();
+
+    ImageAccessor accessor;
+    accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+
+
+  void ImageBuffer::AcquireOwnership(ImageBuffer& other)
+  {
+    // Remove the content of the current image
+    Deallocate();
+
+    // Force the allocation of the other image (if not already
+    // allocated)
+    other.Allocate();
+
+    // Transfer the content of the other image
+    changed_ = false;
+    forceMinimalPitch_ = other.forceMinimalPitch_;
+    format_ = other.format_;
+    width_ = other.width_;
+    height_ = other.height_;
+    pitch_ = other.pitch_;
+    buffer_ = other.buffer_;
+
+    // Force the reinitialization of the other image
+    other.Initialize();
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/ImageBuffer.h b/Framework/Orthanc/Core/Images/ImageBuffer.h
new file mode 100644
index 0000000..21b1b15
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/ImageBuffer.h
@@ -0,0 +1,114 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ImageAccessor.h"
+
+#include <vector>
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ImageBuffer : public boost::noncopyable
+  {
+  private:
+    bool changed_;
+
+    bool forceMinimalPitch_;  // Currently unused
+    PixelFormat format_;
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int pitch_;
+    void *buffer_;
+
+    void Initialize();
+    
+    void Allocate();
+
+    void Deallocate();
+
+  public:
+    ImageBuffer(PixelFormat format,
+                unsigned int width,
+                unsigned int height,
+                bool forceMinimalPitch);
+
+    ImageBuffer()
+    {
+      Initialize();
+    }
+
+    ~ImageBuffer()
+    {
+      Deallocate();
+    }
+
+    PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    void SetFormat(PixelFormat format);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    void SetWidth(unsigned int width);
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    void SetHeight(unsigned int height);
+
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
+    ImageAccessor GetAccessor();
+
+    ImageAccessor GetConstAccessor();
+
+    bool IsMinimalPitchForced() const
+    {
+      return forceMinimalPitch_;
+    }
+
+    void AcquireOwnership(ImageBuffer& other);
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/ImageProcessing.cpp b/Framework/Orthanc/Core/Images/ImageProcessing.cpp
new file mode 100644
index 0000000..23c9fee
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/ImageProcessing.cpp
@@ -0,0 +1,754 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "ImageProcessing.h"
+
+#include "../OrthancException.h"
+
+#include <boost/math/special_functions/round.hpp>
+
+#include <cassert>
+#include <string.h>
+#include <limits>
+#include <stdint.h>
+
+namespace Orthanc
+{
+  template <typename TargetType, typename SourceType>
+  static void ConvertInternal(ImageAccessor& target,
+                              const ImageAccessor& source)
+  {
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++)
+      {
+        if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(*s);
+        }
+      }
+    }
+  }
+
+
+  template <typename SourceType>
+  static void ConvertGrayscaleToFloat(ImageAccessor& target,
+                                      const ImageAccessor& source)
+  {
+    assert(sizeof(float) == 4);
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      float* t = reinterpret_cast<float*>(target.GetRow(y));
+      const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++)
+      {
+        *t = static_cast<float>(*s);
+      }
+    }
+  }
+
+
+  template <typename TargetType>
+  static void ConvertColorToGrayscale(ImageAccessor& target,
+                                      const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3)
+      {
+        // Y = 0.2126 R + 0.7152 G + 0.0722 B
+        int32_t v = (2126 * static_cast<int32_t>(s[0]) +
+                     7152 * static_cast<int32_t>(s[1]) +
+                     0722 * static_cast<int32_t>(s[2])) / 1000;
+        
+        if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(v);
+        }
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  static void SetInternal(ImageAccessor& image,
+                          int64_t constant)
+  {
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        *p = static_cast<PixelType>(constant);
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  static void GetMinMaxValueInternal(PixelType& minValue,
+                                     PixelType& maxValue,
+                                     const ImageAccessor& source)
+  {
+    // Deal with the special case of empty image
+    if (source.GetWidth() == 0 ||
+        source.GetHeight() == 0)
+    {
+      minValue = 0;
+      maxValue = 0;
+      return;
+    }
+
+    minValue = std::numeric_limits<PixelType>::max();
+    maxValue = std::numeric_limits<PixelType>::min();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, p++)
+      {
+        if (*p < minValue)
+        {
+          minValue = *p;
+        }
+
+        if (*p > maxValue)
+        {
+          maxValue = *p;
+        }
+      }
+    }
+  }
+
+
+
+  template <typename PixelType>
+  static void AddConstantInternal(ImageAccessor& image,
+                                  int64_t constant)
+  {
+    if (constant == 0)
+    {
+      return;
+    }
+
+    const int64_t minValue = std::numeric_limits<PixelType>::min();
+    const int64_t maxValue = std::numeric_limits<PixelType>::max();
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        int64_t v = static_cast<int64_t>(*p) + constant;
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(v);
+        }
+      }
+    }
+  }
+
+
+
+  template <typename PixelType>
+  void MultiplyConstantInternal(ImageAccessor& image,
+                                float factor)
+  {
+    if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon())
+    {
+      return;
+    }
+
+    const int64_t minValue = std::numeric_limits<PixelType>::min();
+    const int64_t maxValue = std::numeric_limits<PixelType>::max();
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        int64_t v = boost::math::llround(static_cast<float>(*p) * factor);
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(v);
+        }
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  void ShiftScaleInternal(ImageAccessor& image,
+                          float offset,
+                          float scaling)
+  {
+    const float minValue = static_cast<float>(std::numeric_limits<PixelType>::min());
+    const float maxValue = static_cast<float>(std::numeric_limits<PixelType>::max());
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        float v = (static_cast<float>(*p) + offset) * scaling;
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(boost::math::iround(v));
+        }
+      }
+    }
+  }
+
+
+  void ImageProcessing::Copy(ImageAccessor& target,
+                             const ImageAccessor& source)
+  {
+    if (target.GetWidth() != source.GetWidth() ||
+        target.GetHeight() != source.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (target.GetFormat() != source.GetFormat())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+
+    unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
+
+    assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize);
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      memcpy(target.GetRow(y), source.GetConstRow(y), lineSize);
+    }
+  }
+
+
+  void ImageProcessing::Convert(ImageAccessor& target,
+                                const ImageAccessor& source)
+  {
+    if (target.GetWidth() != source.GetWidth() ||
+        target.GetHeight() != source.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (source.GetFormat() == target.GetFormat())
+    {
+      Copy(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertInternal<uint16_t, uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertInternal<int16_t, uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertInternal<uint8_t, uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertInternal<int16_t, uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertInternal<uint8_t, int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertInternal<uint16_t, int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Float32 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertGrayscaleToFloat<uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Float32 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertGrayscaleToFloat<uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Float32 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertGrayscaleToFloat<int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_RGBA32)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++, q++)
+        {
+          *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[0]) +
+                                     7152 * static_cast<uint32_t>(p[1]) +
+                                     0722 * static_cast<uint32_t>(p[2])) / 10000);
+          p += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGB24 &&
+        source.GetFormat() == PixelFormat_RGBA32)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          q[0] = p[0];
+          q[1] = p[1];
+          q[2] = p[2];
+          p += 4;
+          q += 3;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGBA32 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          q[0] = p[0];
+          q[1] = p[1];
+          q[2] = p[2];
+          q[3] = 255;   // Set the alpha channel to full opacity
+          p += 3;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGB24 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          q[0] = *p;
+          q[1] = *p;
+          q[2] = *p;
+          p += 1;
+          q += 3;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGBA32 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          q[0] = *p;
+          q[1] = *p;
+          q[2] = *p;
+          q[3] = 255;
+          p += 1;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_BGRA32 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          q[0] = p[2];
+          q[1] = p[1];
+          q[2] = p[0];
+          q[3] = 255;
+          p += 3;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+
+  void ImageProcessing::Set(ImageAccessor& image,
+                            int64_t value)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        SetInternal<uint8_t>(image, value);
+        return;
+
+      case PixelFormat_Grayscale16:
+        SetInternal<uint16_t>(image, value);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        SetInternal<int16_t>(image, value);
+        return;
+
+      case PixelFormat_Float32:
+        assert(sizeof(float) == 4);
+        SetInternal<float>(image, value);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::Set(ImageAccessor& image,
+                            uint8_t red,
+                            uint8_t green,
+                            uint8_t blue,
+                            uint8_t alpha)
+  {
+    uint8_t p[4];
+    unsigned int size;
+
+    switch (image.GetFormat())
+    {
+      case PixelFormat_RGBA32:
+        p[0] = red;
+        p[1] = green;
+        p[2] = blue;
+        p[3] = alpha;
+        size = 4;
+        break;
+
+      case PixelFormat_BGRA32:
+        p[0] = blue;
+        p[1] = green;
+        p[2] = red;
+        p[3] = alpha;
+        size = 4;
+        break;
+
+      case PixelFormat_RGB24:
+        p[0] = red;
+        p[1] = green;
+        p[2] = blue;
+        size = 3;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }    
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++)
+      {
+        for (unsigned int i = 0; i < size; i++)
+        {
+          q[i] = p[i];
+        }
+
+        q += size;
+      }
+    }
+  }
+
+
+  void ImageProcessing::ShiftRight(ImageAccessor& image,
+                                   unsigned int shift)
+  {
+    if (image.GetWidth() == 0 ||
+        image.GetHeight() == 0 ||
+        shift == 0)
+    {
+      // Nothing to do
+      return;
+    }
+
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+  void ImageProcessing::GetMinMaxValue(int64_t& minValue,
+                                       int64_t& maxValue,
+                                       const ImageAccessor& image)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+      {
+        uint8_t a, b;
+        GetMinMaxValueInternal<uint8_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      case PixelFormat_Grayscale16:
+      {
+        uint16_t a, b;
+        GetMinMaxValueInternal<uint16_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      case PixelFormat_SignedGrayscale16:
+      {
+        int16_t a, b;
+        GetMinMaxValueInternal<int16_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+
+  void ImageProcessing::AddConstant(ImageAccessor& image,
+                                    int64_t value)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        AddConstantInternal<uint8_t>(image, value);
+        return;
+
+      case PixelFormat_Grayscale16:
+        AddConstantInternal<uint16_t>(image, value);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        AddConstantInternal<int16_t>(image, value);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::MultiplyConstant(ImageAccessor& image,
+                                         float factor)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        MultiplyConstantInternal<uint8_t>(image, factor);
+        return;
+
+      case PixelFormat_Grayscale16:
+        MultiplyConstantInternal<uint16_t>(image, factor);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        MultiplyConstantInternal<int16_t>(image, factor);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::ShiftScale(ImageAccessor& image,
+                                   float offset,
+                                   float scaling)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        ShiftScaleInternal<uint8_t>(image, offset, scaling);
+        return;
+
+      case PixelFormat_Grayscale16:
+        ShiftScaleInternal<uint16_t>(image, offset, scaling);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        ShiftScaleInternal<int16_t>(image, offset, scaling);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/ImageProcessing.h b/Framework/Orthanc/Core/Images/ImageProcessing.h
new file mode 100644
index 0000000..45176b5
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/ImageProcessing.h
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ImageAccessor.h"
+
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class ImageProcessing
+  {
+  public:
+    static void Copy(ImageAccessor& target,
+                     const ImageAccessor& source);
+
+    static void Convert(ImageAccessor& target,
+                        const ImageAccessor& source);
+
+    static void Set(ImageAccessor& image,
+                    int64_t value);
+
+    static void Set(ImageAccessor& image,
+                    uint8_t red,
+                    uint8_t green,
+                    uint8_t blue,
+                    uint8_t alpha);
+
+    static void ShiftRight(ImageAccessor& target,
+                           unsigned int shift);
+
+    static void GetMinMaxValue(int64_t& minValue,
+                               int64_t& maxValue,
+                               const ImageAccessor& image);
+
+    static void AddConstant(ImageAccessor& image,
+                            int64_t value);
+
+    static void MultiplyConstant(ImageAccessor& image,
+                                 float factor);
+
+    static void ShiftScale(ImageAccessor& image,
+                           float offset,
+                           float scaling);
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/JpegErrorManager.cpp b/Framework/Orthanc/Core/Images/JpegErrorManager.cpp
new file mode 100644
index 0000000..42c3842
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/JpegErrorManager.cpp
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "JpegErrorManager.h"
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    void JpegErrorManager::OutputMessage(j_common_ptr cinfo)
+    {
+      char message[JMSG_LENGTH_MAX];
+      (*cinfo->err->format_message) (cinfo, message);
+
+      JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err);
+      that->message = std::string(message);
+    }
+
+
+    void JpegErrorManager::ErrorExit(j_common_ptr cinfo)
+    {
+      (*cinfo->err->output_message) (cinfo);
+
+      JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err);
+      longjmp(that->setjmp_buffer, 1);
+    }
+      
+
+    JpegErrorManager::JpegErrorManager()
+    {
+      memset(&pub, 0, sizeof(struct jpeg_error_mgr));
+      memset(&setjmp_buffer, 0, sizeof(jmp_buf));
+
+      jpeg_std_error(&pub);
+      pub.error_exit = ErrorExit;
+      pub.output_message = OutputMessage;
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/JpegErrorManager.h b/Framework/Orthanc/Core/Images/JpegErrorManager.h
new file mode 100644
index 0000000..610cd73
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/JpegErrorManager.h
@@ -0,0 +1,74 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <string.h>
+#include <stdio.h>
+#include <jpeglib.h>
+#include <setjmp.h>
+#include <string>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    class JpegErrorManager 
+    {
+    private:
+      struct jpeg_error_mgr pub;  /* "public" fields */
+      jmp_buf setjmp_buffer;      /* for return to caller */
+      std::string message;
+
+      static void OutputMessage(j_common_ptr cinfo);
+
+      static void ErrorExit(j_common_ptr cinfo);
+
+    public:
+      JpegErrorManager();
+
+      struct jpeg_error_mgr* GetPublic()
+      {
+        return &pub;
+      }
+
+      jmp_buf& GetJumpBuffer()
+      {
+        return setjmp_buffer;
+      }
+
+      const std::string& GetMessage() const
+      {
+        return message;
+      }
+    };
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/JpegReader.cpp b/Framework/Orthanc/Core/Images/JpegReader.cpp
new file mode 100644
index 0000000..c8e725e
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/JpegReader.cpp
@@ -0,0 +1,185 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "JpegReader.h"
+
+#include "JpegErrorManager.h"
+#include "../OrthancException.h"
+#include "../Logging.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  static void Uncompress(struct jpeg_decompress_struct& cinfo,
+                         std::string& content,
+                         ImageAccessor& accessor)
+  {
+    jpeg_read_header(&cinfo, TRUE);
+    jpeg_start_decompress(&cinfo);
+
+    PixelFormat format;
+    if (cinfo.output_components == 1 &&
+        cinfo.out_color_space == JCS_GRAYSCALE)
+    {
+      format = PixelFormat_Grayscale8;
+    }
+    else if (cinfo.output_components == 3 &&
+             cinfo.out_color_space == JCS_RGB)
+    {
+      format = PixelFormat_RGB24;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    unsigned int pitch = cinfo.output_width * cinfo.output_components;
+
+    /* Make a one-row-high sample array that will go away when done with image */
+    JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, pitch, 1);
+
+    try
+    {
+      content.resize(pitch * cinfo.output_height);
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    accessor.AssignWritable(format, cinfo.output_width, cinfo.output_height, pitch, 
+                            content.empty() ? NULL : &content[0]);
+
+    uint8_t* target = reinterpret_cast<uint8_t*>(&content[0]);
+    while (cinfo.output_scanline < cinfo.output_height) 
+    {
+      jpeg_read_scanlines(&cinfo, buffer, 1);
+      memcpy(target, buffer[0], pitch);
+      target += pitch;
+    }
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    jpeg_finish_decompress(&cinfo);
+  }
+
+
+  void JpegReader::ReadFromFile(const std::string& filename)
+  {
+    FILE* fp = Toolbox::OpenFile(filename, FileMode_ReadBinary);
+    if (!fp)
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    struct jpeg_decompress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct));
+
+    Internals::JpegErrorManager jerr;
+    cinfo.err = jerr.GetPublic();
+    
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      jpeg_destroy_decompress(&cinfo);
+      fclose(fp);
+      LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Below this line, we are under the scope of a "setjmp"
+
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+
+    try
+    {
+      Uncompress(cinfo, content_, *this);
+    }
+    catch (OrthancException&)
+    {
+      jpeg_destroy_decompress(&cinfo);
+      fclose(fp);
+      throw;
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    fclose(fp);
+  }
+
+
+  void JpegReader::ReadFromMemory(const void* buffer,
+                                  size_t size)
+  {
+    struct jpeg_decompress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct));
+
+    Internals::JpegErrorManager jerr;
+    cinfo.err = jerr.GetPublic();
+    
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      jpeg_destroy_decompress(&cinfo);
+      LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Below this line, we are under the scope of a "setjmp"
+    jpeg_create_decompress(&cinfo);
+    jpeg_mem_src(&cinfo, const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(buffer)), size);
+
+    try
+    {
+      Uncompress(cinfo, content_, *this);
+    }
+    catch (OrthancException&)
+    {
+      jpeg_destroy_decompress(&cinfo);
+      throw;
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+  }
+
+
+  void JpegReader::ReadFromMemory(const std::string& buffer)
+  {
+    if (buffer.empty())
+    {
+      ReadFromMemory(NULL, 0);
+    }
+    else
+    {
+      ReadFromMemory(buffer.c_str(), buffer.size());
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/JpegReader.h b/Framework/Orthanc/Core/Images/JpegReader.h
new file mode 100644
index 0000000..5cb5551
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/JpegReader.h
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ImageAccessor.h"
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class JpegReader : 
+    public ImageAccessor,
+    public boost::noncopyable
+  {
+  private:
+    std::string  content_;
+
+  public:
+    void ReadFromFile(const std::string& filename);
+
+    void ReadFromMemory(const void* buffer,
+                        size_t size);
+
+    void ReadFromMemory(const std::string& buffer);
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/JpegWriter.cpp b/Framework/Orthanc/Core/Images/JpegWriter.cpp
new file mode 100644
index 0000000..abe98fa
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/JpegWriter.cpp
@@ -0,0 +1,204 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "JpegWriter.h"
+
+#include "../OrthancException.h"
+#include "../Logging.h"
+#include "../Toolbox.h"
+
+#include "JpegErrorManager.h"
+
+#include <stdlib.h>
+#include <vector>
+
+namespace Orthanc
+{
+  static void GetLines(std::vector<uint8_t*>& lines,
+                       unsigned int height,
+                       unsigned int pitch,
+                       PixelFormat format,
+                       const void* buffer)
+  {
+    if (format != PixelFormat_Grayscale8 &&
+        format != PixelFormat_RGB24)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    lines.resize(height);
+
+    uint8_t* base = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer));
+    for (unsigned int y = 0; y < height; y++)
+    {
+      lines[y] = base + static_cast<intptr_t>(y) * static_cast<intptr_t>(pitch);
+    }
+  }
+
+
+  static void Compress(struct jpeg_compress_struct& cinfo,
+                       std::vector<uint8_t*>& lines,
+                       unsigned int width,
+                       unsigned int height,
+                       PixelFormat format,
+                       uint8_t quality)
+  {
+    cinfo.image_width = width;
+    cinfo.image_height = height;
+
+    switch (format)
+    {
+      case PixelFormat_Grayscale8:
+        cinfo.input_components = 1;
+        cinfo.in_color_space = JCS_GRAYSCALE;
+        break;
+
+      case PixelFormat_RGB24:
+        cinfo.input_components = 3;
+        cinfo.in_color_space = JCS_RGB;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_quality(&cinfo, quality, TRUE);
+    jpeg_start_compress(&cinfo, TRUE);
+    jpeg_write_scanlines(&cinfo, &lines[0], height);
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+  }
+                       
+
+  void JpegWriter::SetQuality(uint8_t quality)
+  {
+    if (quality <= 0 || quality > 100)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    quality_ = quality;
+  }
+
+
+  void JpegWriter::WriteToFileInternal(const std::string& filename,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer)
+  {
+    FILE* fp = Toolbox::OpenFile(filename, FileMode_WriteBinary);
+    if (fp == NULL)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    std::vector<uint8_t*> lines;
+    GetLines(lines, height, pitch, format, buffer);
+
+    struct jpeg_compress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_compress_struct));
+
+    Internals::JpegErrorManager jerr;
+    cinfo.err = jerr.GetPublic();
+
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      /* If we get here, the JPEG code has signaled an error.
+       * We need to clean up the JPEG object, close the input file, and return.
+       */
+      jpeg_destroy_compress(&cinfo);
+      fclose(fp);
+      LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Do not allocate data on the stack below this line!
+
+    jpeg_create_compress(&cinfo);
+    jpeg_stdio_dest(&cinfo, fp);
+    Compress(cinfo, lines, width, height, format, quality_);
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    fclose(fp);
+  }
+
+
+  void JpegWriter::WriteToMemoryInternal(std::string& jpeg,
+                                         unsigned int width,
+                                         unsigned int height,
+                                         unsigned int pitch,
+                                         PixelFormat format,
+                                         const void* buffer)
+  {
+    std::vector<uint8_t*> lines;
+    GetLines(lines, height, pitch, format, buffer);
+
+    struct jpeg_compress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_compress_struct));
+
+    Internals::JpegErrorManager jerr;
+
+    unsigned char* data = NULL;
+    unsigned long size;
+
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      jpeg_destroy_compress(&cinfo);
+
+      if (data != NULL)
+      {
+        free(data);
+      }
+
+      LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Do not allocate data on the stack below this line!
+
+    jpeg_create_compress(&cinfo);
+    cinfo.err = jerr.GetPublic();
+    jpeg_mem_dest(&cinfo, &data, &size);
+
+    Compress(cinfo, lines, width, height, format, quality_);
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    jpeg.assign(reinterpret_cast<const char*>(data), size);
+    free(data);
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/JpegWriter.h b/Framework/Orthanc/Core/Images/JpegWriter.h
new file mode 100644
index 0000000..5d18af3
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/JpegWriter.h
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IImageWriter.h"
+
+namespace Orthanc
+{
+  class JpegWriter : public IImageWriter
+  {
+  protected:
+    virtual void WriteToFileInternal(const std::string& filename,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     PixelFormat format,
+                                     const void* buffer);
+
+    virtual void WriteToMemoryInternal(std::string& jpeg,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer);
+
+  private:
+    uint8_t  quality_;
+
+  public:
+    JpegWriter() : quality_(90)
+    {
+    }
+
+    void SetQuality(uint8_t quality);
+
+    uint8_t GetQuality() const
+    {
+      return quality_;
+    }
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/PngReader.cpp b/Framework/Orthanc/Core/Images/PngReader.cpp
new file mode 100644
index 0000000..b93aee0
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/PngReader.cpp
@@ -0,0 +1,315 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "PngReader.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+#include <png.h>
+#include <string.h>  // For memcpy()
+
+namespace Orthanc
+{
+  namespace 
+  {
+    struct FileRabi
+    {
+      FILE* fp_;
+
+      FileRabi(const char* filename)
+      {
+        fp_ = Toolbox::OpenFile(filename, FileMode_ReadBinary);
+        if (!fp_)
+        {
+          throw OrthancException(ErrorCode_InexistentFile);
+        }
+      }
+
+      ~FileRabi()
+      {
+        if (fp_)
+        {
+          fclose(fp_);
+        }
+      }
+    };
+  }
+
+
+  struct PngReader::PngRabi
+  {
+    png_structp png_;
+    png_infop info_;
+    png_infop endInfo_;
+
+    void Destruct()
+    {
+      if (png_)
+      {
+        png_destroy_read_struct(&png_, &info_, &endInfo_);
+
+        png_ = NULL;
+        info_ = NULL;
+        endInfo_ = NULL;
+      }
+    }
+
+    PngRabi()
+    {
+      png_ = NULL;
+      info_ = NULL;
+      endInfo_ = NULL;
+
+      png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+      if (!png_)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
+      info_ = png_create_info_struct(png_);
+      if (!info_)
+      {
+        png_destroy_read_struct(&png_, NULL, NULL);
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
+      endInfo_ = png_create_info_struct(png_);
+      if (!info_)
+      {
+        png_destroy_read_struct(&png_, &info_, NULL);
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+    }
+
+    ~PngRabi()
+    {
+      Destruct();
+    }
+
+    static void MemoryCallback(png_structp png_ptr, 
+                               png_bytep data, 
+                               png_size_t size);
+  };
+
+
+  void PngReader::CheckHeader(const void* header)
+  {
+    int is_png = !png_sig_cmp((png_bytep) header, 0, 8);
+    if (!is_png)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  PngReader::PngReader()
+  {
+  }
+
+  void PngReader::Read(PngRabi& rabi)
+  {
+    png_set_sig_bytes(rabi.png_, 8);
+
+    png_read_info(rabi.png_, rabi.info_);
+
+    png_uint_32 width, height;
+    int bit_depth, color_type, interlace_type;
+    int compression_type, filter_method;
+    // get size and bit-depth of the PNG-image
+    png_get_IHDR(rabi.png_, rabi.info_,
+                 &width, &height,
+                 &bit_depth, &color_type, &interlace_type,
+                 &compression_type, &filter_method);
+
+    PixelFormat format;
+    unsigned int pitch;
+
+    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8)
+    {
+      format = PixelFormat_Grayscale8;
+      pitch = width;
+    }
+    else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16)
+    {
+      format = PixelFormat_Grayscale16;
+      pitch = 2 * width;
+
+      if (Toolbox::DetectEndianness() == Endianness_Little)
+      {
+        png_set_swap(rabi.png_);
+      }
+    }
+    else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8)
+    {
+      format = PixelFormat_RGB24;
+      pitch = 3 * width;
+    }
+    else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8)
+    {
+      format = PixelFormat_RGBA32;
+      pitch = 4 * width;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    data_.resize(height * pitch);
+
+    if (height == 0 || width == 0)
+    {
+      // Empty image, we are done
+      AssignEmpty(format);
+      return;
+    }
+    
+    png_read_update_info(rabi.png_, rabi.info_);
+
+    std::vector<png_bytep> rows(height);
+    for (size_t i = 0; i < height; i++)
+    {
+      rows[i] = &data_[0] + i * pitch;
+    }
+
+    png_read_image(rabi.png_, &rows[0]);
+
+    AssignWritable(format, width, height, pitch, &data_[0]);
+  }
+
+  void PngReader::ReadFromFile(const std::string& filename)
+  {
+    FileRabi f(filename.c_str());
+
+    char header[8];
+    if (fread(header, 1, 8, f.fp_) != 8)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    CheckHeader(header);
+
+    PngRabi rabi;
+
+    if (setjmp(png_jmpbuf(rabi.png_)))
+    {
+      rabi.Destruct();
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    png_init_io(rabi.png_, f.fp_);
+
+    Read(rabi);
+  }
+
+
+  namespace
+  {
+    struct MemoryBuffer
+    {
+      const uint8_t* buffer_;
+      size_t size_;
+      size_t pos_;
+      bool ok_;
+    };
+  }
+
+
+  void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, 
+                                          png_bytep outBytes, 
+                                          png_size_t byteCountToRead)
+  {
+    MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr));
+
+    if (!from->ok_)
+    {
+      return;
+    }
+
+    if (from->pos_ + byteCountToRead > from->size_)
+    {
+      from->ok_ = false;
+      return;
+    }
+
+    memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead);
+
+    from->pos_ += byteCountToRead;
+  }
+
+
+  void PngReader::ReadFromMemory(const void* buffer,
+                                 size_t size)
+  {
+    if (size < 8)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    CheckHeader(buffer);
+
+    PngRabi rabi;
+
+    if (setjmp(png_jmpbuf(rabi.png_)))
+    {
+      rabi.Destruct();
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    MemoryBuffer tmp;
+    tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8;  // We skip the header
+    tmp.size_ = size - 8;
+    tmp.pos_ = 0;
+    tmp.ok_ = true;
+
+    png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback);
+
+    Read(rabi);
+
+    if (!tmp.ok_)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  void PngReader::ReadFromMemory(const std::string& buffer)
+  {
+    if (buffer.size() != 0)
+    {
+      ReadFromMemory(&buffer[0], buffer.size());
+    }
+    else
+    {
+      ReadFromMemory(NULL, 0);
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/PngReader.h b/Framework/Orthanc/Core/Images/PngReader.h
new file mode 100644
index 0000000..b6ad811
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/PngReader.h
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ImageAccessor.h"
+
+#include "../Enumerations.h"
+
+#include <vector>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class PngReader : 
+    public ImageAccessor, 
+    public boost::noncopyable
+  {
+  private:
+    struct PngRabi;
+
+    std::vector<uint8_t> data_;
+
+    void CheckHeader(const void* header);
+
+    void Read(PngRabi& rabi);
+
+  public:
+    PngReader();
+
+    void ReadFromFile(const std::string& filename);
+
+    void ReadFromMemory(const void* buffer,
+                        size_t size);
+
+    void ReadFromMemory(const std::string& buffer);
+  };
+}
diff --git a/Framework/Orthanc/Core/Images/PngWriter.cpp b/Framework/Orthanc/Core/Images/PngWriter.cpp
new file mode 100644
index 0000000..8a2d66a
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/PngWriter.cpp
@@ -0,0 +1,268 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "PngWriter.h"
+
+#include <vector>
+#include <stdint.h>
+#include <png.h>
+#include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
+#include "../Toolbox.h"
+
+
+// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4
+// http://zarb.org/~gc/html/libpng.html
+/*
+  void write_row_callback(png_ptr, png_uint_32 row, int pass)
+  {
+  }*/
+
+
+
+
+/*  bool isError_;
+
+// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2
+
+static void ErrorHandler(png_structp png, png_const_charp message)
+{
+printf("** [%s]\n", message);
+
+PngWriter* that = (PngWriter*) png_get_error_ptr(png);
+that->isError_ = true;
+printf("** %d\n", (int)that);
+
+//((PngWriter*) payload)->isError_ = true;
+}
+
+static void WarningHandler(png_structp png, png_const_charp message)
+{
+  printf("++ %d\n", (int)message);
+}*/
+
+
+namespace Orthanc
+{
+  struct PngWriter::PImpl
+  {
+    png_structp png_;
+    png_infop info_;
+
+    // Filled by Prepare()
+    std::vector<uint8_t*> rows_;
+    int bitDepth_;
+    int colorType_;
+  };
+
+
+
+  PngWriter::PngWriter() : pimpl_(new PImpl)
+  {
+    pimpl_->png_ = NULL;
+    pimpl_->info_ = NULL;
+
+    pimpl_->png_ = png_create_write_struct
+      (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler);
+    if (!pimpl_->png_)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    pimpl_->info_ = png_create_info_struct(pimpl_->png_);
+    if (!pimpl_->info_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
+  PngWriter::~PngWriter()
+  {
+    if (pimpl_->info_)
+    {
+      png_destroy_info_struct(pimpl_->png_, &pimpl_->info_);
+    }
+
+    if (pimpl_->png_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+    }
+  }
+
+
+
+  void PngWriter::Prepare(unsigned int width,
+                          unsigned int height,
+                          unsigned int pitch,
+                          PixelFormat format,
+                          const void* buffer)
+  {
+    pimpl_->rows_.resize(height);
+    for (unsigned int y = 0; y < height; y++)
+    {
+      pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch;
+    }
+
+    switch (format)
+    {
+    case PixelFormat_RGB24:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
+      break;
+
+    case PixelFormat_RGBA32:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA;
+      break;
+
+    case PixelFormat_Grayscale8:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    case PixelFormat_Grayscale16:
+    case PixelFormat_SignedGrayscale16:
+      pimpl_->bitDepth_ = 16;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void PngWriter::Compress(unsigned int width,
+                           unsigned int height,
+                           unsigned int pitch,
+                           PixelFormat format)
+  {
+    png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height,
+                 pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+    png_write_info(pimpl_->png_, pimpl_->info_);
+
+    if (height > 0)
+    {
+      switch (format)
+      {
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+      {
+        int transforms = 0;
+        if (Toolbox::DetectEndianness() == Endianness_Little)
+        {
+          transforms = PNG_TRANSFORM_SWAP_ENDIAN;
+        }
+
+        png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]);
+        png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL);
+
+        break;
+      }
+
+      default:
+        png_write_image(pimpl_->png_, &pimpl_->rows_[0]);
+      }
+    }
+
+    png_write_end(pimpl_->png_, NULL);
+  }
+
+
+  void PngWriter::WriteToFileInternal(const std::string& filename,
+                                      unsigned int width,
+                                      unsigned int height,
+                                      unsigned int pitch,
+                                      PixelFormat format,
+                                      const void* buffer)
+  {
+    Prepare(width, height, pitch, format, buffer);
+
+    FILE* fp = Toolbox::OpenFile(filename, FileMode_WriteBinary);
+    if (!fp)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }    
+
+    png_init_io(pimpl_->png_, fp);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_CannotWriteFile);      
+    }
+
+    Compress(width, height, pitch, format);
+
+    fclose(fp);
+  }
+
+
+
+  static void MemoryCallback(png_structp png_ptr, 
+                             png_bytep data, 
+                             png_size_t size)
+  {
+    ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr));
+    buffer->AddChunk(reinterpret_cast<const char*>(data), size);
+  }
+
+
+
+  void PngWriter::WriteToMemoryInternal(std::string& png,
+                                        unsigned int width,
+                                        unsigned int height,
+                                        unsigned int pitch,
+                                        PixelFormat format,
+                                        const void* buffer)
+  {
+    ChunkedBuffer chunks;
+
+    Prepare(width, height, pitch, format, buffer);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_InternalError);      
+    }
+
+    png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL);
+
+    Compress(width, height, pitch, format);
+
+    chunks.Flatten(png);
+  }
+}
diff --git a/Framework/Orthanc/Core/Images/PngWriter.h b/Framework/Orthanc/Core/Images/PngWriter.h
new file mode 100644
index 0000000..591112f
--- /dev/null
+++ b/Framework/Orthanc/Core/Images/PngWriter.h
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IImageWriter.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class PngWriter : public IImageWriter
+  {
+  protected:
+    virtual void WriteToFileInternal(const std::string& filename,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     PixelFormat format,
+                                     const void* buffer);
+
+    virtual void WriteToMemoryInternal(std::string& png,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer);
+
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void Compress(unsigned int width,
+                  unsigned int height,
+                  unsigned int pitch,
+                  PixelFormat format);
+
+    void Prepare(unsigned int width,
+                 unsigned int height,
+                 unsigned int pitch,
+                 PixelFormat format,
+                 const void* buffer);
+
+  public:
+    PngWriter();
+
+    ~PngWriter();
+  };
+}
diff --git a/Framework/Orthanc/Core/Logging.cpp b/Framework/Orthanc/Core/Logging.cpp
new file mode 100644
index 0000000..7bce92b
--- /dev/null
+++ b/Framework/Orthanc/Core/Logging.cpp
@@ -0,0 +1,459 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "Logging.h"
+
+#if ORTHANC_ENABLE_LOGGING != 1
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    void Initialize()
+    {
+    }
+
+    void Finalize()
+    {
+    }
+
+    void Reset()
+    {
+    }
+
+    void Flush()
+    {
+    }
+
+    void EnableInfoLevel(bool enabled)
+    {
+    }
+
+    void EnableTraceLevel(bool enabled)
+    {
+    }
+
+    void SetTargetFile(const std::string& path)
+    {
+    }
+
+    void SetTargetFolder(const std::string& path)
+    {
+    }
+  }
+}
+
+#else
+
+/*********************************************************
+ * Internal logger of Orthanc, that mimics some
+ * behavior from Google Log.
+ *********************************************************/
+
+#include "OrthancException.h"
+#include "Enumerations.h"
+#include "Toolbox.h"
+
+#include <fstream>
+#include <boost/filesystem.hpp>
+#include <boost/thread.hpp>
+
+#if BOOST_HAS_DATE_TIME == 1
+#  include <boost/date_time/posix_time/posix_time.hpp>
+#else
+#  error Boost::date_time is required
+#endif
+
+
+namespace
+{
+  struct LoggingContext
+  {
+    bool infoEnabled_;
+    bool traceEnabled_;
+    std::string  targetFile_;
+    std::string  targetFolder_;
+
+    std::ostream* error_;
+    std::ostream* warning_;
+    std::ostream* info_;
+
+    std::auto_ptr<std::ofstream> file_;
+
+    LoggingContext() : 
+      infoEnabled_(false),
+      traceEnabled_(false),
+      error_(&std::cerr),
+      warning_(&std::cerr),
+      info_(&std::cerr)
+    {
+    }
+  };
+}
+
+
+
+static std::auto_ptr<LoggingContext> loggingContext_;
+static boost::mutex  loggingMutex_;
+
+
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    static void GetLogPath(boost::filesystem::path& log,
+                           boost::filesystem::path& link,
+                           const std::string& suffix,
+                           const std::string& directory)
+    {
+      /**
+         From Google Log documentation:
+
+         Unless otherwise specified, logs will be written to the filename
+         "<program name>.<hostname>.<user name>.log<suffix>.",
+         followed by the date, time, and pid (you can't prevent the date,
+         time, and pid from being in the filename).
+
+         In this implementation : "hostname" and "username" are not used
+      **/
+
+      boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+      boost::filesystem::path root(directory);
+      boost::filesystem::path exe(Toolbox::GetPathToExecutable());
+      
+      if (!boost::filesystem::exists(root) ||
+          !boost::filesystem::is_directory(root))
+      {
+        throw OrthancException(ErrorCode_CannotWriteFile);
+      }
+
+      char date[64];
+      sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d",
+              static_cast<int>(now.date().year()),
+              now.date().month().as_number(),
+              now.date().day().as_number(),
+              now.time_of_day().hours(),
+              now.time_of_day().minutes(),
+              now.time_of_day().seconds(),
+              Toolbox::GetProcessId());
+
+      std::string programName = exe.filename().replace_extension("").string();
+
+      log = (root / (programName + ".log" + suffix + "." + std::string(date)));
+      link = (root / (programName + ".log" + suffix));
+    }
+
+
+    static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file,
+                                 const std::string& suffix,
+                                 const std::string& directory)
+    {
+      boost::filesystem::path log, link;
+      GetLogPath(log, link, suffix, directory);
+
+#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
+      boost::filesystem::remove(link);
+      boost::filesystem::create_symlink(log.filename(), link);
+#endif
+
+      file.reset(new std::ofstream(log.string().c_str()));
+    }
+
+
+    void Initialize()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      loggingContext_.reset(new LoggingContext);
+    }
+
+    void Finalize()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      loggingContext_.reset(NULL);
+    }
+
+    void Reset()
+    {
+      // Recover the old logging context
+      std::auto_ptr<LoggingContext> old;
+
+      {
+        boost::mutex::scoped_lock lock(loggingMutex_);
+        if (loggingContext_.get() == NULL)
+        {
+          return;
+        }
+        else
+        {
+          old = loggingContext_;
+
+          // Create a new logging context, 
+          loggingContext_.reset(new LoggingContext);
+        }
+      }
+      
+      EnableInfoLevel(old->infoEnabled_);
+      EnableTraceLevel(old->traceEnabled_);
+
+      if (!old->targetFolder_.empty())
+      {
+        SetTargetFolder(old->targetFolder_);
+      }
+      else if (!old->targetFile_.empty())
+      {
+        SetTargetFile(old->targetFile_);
+      }
+    }
+
+    void EnableInfoLevel(bool enabled)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->infoEnabled_ = enabled;
+      
+      if (!enabled)
+      {
+        // Also disable the "TRACE" level when info-level debugging is disabled
+        loggingContext_->traceEnabled_ = false;
+      }
+    }
+
+    void EnableTraceLevel(bool enabled)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->traceEnabled_ = enabled;
+      
+      if (enabled)
+      {
+        // Also enable the "INFO" level when trace-level debugging is enabled
+        loggingContext_->infoEnabled_ = true;
+      }
+    }
+
+
+    static void CheckFile(std::auto_ptr<std::ofstream>& f)
+    {
+      if (loggingContext_->file_.get() == NULL ||
+          !loggingContext_->file_->is_open())
+      {
+        throw OrthancException(ErrorCode_CannotWriteFile);
+      }
+    }
+
+    void SetTargetFolder(const std::string& path)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      PrepareLogFolder(loggingContext_->file_, "" /* no suffix */, path);
+      CheckFile(loggingContext_->file_);
+
+      loggingContext_->targetFile_.clear();
+      loggingContext_->targetFolder_ = path;
+      loggingContext_->warning_ = loggingContext_->file_.get();
+      loggingContext_->error_ = loggingContext_->file_.get();
+      loggingContext_->info_ = loggingContext_->file_.get();
+    }
+
+
+    void SetTargetFile(const std::string& path)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app));
+      CheckFile(loggingContext_->file_);
+
+      loggingContext_->targetFile_ = path;
+      loggingContext_->targetFolder_.clear();
+      loggingContext_->warning_ = loggingContext_->file_.get();
+      loggingContext_->error_ = loggingContext_->file_.get();
+      loggingContext_->info_ = loggingContext_->file_.get();
+    }
+
+
+    InternalLogger::InternalLogger(const char* level,
+                                   const char* file,
+                                   int line) : 
+      lock_(loggingMutex_), 
+      stream_(&null_)  // By default, logging to "/dev/null" is simulated
+    {
+      if (loggingContext_.get() == NULL)
+      {
+        fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
+        return;
+      }
+
+      LogLevel l = StringToLogLevel(level);
+      
+      if ((l == LogLevel_Info  && !loggingContext_->infoEnabled_) ||
+          (l == LogLevel_Trace && !loggingContext_->traceEnabled_))
+      {
+        // This logging level is disabled, directly exit and unlock
+        // the mutex to speed-up things. The stream is set to "/dev/null"
+        lock_.unlock();
+        return;
+      }
+
+      // Compute the header of the line, temporary release the lock as
+      // this is a time-consuming operation
+      lock_.unlock();
+      std::string header;
+
+      {
+        boost::filesystem::path path(file);
+        boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
+        boost::posix_time::time_duration duration = now.time_of_day();
+
+        /**
+           From Google Log documentation:
+
+           "Log lines have this form:
+
+           Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+
+           where the fields are defined as follows:
+
+           L                A single character, representing the log level (eg 'I' for INFO)
+           mm               The month (zero padded; ie May is '05')
+           dd               The day (zero padded)
+           hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
+           threadid         The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
+           file             The file name
+           line             The line number
+           msg              The user-supplied message"
+
+           In this implementation, "threadid" is not printed.
+         **/
+
+        char date[32];
+        sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
+                level[0],
+                now.date().month().as_number(),
+                now.date().day().as_number(),
+                duration.hours(),
+                duration.minutes(),
+                duration.seconds(),
+                static_cast<int>(duration.fractional_seconds()));
+
+        header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] ";
+      }
+
+
+      // The header is computed, we now re-lock the mutex to access
+      // the stream objects. Pay attention that "loggingContext_",
+      // "infoEnabled_" or "traceEnabled_" might have changed while
+      // the mutex was unlocked.
+      lock_.lock();
+
+      if (loggingContext_.get() == NULL)
+      {
+        fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
+        return;
+      }
+
+      switch (l)
+      {
+        case LogLevel_Error:
+          stream_ = loggingContext_->error_;
+          break;
+
+        case LogLevel_Warning:
+          stream_ = loggingContext_->warning_;
+          break;
+
+        case LogLevel_Info:
+          if (loggingContext_->infoEnabled_)
+          {
+            stream_ = loggingContext_->info_;
+          }
+
+          break;
+
+        case LogLevel_Trace:
+          if (loggingContext_->traceEnabled_)
+          {
+            stream_ = loggingContext_->info_;
+          }
+
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (stream_ == &null_)
+      {
+        // The logging is disabled for this level. The stream is the
+        // "null_" member of this object, so we can release the global
+        // mutex.
+        lock_.unlock();
+      }
+
+      (*stream_) << header;
+    }
+
+
+    InternalLogger::~InternalLogger()
+    {
+      if (stream_ != &null_)
+      {
+#if defined(_WIN32)
+        *stream_ << "\r\n";
+#else
+        *stream_ << "\n";
+#endif
+
+        stream_->flush();
+      }
+    }
+      
+
+    void Flush()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+
+      if (loggingContext_.get() != NULL &&
+          loggingContext_->file_.get() != NULL)
+      {
+        loggingContext_->file_->flush();
+      }
+    }
+  }
+}
+
+#endif   // ORTHANC_ENABLE_LOGGING
diff --git a/Framework/Orthanc/Core/Logging.h b/Framework/Orthanc/Core/Logging.h
new file mode 100644
index 0000000..dd791d1
--- /dev/null
+++ b/Framework/Orthanc/Core/Logging.h
@@ -0,0 +1,117 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <iostream>
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    void Initialize();
+
+    void Finalize();
+
+    void Reset();
+
+    void Flush();
+
+    void EnableInfoLevel(bool enabled);
+
+    void EnableTraceLevel(bool enabled);
+
+    void SetTargetFile(const std::string& path);
+
+    void SetTargetFolder(const std::string& path);
+
+    struct NullStream : public std::ostream 
+    {
+      NullStream() : 
+        std::ios(0), 
+        std::ostream(0)
+      {
+      }
+      
+      std::ostream& operator<< (const std::string& message)
+      {
+        return *this;
+      }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return *this;
+      }
+    };
+  }
+}
+
+
+#if ORTHANC_ENABLE_LOGGING != 1
+
+#  define LOG(level)   ::Orthanc::Logging::NullStream()
+#  define VLOG(level)  ::Orthanc::Logging::NullStream()
+
+#else  /* ORTHANC_ENABLE_LOGGING == 1 */
+
+#  include <boost/thread/mutex.hpp>
+#  define LOG(level)  ::Orthanc::Logging::InternalLogger(#level,  __FILE__, __LINE__)
+#  define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__)
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    class InternalLogger
+    {
+    private:
+      boost::mutex::scoped_lock lock_;
+      NullStream                null_;
+      std::ostream*             stream_;
+
+    public:
+      InternalLogger(const char* level,
+                     const char* file,
+                     int line);
+
+      ~InternalLogger();
+      
+      std::ostream& operator<< (const std::string& message)
+      {
+        return (*stream_) << message;
+      }
+    };
+  }
+}
+
+#endif  // ORTHANC_ENABLE_LOGGING
diff --git a/Framework/Orthanc/Core/MultiThreading/BagOfTasks.h b/Framework/Orthanc/Core/MultiThreading/BagOfTasks.h
new file mode 100644
index 0000000..7b992f6
--- /dev/null
+++ b/Framework/Orthanc/Core/MultiThreading/BagOfTasks.h
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../ICommand.h"
+
+#include <list>
+#include <cstddef>
+
+namespace Orthanc
+{
+  class BagOfTasks : public boost::noncopyable
+  {
+  private:
+    typedef std::list<ICommand*>  Tasks;
+
+    Tasks  tasks_;
+
+  public:
+    ~BagOfTasks()
+    {
+      for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it)
+      {
+        delete *it;
+      }
+    }
+
+    ICommand* Pop()
+    {
+      ICommand* task = tasks_.front();
+      tasks_.pop_front();
+      return task;
+    }
+
+    void Push(ICommand* task)   // Takes ownership
+    {
+      if (task != NULL)
+      {
+        tasks_.push_back(task);
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return tasks_.size();
+    }
+
+    bool IsEmpty() const
+    {
+      return tasks_.empty();
+    }
+  };
+}
diff --git a/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp b/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp
new file mode 100644
index 0000000..c409f1c
--- /dev/null
+++ b/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp
@@ -0,0 +1,276 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "BagOfTasksProcessor.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  class BagOfTasksProcessor::Task : public IDynamicObject
+  {
+  private:
+    uint64_t                 bag_;
+    std::auto_ptr<ICommand>  command_;
+
+  public:
+    Task(uint64_t  bag,
+         ICommand* command) :
+      bag_(bag),
+      command_(command)
+    {
+    }
+
+    bool Execute()
+    {
+      try
+      {
+        return command_->Execute();
+      }
+      catch (OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What();
+        return false;
+      }
+      catch (std::runtime_error& e)
+      {
+        LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what();
+        return false;
+      }
+      catch (...)
+      {
+        LOG(ERROR) << "Native exception while processing a bag of tasks";
+        return false;
+      }
+    }
+
+    uint64_t GetBag()
+    {
+      return bag_;
+    }
+  };
+
+
+  void BagOfTasksProcessor::SignalProgress(Task& task,
+                                           Bag& bag)
+  {
+    assert(bag.done_ < bag.size_);
+
+    bag.done_ += 1;
+
+    if (bag.done_ == bag.size_)
+    {
+      exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running);
+      bagFinished_.notify_all();
+    }
+  }
+
+  void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that)
+  {
+    while (that->continue_)
+    {
+      std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100));
+      if (obj.get() != NULL)
+      {
+        Task& task = *dynamic_cast<Task*>(obj.get());
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+
+          Bags::iterator bag = that->bags_.find(task.GetBag());
+          assert(bag != that->bags_.end());
+          assert(bag->second.done_ < bag->second.size_);
+
+          if (bag->second.status_ != BagStatus_Running)
+          {
+            // Do not execute this task, as its parent bag of tasks
+            // has failed or is tagged as canceled
+            that->SignalProgress(task, bag->second);
+            continue;
+          }
+        }
+
+        bool success = task.Execute();
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+
+          Bags::iterator bag = that->bags_.find(task.GetBag());
+          assert(bag != that->bags_.end());
+
+          if (!success)
+          {
+            bag->second.status_ = BagStatus_Failed;
+          }
+
+          that->SignalProgress(task, bag->second);
+        }
+      }
+    }
+  }
+
+
+  void BagOfTasksProcessor::Cancel(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    Bags::iterator it = bags_.find(bag);
+    if (it != bags_.end())
+    {
+      it->second.status_ = BagStatus_Canceled;
+    }
+  }
+
+
+  bool BagOfTasksProcessor::Join(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    while (continue_)
+    {
+      ExitStatus::iterator it = exitStatus_.find(bag);
+      if (it == exitStatus_.end())  // The bag is still running
+      {
+        bagFinished_.wait(lock);
+      }
+      else
+      {
+        bool status = it->second;
+        exitStatus_.erase(it);
+        return status;
+      }
+    }
+
+    return false;   // The processor is stopping
+  }
+
+
+  float BagOfTasksProcessor::GetProgress(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    Bags::const_iterator it = bags_.find(bag);
+    if (it == bags_.end())
+    {
+      // The bag of tasks has finished
+      return 1.0f;
+    }
+    else
+    {
+      return (static_cast<float>(it->second.done_) / 
+              static_cast<float>(it->second.size_));
+    }
+  }
+
+
+  bool BagOfTasksProcessor::Handle::Join()
+  {
+    if (hasJoined_)
+    {
+      return status_;
+    }
+    else
+    {
+      status_ = that_.Join(bag_);
+      hasJoined_ = true;
+      return status_;
+    }
+  }
+
+
+  BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : 
+    countBags_(0),
+    continue_(true)
+  {
+    if (countThreads == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    threads_.resize(countThreads);
+
+    for (size_t i = 0; i < threads_.size(); i++)
+    {
+      threads_[i] = new boost::thread(Worker, this);
+    }
+  }
+
+
+  BagOfTasksProcessor::~BagOfTasksProcessor()
+  {
+    continue_ = false;
+
+    bagFinished_.notify_all();   // Wakes up all the pending "Join()"
+
+    for (size_t i = 0; i < threads_.size(); i++)
+    {
+      if (threads_[i])
+      {
+        if (threads_[i]->joinable())
+        {
+          threads_[i]->join();
+        }
+
+        delete threads_[i];
+        threads_[i] = NULL;
+      }
+    }
+  }
+
+
+  BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks)
+  {
+    if (tasks.GetSize() == 0)
+    {
+      return new Handle(*this, 0, true);
+    }
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    uint64_t id = countBags_;
+    countBags_ += 1;
+
+    Bag bag(tasks.GetSize());
+    bags_[id] = bag;
+
+    while (!tasks.IsEmpty())
+    {
+      queue_.Enqueue(new Task(id, tasks.Pop()));
+    }
+
+    return new Handle(*this, id, false);
+  }
+}
diff --git a/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h b/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h
new file mode 100644
index 0000000..525756e
--- /dev/null
+++ b/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h
@@ -0,0 +1,149 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "BagOfTasks.h"
+#include "SharedMessageQueue.h"
+
+#include <stdint.h>
+#include <map>
+
+namespace Orthanc
+{
+  class BagOfTasksProcessor : public boost::noncopyable
+  {
+  private:
+    enum BagStatus
+    {
+      BagStatus_Running,
+      BagStatus_Canceled,
+      BagStatus_Failed
+    };
+
+
+    struct Bag
+    {
+      size_t    size_;
+      size_t    done_;
+      BagStatus status_;
+
+      Bag() :
+        size_(0),
+        done_(0),
+        status_(BagStatus_Failed)
+      {
+      }
+
+      Bag(size_t size) : 
+      size_(size),
+      done_(0),
+      status_(BagStatus_Running)
+      {
+      }
+    };
+
+    class Task;
+
+
+    typedef std::map<uint64_t, Bag>   Bags;
+    typedef std::map<uint64_t, bool>  ExitStatus;
+
+    SharedMessageQueue  queue_;
+
+    boost::mutex  mutex_;
+    uint64_t  countBags_;
+    Bags bags_;
+    std::vector<boost::thread*>   threads_;
+    ExitStatus  exitStatus_;
+    bool continue_;
+
+    boost::condition_variable  bagFinished_;
+
+    static void Worker(BagOfTasksProcessor* that);
+
+    void Cancel(int64_t bag);
+
+    bool Join(int64_t bag);
+
+    float GetProgress(int64_t bag);
+
+    void SignalProgress(Task& task,
+                        Bag& bag);
+
+  public:
+    class Handle : public boost::noncopyable
+    {
+      friend class BagOfTasksProcessor;
+
+    private:
+      BagOfTasksProcessor&  that_;
+      uint64_t              bag_;
+      bool                  hasJoined_;
+      bool                  status_;
+ 
+      Handle(BagOfTasksProcessor&  that,
+             uint64_t bag,
+             bool empty) : 
+        that_(that),
+        bag_(bag),
+        hasJoined_(empty)
+      {
+      }
+
+    public:
+      ~Handle()
+      {
+        Join();
+      }
+
+      void Cancel()
+      {
+        that_.Cancel(bag_);
+      }
+
+      bool Join();
+
+      float GetProgress()
+      {
+        return that_.GetProgress(bag_);
+      }
+    };
+  
+
+    BagOfTasksProcessor(size_t countThreads);
+
+    ~BagOfTasksProcessor();
+
+    Handle* Submit(BagOfTasks& tasks);
+  };
+}
diff --git a/Framework/Orthanc/Core/MultiThreading/Semaphore.cpp b/Framework/Orthanc/Core/MultiThreading/Semaphore.cpp
new file mode 100644
index 0000000..82d5aa5
--- /dev/null
+++ b/Framework/Orthanc/Core/MultiThreading/Semaphore.cpp
@@ -0,0 +1,64 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "Semaphore.h"
+
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  Semaphore::Semaphore(unsigned int count) : count_(count)
+  {
+  }
+
+  void Semaphore::Release()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    count_++;
+    condition_.notify_one(); 
+  }
+
+  void Semaphore::Acquire()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (count_ == 0)
+    {
+      condition_.wait(lock);
+    }
+
+    count_++;
+  }
+}
diff --git a/Framework/Orthanc/Core/MultiThreading/Semaphore.h b/Framework/Orthanc/Core/MultiThreading/Semaphore.h
new file mode 100644
index 0000000..9d5354c
--- /dev/null
+++ b/Framework/Orthanc/Core/MultiThreading/Semaphore.h
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class Semaphore : public boost::noncopyable
+  {
+  private:
+    unsigned int count_;
+    boost::mutex mutex_;
+    boost::condition_variable condition_;
+
+  public:
+    explicit Semaphore(unsigned int count);
+
+    void Release();
+
+    void Acquire();
+
+    class Locker : public boost::noncopyable
+    {
+    private:
+      Semaphore&  that_;
+
+    public:
+      Locker(Semaphore& that) :
+        that_(that)
+      {
+        that_.Acquire();
+      }
+
+      ~Locker()
+      {
+        that_.Release();
+      }
+    };
+  };
+}
diff --git a/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp b/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp
new file mode 100644
index 0000000..0c1a727
--- /dev/null
+++ b/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp
@@ -0,0 +1,208 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "SharedMessageQueue.h"
+
+
+
+/**
+ * FIFO (queue):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ * Enqueue -> |  |  |  |  |  |  |  |  |  |  |  |
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *                                            ^
+ *                                            |
+ *                                      Make room here
+ *
+ *
+ * LIFO (stack):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *            |  |  |  |  |  |  |  |  |  |  |  | <- Enqueue
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *              ^
+ *              |
+ *        Make room here
+ **/
+
+
+namespace Orthanc
+{
+  SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) :
+    isFifo_(true),
+    maxSize_(maxSize)
+  {
+  }
+
+
+  SharedMessageQueue::~SharedMessageQueue()
+  {
+    for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void SharedMessageQueue::Enqueue(IDynamicObject* message)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (maxSize_ != 0 && queue_.size() > maxSize_)
+    {
+      if (isFifo_)
+      {
+        // Too many elements in the queue: Make room
+        delete queue_.front();
+        queue_.pop_front();
+      }
+      else
+      {
+        // Too many elements in the stack: Make room
+        delete queue_.back();
+        queue_.pop_back();
+      }
+    }
+
+    if (isFifo_)
+    {
+      // Queue policy (FIFO)
+      queue_.push_back(message);
+    }
+    else
+    {
+      // Stack policy (LIFO)
+      queue_.push_front(message);
+    }
+
+    elementAvailable_.notify_one();
+  }
+
+
+  IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Wait for a message to arrive in the FIFO queue
+    while (queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        elementAvailable_.wait(lock);
+      }
+      else
+      {
+        bool success = elementAvailable_.timed_wait
+          (lock, boost::posix_time::milliseconds(millisecondsTimeout));
+        if (!success)
+        {
+          return NULL;
+        }
+      }
+    }
+
+    std::auto_ptr<IDynamicObject> message(queue_.front());
+    queue_.pop_front();
+
+    if (queue_.empty())
+    {
+      emptied_.notify_all();
+    }
+
+    return message.release();
+  }
+
+
+
+  bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    
+    // Wait for the queue to become empty
+    while (!queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        emptied_.wait(lock);
+      }
+      else
+      {
+        if (!emptied_.timed_wait
+            (lock, boost::posix_time::milliseconds(millisecondsTimeout)))
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+  void SharedMessageQueue::SetFifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = true;
+  }
+
+  void SharedMessageQueue::SetLifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = false;
+  }
+
+  void SharedMessageQueue::Clear()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (queue_.empty())
+    {
+      return;
+    }
+    else
+    {
+      while (!queue_.empty())
+      {
+        std::auto_ptr<IDynamicObject> message(queue_.front());
+        queue_.pop_front();
+      }
+
+      emptied_.notify_all();
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.h b/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.h
new file mode 100644
index 0000000..211b774
--- /dev/null
+++ b/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.h
@@ -0,0 +1,84 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IDynamicObject.h"
+
+#include <stdint.h>
+#include <list>
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class SharedMessageQueue : public boost::noncopyable
+  {
+  private:
+    typedef std::list<IDynamicObject*>  Queue;
+
+    bool isFifo_;
+    unsigned int maxSize_;
+    Queue queue_;
+    boost::mutex mutex_;
+    boost::condition_variable elementAvailable_;
+    boost::condition_variable emptied_;
+
+  public:
+    explicit SharedMessageQueue(unsigned int maxSize = 0);
+    
+    ~SharedMessageQueue();
+
+    // This transfers the ownership of the message
+    void Enqueue(IDynamicObject* message);
+
+    // The caller is responsible to delete the dequeud message!
+    IDynamicObject* Dequeue(int32_t millisecondsTimeout);
+
+    bool WaitEmpty(int32_t millisecondsTimeout);
+
+    bool IsFifoPolicy() const
+    {
+      return isFifo_;
+    }
+
+    bool IsLifoPolicy() const
+    {
+      return !isFifo_;
+    }
+
+    void SetFifoPolicy();
+
+    void SetLifoPolicy();
+
+    void Clear();
+  };
+}
diff --git a/Framework/Orthanc/Core/OrthancException.h b/Framework/Orthanc/Core/OrthancException.h
new file mode 100644
index 0000000..5afa41f
--- /dev/null
+++ b/Framework/Orthanc/Core/OrthancException.h
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <stdint.h>
+#include <string>
+#include "Enumerations.h"
+
+namespace Orthanc
+{
+  class OrthancException
+  {
+  protected:
+    ErrorCode  errorCode_;
+    HttpStatus httpStatus_;
+
+  public:
+    OrthancException(ErrorCode errorCode) : 
+      errorCode_(errorCode),
+      httpStatus_(ConvertErrorCodeToHttpStatus(errorCode))
+    {
+    }
+
+    OrthancException(ErrorCode errorCode,
+                     HttpStatus httpStatus) :
+      errorCode_(errorCode),
+      httpStatus_(httpStatus)
+    {
+    }
+
+    ErrorCode GetErrorCode() const
+    {
+      return errorCode_;
+    }
+
+    HttpStatus GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    const char* What() const
+    {
+      return EnumerationToString(errorCode_);
+    }
+  };
+}
diff --git a/Framework/Orthanc/Core/PrecompiledHeaders.cpp b/Framework/Orthanc/Core/PrecompiledHeaders.cpp
new file mode 100644
index 0000000..01ea20f
--- /dev/null
+++ b/Framework/Orthanc/Core/PrecompiledHeaders.cpp
@@ -0,0 +1,33 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
diff --git a/Framework/Orthanc/Core/PrecompiledHeaders.h b/Framework/Orthanc/Core/PrecompiledHeaders.h
new file mode 100644
index 0000000..e22f012
--- /dev/null
+++ b/Framework/Orthanc/Core/PrecompiledHeaders.h
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if defined(_WIN32) && !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+
+#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/locale.hpp>
+#include <boost/regex.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/shared_mutex.hpp>
+
+#include <json/value.h>
+
+#if ORTHANC_PUGIXML_ENABLED == 1
+#include <pugixml.hpp>
+#endif
+
+#include "Enumerations.h"
+#include "Logging.h"
+#include "OrthancException.h"
+#include "Toolbox.h"
+#include "Uuid.h"
+
+#endif
diff --git a/Framework/Orthanc/Core/Toolbox.cpp b/Framework/Orthanc/Core/Toolbox.cpp
new file mode 100644
index 0000000..ac1ad51
--- /dev/null
+++ b/Framework/Orthanc/Core/Toolbox.cpp
@@ -0,0 +1,1682 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "Toolbox.h"
+
+#include "OrthancException.h"
+#include "Logging.h"
+
+#include <string>
+#include <stdint.h>
+#include <string.h>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/uuid/sha1.hpp>
+#include <boost/lexical_cast.hpp>
+#include <algorithm>
+#include <ctype.h>
+
+#if BOOST_HAS_DATE_TIME == 1
+#include <boost/date_time/posix_time/posix_time.hpp>
+#endif
+
+#if BOOST_HAS_REGEX == 1
+#include <boost/regex.hpp> 
+#endif
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <process.h>   // For "_spawnvp()" and "_getpid()"
+#else
+#include <unistd.h>    // For "execvp()"
+#include <sys/wait.h>  // For "waitpid()"
+#endif
+
+#if defined(__APPLE__) && defined(__MACH__)
+#include <mach-o/dyld.h> /* _NSGetExecutablePath */
+#include <limits.h>      /* PATH_MAX */
+#endif
+
+#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#include <limits.h>      /* PATH_MAX */
+#include <signal.h>
+#include <unistd.h>
+#endif
+
+#if BOOST_HAS_LOCALE != 1
+#error Since version 0.7.6, Orthanc entirely relies on boost::locale
+#endif
+
+#include <boost/locale.hpp>
+
+
+#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
+#include "../Resources/ThirdParty/md5/md5.h"
+#endif
+
+
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+#include "../Resources/ThirdParty/base64/base64.h"
+#endif
+
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013
+extern "C"
+{
+  int64_t _strtoi64(const char *nptr, char **endptr, int base);
+  int64_t strtoll(const char *nptr, char **endptr, int base)
+  {
+    return _strtoi64(nptr, endptr, base);
+  } 
+}
+#endif
+
+
+#if ORTHANC_PUGIXML_ENABLED == 1
+#include "ChunkedBuffer.h"
+#include <pugixml.hpp>
+#endif
+
+
+namespace Orthanc
+{
+  void Toolbox::USleep(uint64_t microSeconds)
+  {
+#if defined(_WIN32)
+    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
+#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__native_client__)
+    usleep(microSeconds);
+#else
+#error Support your platform here
+#endif
+  }
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  static bool finish_;
+  static ServerBarrierEvent barrierEvent_;
+
+#if defined(_WIN32)
+  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
+  {
+    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
+    finish_ = true;
+    return true;
+  }
+#else
+  static void SignalHandler(int signal)
+  {
+    if (signal == SIGHUP)
+    {
+      barrierEvent_ = ServerBarrierEvent_Reload;
+    }
+
+    finish_ = true;
+  }
+#endif
+
+
+  static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag)
+  {
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, true);
+#else
+    signal(SIGINT, SignalHandler);
+    signal(SIGQUIT, SignalHandler);
+    signal(SIGTERM, SignalHandler);
+    signal(SIGHUP, SignalHandler);
+#endif
+  
+    // Active loop that awakens every 100ms
+    finish_ = false;
+    barrierEvent_ = ServerBarrierEvent_Stop;
+    while (!(*stopFlag || finish_))
+    {
+      Toolbox::USleep(100 * 1000);
+    }
+
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, false);
+#else
+    signal(SIGINT, NULL);
+    signal(SIGQUIT, NULL);
+    signal(SIGTERM, NULL);
+    signal(SIGHUP, NULL);
+#endif
+
+    return barrierEvent_;
+  }
+
+
+  ServerBarrierEvent Toolbox::ServerBarrier(const bool& stopFlag)
+  {
+    return ServerBarrierInternal(&stopFlag);
+  }
+
+  ServerBarrierEvent Toolbox::ServerBarrier()
+  {
+    const bool stopFlag = false;
+    return ServerBarrierInternal(&stopFlag);
+  }
+#endif  /* ORTHANC_SANDBOXED */
+
+
+  void Toolbox::ToUpperCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), toupper);
+  }
+
+
+  void Toolbox::ToLowerCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), tolower);
+  }
+
+
+  void Toolbox::ToUpperCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToUpperCase(result);
+  }
+
+  void Toolbox::ToLowerCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToLowerCase(result);
+  }
+
+
+  static std::streamsize GetStreamSize(std::istream& f)
+  {
+    // http://www.cplusplus.com/reference/iostream/istream/tellg/
+    f.seekg(0, std::ios::end);
+    std::streamsize size = f.tellg();
+    f.seekg(0, std::ios::beg);
+
+    return size;
+  }
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  void Toolbox::ReadFile(std::string& content,
+                         const std::string& path) 
+  {
+    if (!IsRegularFile(path))
+    {
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
+      throw OrthancException(ErrorCode_RegularFileExpected);
+    }
+
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    std::streamsize size = GetStreamSize(f);
+    content.resize(size);
+    if (size != 0)
+    {
+      f.read(reinterpret_cast<char*>(&content[0]), size);
+    }
+
+    f.close();
+  }
+#endif
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  bool Toolbox::ReadHeader(std::string& header,
+                           const std::string& path,
+                           size_t headerSize)
+  {
+    if (!IsRegularFile(path))
+    {
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
+      throw OrthancException(ErrorCode_RegularFileExpected);
+    }
+
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    bool full = true;
+
+    {
+      std::streamsize size = GetStreamSize(f);
+      if (size <= 0)
+      {
+        headerSize = 0;
+        full = false;
+      }
+      else if (static_cast<size_t>(size) < headerSize)
+      {
+        headerSize = size;  // Truncate to the size of the file
+        full = false;
+      }
+    }
+
+    header.resize(headerSize);
+    if (headerSize != 0)
+    {
+      f.read(reinterpret_cast<char*>(&header[0]), headerSize);
+    }
+
+    f.close();
+
+    return full;
+  }
+#endif
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  void Toolbox::WriteFile(const void* content,
+                          size_t size,
+                          const std::string& path)
+  {
+    boost::filesystem::ofstream f;
+    f.open(path, std::ofstream::out | std::ofstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    if (size != 0)
+    {
+      f.write(reinterpret_cast<const char*>(content), size);
+
+      if (!f.good())
+      {
+        f.close();
+        throw OrthancException(ErrorCode_FileStorageCannotWrite);
+      }
+    }
+
+    f.close();
+  }
+#endif
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  void Toolbox::WriteFile(const std::string& content,
+                          const std::string& path)
+  {
+    WriteFile(content.size() > 0 ? content.c_str() : NULL,
+              content.size(), path);
+  }
+#endif
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  void Toolbox::RemoveFile(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (IsRegularFile(path))
+      {
+        boost::filesystem::remove(path);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_RegularFileExpected);
+      }
+    }
+  }
+#endif
+
+
+  void Toolbox::SplitUriComponents(UriComponents& components,
+                                   const std::string& uri)
+  {
+    static const char URI_SEPARATOR = '/';
+
+    components.clear();
+
+    if (uri.size() == 0 ||
+        uri[0] != URI_SEPARATOR)
+    {
+      throw OrthancException(ErrorCode_UriSyntax);
+    }
+
+    // Count the number of slashes in the URI to make an assumption
+    // about the number of components in the URI
+    unsigned int estimatedSize = 0;
+    for (unsigned int i = 0; i < uri.size(); i++)
+    {
+      if (uri[i] == URI_SEPARATOR)
+        estimatedSize++;
+    }
+
+    components.reserve(estimatedSize - 1);
+
+    unsigned int start = 1;
+    unsigned int end = 1;
+    while (end < uri.size())
+    {
+      // This is the loop invariant
+      assert(uri[start - 1] == '/' && (end >= start));
+
+      if (uri[end] == '/')
+      {
+        components.push_back(std::string(&uri[start], end - start));
+        end++;
+        start = end;
+      }
+      else
+      {
+        end++;
+      }
+    }
+
+    if (start < uri.size())
+    {
+      components.push_back(std::string(&uri[start], end - start));
+    }
+
+    for (size_t i = 0; i < components.size(); i++)
+    {
+      if (components[i].size() == 0)
+      {
+        // Empty component, as in: "/coucou//e"
+        throw OrthancException(ErrorCode_UriSyntax);
+      }
+    }
+  }
+
+
+  void Toolbox::TruncateUri(UriComponents& target,
+                            const UriComponents& source,
+                            size_t fromLevel)
+  {
+    target.clear();
+
+    if (source.size() > fromLevel)
+    {
+      target.resize(source.size() - fromLevel);
+
+      size_t j = 0;
+      for (size_t i = fromLevel; i < source.size(); i++, j++)
+      {
+        target[j] = source[i];
+      }
+
+      assert(j == target.size());
+    }
+  }
+  
+
+
+  bool Toolbox::IsChildUri(const UriComponents& baseUri,
+                           const UriComponents& testedUri)
+  {
+    if (testedUri.size() < baseUri.size())
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < baseUri.size(); i++)
+    {
+      if (baseUri[i] != testedUri[i])
+        return false;
+    }
+
+    return true;
+  }
+
+
+  std::string Toolbox::AutodetectMimeType(const std::string& path)
+  {
+    std::string contentType;
+    size_t lastDot = path.rfind('.');
+    size_t lastSlash = path.rfind('/');
+
+    if (lastDot == std::string::npos ||
+        (lastSlash != std::string::npos && lastDot < lastSlash))
+    {
+      // No trailing dot, unable to detect the content type
+    }
+    else
+    {
+      const char* extension = &path[lastDot + 1];
+    
+      // http://en.wikipedia.org/wiki/Mime_types
+      // Text types
+      if (!strcmp(extension, "txt"))
+        contentType = "text/plain";
+      else if (!strcmp(extension, "html"))
+        contentType = "text/html";
+      else if (!strcmp(extension, "xml"))
+        contentType = "text/xml";
+      else if (!strcmp(extension, "css"))
+        contentType = "text/css";
+
+      // Application types
+      else if (!strcmp(extension, "js"))
+        contentType = "application/javascript";
+      else if (!strcmp(extension, "json"))
+        contentType = "application/json";
+      else if (!strcmp(extension, "pdf"))
+        contentType = "application/pdf";
+
+      // Images types
+      else if (!strcmp(extension, "jpg") || !strcmp(extension, "jpeg"))
+        contentType = "image/jpeg";
+      else if (!strcmp(extension, "gif"))
+        contentType = "image/gif";
+      else if (!strcmp(extension, "png"))
+        contentType = "image/png";
+    }
+
+    return contentType;
+  }
+
+
+  std::string Toolbox::FlattenUri(const UriComponents& components,
+                                  size_t fromLevel)
+  {
+    if (components.size() <= fromLevel)
+    {
+      return "/";
+    }
+    else
+    {
+      std::string r;
+
+      for (size_t i = fromLevel; i < components.size(); i++)
+      {
+        r += "/" + components[i];
+      }
+
+      return r;
+    }
+  }
+
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  uint64_t Toolbox::GetFileSize(const std::string& path)
+  {
+    try
+    {
+      return static_cast<uint64_t>(boost::filesystem::file_size(path));
+    }
+    catch (boost::filesystem::filesystem_error&)
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+  }
+#endif
+
+
+#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
+  static char GetHexadecimalCharacter(uint8_t value)
+  {
+    assert(value < 16);
+
+    if (value < 10)
+    {
+      return value + '0';
+    }
+    else
+    {
+      return (value - 10) + 'a';
+    }
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeMD5(result, &data[0], data.size());
+    }
+    else
+    {
+      ComputeMD5(result, NULL, 0);
+    }
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const void* data,
+                           size_t size)
+  {
+    md5_state_s state;
+    md5_init(&state);
+
+    if (size > 0)
+    {
+      md5_append(&state, 
+                 reinterpret_cast<const md5_byte_t*>(data), 
+                 static_cast<int>(size));
+    }
+
+    md5_byte_t actualHash[16];
+    md5_finish(&state, actualHash);
+
+    result.resize(32);
+    for (unsigned int i = 0; i < 16; i++)
+    {
+      result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16));
+      result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16));
+    }
+  }
+#endif
+
+
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+  void Toolbox::EncodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    result = base64_encode(data);
+  }
+
+  void Toolbox::DecodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    for (size_t i = 0; i < data.length(); i++)
+    {
+      if (!isalnum(data[i]) &&
+          data[i] != '+' &&
+          data[i] != '/' &&
+          data[i] != '=')
+      {
+        // This is not a valid character for a Base64 string
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    result = base64_decode(data);
+  }
+
+
+#  if BOOST_HAS_REGEX == 1
+  bool Toolbox::DecodeDataUriScheme(std::string& mime,
+                                    std::string& content,
+                                    const std::string& source)
+  {
+    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
+                         boost::regex::icase /* case insensitive search */);
+
+    boost::cmatch what;
+    if (regex_match(source.c_str(), what, pattern))
+    {
+      mime = what[1];
+      DecodeBase64(content, what[2]);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+#  endif
+
+
+  void Toolbox::EncodeDataUriScheme(std::string& result,
+                                    const std::string& mime,
+                                    const std::string& content)
+  {
+    result = "data:" + mime + ";base64," + base64_encode(content);
+  }
+
+#endif
+
+
+
+#if defined(_WIN32)
+  static std::string GetPathToExecutableInternal()
+  {
+    // Yes, this is ugly, but there is no simple way to get the 
+    // required buffer size, so we use a big constant
+    std::vector<char> buffer(32768);
+    /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1));
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+  static std::string GetPathToExecutableInternal()
+  {
+    std::vector<char> buffer(PATH_MAX + 1);
+    ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1);
+    if (bytes == 0)
+    {
+      throw OrthancException(ErrorCode_PathToExecutable);
+    }
+
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__APPLE__) && defined(__MACH__)
+  static std::string GetPathToExecutableInternal()
+  {
+    char pathbuf[PATH_MAX + 1];
+    unsigned int  bufsize = static_cast<int>(sizeof(pathbuf));
+
+    _NSGetExecutablePath( pathbuf, &bufsize);
+
+    return std::string(pathbuf);
+  }
+
+#elif defined(ORTHANC_SANDBOXED) && ORTHANC_SANDBOXED == 1
+  // Sandboxed Orthanc, no access to the executable
+
+#else
+#error Support your platform here
+#endif
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  std::string Toolbox::GetPathToExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p).string();
+  }
+
+
+  std::string Toolbox::GetDirectoryOfExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p.parent_path()).string();
+  }
+#endif
+
+
+  static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding)
+  {
+    switch (sourceEncoding)
+    {
+      case Encoding_Utf8:
+        return "UTF-8";
+
+      case Encoding_Ascii:
+        return "ASCII";
+
+      case Encoding_Latin1:
+        return "ISO-8859-1";
+        break;
+
+      case Encoding_Latin2:
+        return "ISO-8859-2";
+        break;
+
+      case Encoding_Latin3:
+        return "ISO-8859-3";
+        break;
+
+      case Encoding_Latin4:
+        return "ISO-8859-4";
+        break;
+
+      case Encoding_Latin5:
+        return "ISO-8859-9";
+        break;
+
+      case Encoding_Cyrillic:
+        return "ISO-8859-5";
+        break;
+
+      case Encoding_Windows1251:
+        return "WINDOWS-1251";
+        break;
+
+      case Encoding_Arabic:
+        return "ISO-8859-6";
+        break;
+
+      case Encoding_Greek:
+        return "ISO-8859-7";
+        break;
+
+      case Encoding_Hebrew:
+        return "ISO-8859-8";
+        break;
+        
+      case Encoding_Japanese:
+        return "SHIFT-JIS";
+        break;
+
+      case Encoding_Chinese:
+        return "GB18030";
+        break;
+
+      case Encoding_Thai:
+        return "TIS620.2533-0";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  std::string Toolbox::ConvertToUtf8(const std::string& source,
+                                     Encoding sourceEncoding)
+  {
+    if (sourceEncoding == Encoding_Utf8)
+    {
+      // Already in UTF-8: No conversion is required
+      return source;
+    }
+
+    if (sourceEncoding == Encoding_Ascii)
+    {
+      return ConvertToAscii(source);
+    }
+
+    const char* encoding = GetBoostLocaleEncoding(sourceEncoding);
+
+    try
+    {
+      return boost::locale::conv::to_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+
+
+  std::string Toolbox::ConvertFromUtf8(const std::string& source,
+                                       Encoding targetEncoding)
+  {
+    if (targetEncoding == Encoding_Utf8)
+    {
+      // Already in UTF-8: No conversion is required
+      return source;
+    }
+
+    if (targetEncoding == Encoding_Ascii)
+    {
+      return ConvertToAscii(source);
+    }
+
+    const char* encoding = GetBoostLocaleEncoding(targetEncoding);
+
+    try
+    {
+      return boost::locale::conv::from_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+
+
+  std::string Toolbox::ConvertToAscii(const std::string& source)
+  {
+    std::string result;
+
+    result.reserve(source.size() + 1);
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
+      {
+        result.push_back(source[i]);
+      }
+    }
+
+    return result;
+  }
+
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const void* data,
+                            size_t size)
+  {
+    boost::uuids::detail::sha1 sha1;
+
+    if (size > 0)
+    {
+      sha1.process_bytes(data, size);
+    }
+
+    unsigned int digest[5];
+
+    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
+    assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); 
+    
+    sha1.get_digest(digest);
+
+    result.resize(8 * 5 + 4);
+    sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
+            digest[0],
+            digest[1],
+            digest[2],
+            digest[3],
+            digest[4]);
+  }
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeSHA1(result, data.c_str(), data.size());
+    }
+    else
+    {
+      ComputeSHA1(result, NULL, 0);
+    }
+  }
+
+
+  bool Toolbox::IsSHA1(const char* str,
+                       size_t size)
+  {
+    if (size == 0)
+    {
+      return false;
+    }
+
+    const char* start = str;
+    const char* end = str + size;
+
+    // Trim the beginning of the string
+    while (start < end)
+    {
+      if (*start == '\0' ||
+          isspace(*start))
+      {
+        start++;
+      }
+      else
+      {
+        break;
+      }
+    }
+
+    // Trim the trailing of the string
+    while (start < end)
+    {
+      if (*(end - 1) == '\0' ||
+          isspace(*(end - 1)))
+      {
+        end--;
+      }
+      else
+      {
+        break;
+      }
+    }
+
+    if (end - start != 44)
+    {
+      return false;
+    }
+
+    for (unsigned int i = 0; i < 44; i++)
+    {
+      if (i == 8 ||
+          i == 17 ||
+          i == 26 ||
+          i == 35)
+      {
+        if (start[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(start[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool Toolbox::IsSHA1(const std::string& s)
+  {
+    if (s.size() == 0)
+    {
+      return false;
+    }
+    else
+    {
+      return IsSHA1(s.c_str(), s.size());
+    }
+  }
+
+
+#if BOOST_HAS_DATE_TIME == 1
+  std::string Toolbox::GetNowIsoString()
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    return boost::posix_time::to_iso_string(now);
+  }
+
+  void Toolbox::GetNowDicom(std::string& date,
+                            std::string& time)
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    tm tm = boost::posix_time::to_tm(now);
+
+    char s[32];
+    sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+    date.assign(s);
+
+    // TODO milliseconds
+    sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0);
+    time.assign(s);
+  }
+#endif
+
+
+  std::string Toolbox::StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+
+  static char Hex2Dec(char c)
+  {
+    return ((c >= '0' && c <= '9') ? c - '0' :
+            ((c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - 'A' + 10));
+  }
+
+  void Toolbox::UrlDecode(std::string& s)
+  {
+    // http://en.wikipedia.org/wiki/Percent-encoding
+    // http://www.w3schools.com/tags/ref_urlencode.asp
+    // http://stackoverflow.com/questions/154536/encode-decode-urls-in-c
+
+    if (s.size() == 0)
+    {
+      return;
+    }
+
+    size_t source = 0;
+    size_t target = 0;
+
+    while (source < s.size())
+    {
+      if (s[source] == '%' &&
+          source + 2 < s.size() &&
+          isalnum(s[source + 1]) &&
+          isalnum(s[source + 2]))
+      {
+        s[target] = (Hex2Dec(s[source + 1]) << 4) | Hex2Dec(s[source + 2]);
+        source += 3;
+        target += 1;
+      }
+      else
+      {
+        if (s[source] == '+')
+          s[target] = ' ';
+        else
+          s[target] = s[source];
+
+        source++;
+        target++;
+      }
+    }
+
+    s.resize(target);
+  }
+
+
+  Endianness Toolbox::DetectEndianness()
+  {
+    // http://sourceforge.net/p/predef/wiki/Endianness/
+
+    uint8_t buffer[4];
+
+    buffer[0] = 0x00;
+    buffer[1] = 0x01;
+    buffer[2] = 0x02;
+    buffer[3] = 0x03;
+
+    switch (*((uint32_t *)buffer)) 
+    {
+      case 0x00010203: 
+        return Endianness_Big;
+
+      case 0x03020100: 
+        return Endianness_Little;
+        
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+#if BOOST_HAS_REGEX == 1
+  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
+  {
+    // TODO - Speed up this with a regular expression
+
+    std::string result = source;
+
+    // Escape all special characters
+    boost::replace_all(result, "\\", "\\\\");
+    boost::replace_all(result, "^", "\\^");
+    boost::replace_all(result, ".", "\\.");
+    boost::replace_all(result, "$", "\\$");
+    boost::replace_all(result, "|", "\\|");
+    boost::replace_all(result, "(", "\\(");
+    boost::replace_all(result, ")", "\\)");
+    boost::replace_all(result, "[", "\\[");
+    boost::replace_all(result, "]", "\\]");
+    boost::replace_all(result, "+", "\\+");
+    boost::replace_all(result, "/", "\\/");
+    boost::replace_all(result, "{", "\\{");
+    boost::replace_all(result, "}", "\\}");
+
+    // Convert wildcards '*' and '?' to their regex equivalents
+    boost::replace_all(result, "?", ".");
+    boost::replace_all(result, "*", ".*");
+
+    return result;
+  }
+#endif
+
+
+
+  void Toolbox::TokenizeString(std::vector<std::string>& result,
+                               const std::string& value,
+                               char separator)
+  {
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == separator)
+      {
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
+      }
+    }
+
+    result.push_back(currentItem);
+  }
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  void Toolbox::MakeDirectory(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (!boost::filesystem::is_directory(path))
+      {
+        throw OrthancException(ErrorCode_DirectoryOverFile);
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path))
+      {
+        throw OrthancException(ErrorCode_MakeDirectory);
+      }
+    }
+  }
+#endif
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  bool Toolbox::IsExistingFile(const std::string& path)
+  {
+    return boost::filesystem::exists(path);
+  }
+#endif
+
+
+#if ORTHANC_PUGIXML_ENABLED == 1
+  class ChunkedBufferWriter : public pugi::xml_writer
+  {
+  private:
+    ChunkedBuffer buffer_;
+
+  public:
+    virtual void write(const void *data, size_t size)
+    {
+      if (size > 0)
+      {
+        buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
+      }
+    }
+
+    void Flatten(std::string& s)
+    {
+      buffer_.Flatten(s);
+    }
+  };
+
+
+  static void JsonToXmlInternal(pugi::xml_node& target,
+                                const Json::Value& source,
+                                const std::string& arrayElement)
+  {
+    // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030
+
+    switch (source.type())
+    {
+      case Json::nullValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value("null");
+        break;
+      }
+
+      case Json::intValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::uintValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asUInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::realValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asFloat());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::stringValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asString().c_str());
+        break;
+      }
+
+      case Json::booleanValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false");
+        break;
+      }
+
+      case Json::arrayValue:
+      {
+        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(arrayElement.c_str());
+          JsonToXmlInternal(node, source[i], arrayElement);
+        }
+        break;
+      }
+        
+      case Json::objectValue:
+      {
+        Json::Value::Members members = source.getMemberNames();
+
+        for (size_t i = 0; i < members.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(members[i].c_str());
+          JsonToXmlInternal(node, source[members[i]], arrayElement);          
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void Toolbox::JsonToXml(std::string& target,
+                          const Json::Value& source,
+                          const std::string& rootElement,
+                          const std::string& arrayElement)
+  {
+    pugi::xml_document doc;
+
+    pugi::xml_node n = doc.append_child(rootElement.c_str());
+    JsonToXmlInternal(n, source, arrayElement);
+
+    pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
+    decl.append_attribute("version").set_value("1.0");
+    decl.append_attribute("encoding").set_value("utf-8");
+
+    ChunkedBufferWriter writer;
+    doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
+    writer.Flatten(target);
+  }
+
+#endif
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  void Toolbox::ExecuteSystemCommand(const std::string& command,
+                                     const std::vector<std::string>& arguments)
+  {
+    // Convert the arguments as a C array
+    std::vector<char*>  args(arguments.size() + 2);
+
+    args.front() = const_cast<char*>(command.c_str());
+
+    for (size_t i = 0; i < arguments.size(); i++)
+    {
+      args[i + 1] = const_cast<char*>(arguments[i].c_str());
+    }
+
+    args.back() = NULL;
+
+    int status;
+
+#if defined(_WIN32)
+    // http://msdn.microsoft.com/en-us/library/275khfab.aspx
+    status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0]));
+
+#else
+    int pid = fork();
+
+    if (pid == -1)
+    {
+      // Error in fork()
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Cannot fork a child process";
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+    else if (pid == 0)
+    {
+      // Execute the system command in the child process
+      execvp(command.c_str(), &args[0]);
+
+      // We should never get here
+      _exit(1);
+    }
+    else
+    {
+      // Wait for the system command to exit
+      waitpid(pid, &status, 0);
+    }
+#endif
+
+    if (status != 0)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "System command failed with status code " << status;
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+  }
+#endif
+
+  
+  bool Toolbox::IsInteger(const std::string& str)
+  {
+    std::string s = StripSpaces(str);
+
+    if (s.size() == 0)
+    {
+      return false;
+    }
+
+    size_t pos = 0;
+    if (s[0] == '-')
+    {
+      if (s.size() == 1)
+      {
+        return false;
+      }
+
+      pos = 1;
+    }
+
+    while (pos < s.size())
+    {
+      if (!isdigit(s[pos]))
+      {
+        return false;
+      }
+
+      pos++;
+    }
+
+    return true;
+  }
+
+
+  void Toolbox::CopyJsonWithoutComments(Json::Value& target,
+                                        const Json::Value& source)
+  {
+    switch (source.type())
+    {
+      case Json::nullValue:
+        target = Json::nullValue;
+        break;
+
+      case Json::intValue:
+        target = source.asInt64();
+        break;
+
+      case Json::uintValue:
+        target = source.asUInt64();
+        break;
+
+      case Json::realValue:
+        target = source.asDouble();
+        break;
+
+      case Json::stringValue:
+        target = source.asString();
+        break;
+
+      case Json::booleanValue:
+        target = source.asBool();
+        break;
+
+      case Json::arrayValue:
+      {
+        target = Json::arrayValue;
+        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
+        {
+          Json::Value& item = target.append(Json::nullValue);
+          CopyJsonWithoutComments(item, source[i]);
+        }
+
+        break;
+      }
+
+      case Json::objectValue:
+      {
+        target = Json::objectValue;
+        Json::Value::Members members = source.getMemberNames();
+        for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
+        {
+          const std::string item = members[i];
+          CopyJsonWithoutComments(target[item], source[item]);
+        }
+
+        break;
+      }
+
+      default:
+        break;
+    }
+  }
+
+
+  bool Toolbox::StartsWith(const std::string& str,
+                           const std::string& prefix)
+  {
+    if (str.size() < prefix.size())
+    {
+      return false;
+    }
+    else
+    {
+      return str.compare(0, prefix.size(), prefix) == 0;
+    }
+  }
+
+
+  int Toolbox::GetProcessId()
+  {
+#if defined(_WIN32)
+    return static_cast<int>(_getpid());
+#else
+    return static_cast<int>(getpid());
+#endif
+  }
+
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+  bool Toolbox::IsRegularFile(const std::string& path)
+  {
+    namespace fs = boost::filesystem;
+
+    try
+    {
+      if (fs::exists(path))
+      {
+        fs::file_status status = fs::status(path);
+        return (status.type() == boost::filesystem::regular_file ||
+                status.type() == boost::filesystem::reparse_file);   // Fix BitBucket issue #11
+      }
+    }
+    catch (fs::filesystem_error&)
+    {
+    }
+
+    return false;
+  }
+#endif
+
+
+  FILE* Toolbox::OpenFile(const std::string& path,
+                          FileMode mode)
+  {
+#if defined(_WIN32)
+    // TODO Deal with special characters by converting to the current locale
+#endif
+
+    const char* m;
+    switch (mode)
+    {
+      case FileMode_ReadBinary:
+        m = "rb";
+        break;
+
+      case FileMode_WriteBinary:
+        m = "wb";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    return fopen(path.c_str(), m);
+  }
+
+  
+
+  static bool IsUnreservedCharacter(char c)
+  {
+    // This function checks whether "c" is an unserved character
+    // wrt. an URI percent-encoding
+    // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding%5Fin%5Fa%5FURI
+
+    return ((c >= 'A' && c <= 'Z') ||
+            (c >= 'a' && c <= 'z') ||
+            (c >= '0' && c <= '9') ||
+            c == '-' ||
+            c == '_' ||
+            c == '.' ||
+            c == '~');
+  }
+
+  void Toolbox::UriEncode(std::string& target,
+                          const std::string& source)
+  {
+    // Estimate the length of the percent-encoded URI
+    size_t length = 0;
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (IsUnreservedCharacter(source[i]))
+      {
+        length += 1;
+      }
+      else
+      {
+        // This character must be percent-encoded
+        length += 3;
+      }
+    }
+
+    target.clear();
+    target.reserve(length);
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (IsUnreservedCharacter(source[i]))
+      {
+        target.push_back(source[i]);
+      }
+      else
+      {
+        // This character must be percent-encoded
+        uint8_t byte = static_cast<uint8_t>(source[i]);
+        uint8_t a = byte >> 4;
+        uint8_t b = byte & 0x0f;
+
+        target.push_back('%');
+        target.push_back(a < 10 ? a + '0' : a - 10 + 'A');
+        target.push_back(b < 10 ? b + '0' : b - 10 + 'A');
+      }
+    }
+  }
+
+
+  static bool HasField(const Json::Value& json,
+                       const std::string& key,
+                       Json::ValueType expectedType)
+  {
+    if (json.type() != Json::objectValue ||
+        !json.isMember(key))
+    {
+      return false;
+    }
+    else if (json[key].type() == expectedType)
+    {
+      return true;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+  }
+
+
+  std::string Toolbox::GetJsonStringField(const Json::Value& json,
+                                          const std::string& key,
+                                          const std::string& defaultValue)
+  {
+    if (HasField(json, key, Json::stringValue))
+    {
+      return json[key].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool Toolbox::GetJsonBooleanField(const ::Json::Value& json,
+                                    const std::string& key,
+                                    bool defaultValue)
+  {
+    if (HasField(json, key, Json::booleanValue))
+    {
+      return json[key].asBool();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  int Toolbox::GetJsonIntegerField(const ::Json::Value& json,
+                                   const std::string& key,
+                                   int defaultValue)
+  {
+    if (HasField(json, key, Json::intValue))
+    {
+      return json[key].asInt();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  unsigned int Toolbox::GetJsonUnsignedIntegerField(const ::Json::Value& json,
+                                                    const std::string& key,
+                                                    unsigned int defaultValue)
+  {
+    int v = GetJsonIntegerField(json, key, defaultValue);
+
+    if (v < 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return static_cast<unsigned int>(v);
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/Toolbox.h b/Framework/Orthanc/Core/Toolbox.h
new file mode 100644
index 0000000..98195bc
--- /dev/null
+++ b/Framework/Orthanc/Core/Toolbox.h
@@ -0,0 +1,246 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Enumerations.h"
+
+#include <stdint.h>
+#include <vector>
+#include <string>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  typedef std::vector<std::string> UriComponents;
+
+  class NullType
+  {
+  };
+
+  namespace Toolbox
+  {
+    void USleep(uint64_t microSeconds);
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    ServerBarrierEvent ServerBarrier(const bool& stopFlag);
+
+    ServerBarrierEvent ServerBarrier();
+#endif
+
+    void ToUpperCase(std::string& s);  // Inplace version
+
+    void ToLowerCase(std::string& s);  // Inplace version
+
+    void ToUpperCase(std::string& result,
+                     const std::string& source);
+
+    void ToLowerCase(std::string& result,
+                     const std::string& source);
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    void ReadFile(std::string& content,
+                  const std::string& path);
+#endif
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    bool ReadHeader(std::string& header,
+                    const std::string& path,
+                    size_t headerSize);
+#endif
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    void WriteFile(const std::string& content,
+                   const std::string& path);
+#endif
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    void WriteFile(const void* content,
+                   size_t size,
+                   const std::string& path);
+#endif
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    void RemoveFile(const std::string& path);
+#endif
+
+    void SplitUriComponents(UriComponents& components,
+                            const std::string& uri);
+  
+    void TruncateUri(UriComponents& target,
+                     const UriComponents& source,
+                     size_t fromLevel);
+  
+    bool IsChildUri(const UriComponents& baseUri,
+                    const UriComponents& testedUri);
+
+    std::string AutodetectMimeType(const std::string& path);
+
+    std::string FlattenUri(const UriComponents& components,
+                           size_t fromLevel = 0);
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    uint64_t GetFileSize(const std::string& path);
+#endif
+
+#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
+    void ComputeMD5(std::string& result,
+                    const std::string& data);
+
+    void ComputeMD5(std::string& result,
+                    const void* data,
+                    size_t size);
+#endif
+
+    void ComputeSHA1(std::string& result,
+                     const std::string& data);
+
+    void ComputeSHA1(std::string& result,
+                     const void* data,
+                     size_t size);
+
+    bool IsSHA1(const char* str,
+                size_t size);
+
+    bool IsSHA1(const std::string& s);
+
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+    void DecodeBase64(std::string& result, 
+                      const std::string& data);
+
+    void EncodeBase64(std::string& result, 
+                      const std::string& data);
+
+#  if BOOST_HAS_REGEX == 1
+    bool DecodeDataUriScheme(std::string& mime,
+                             std::string& content,
+                             const std::string& source);
+#  endif
+
+    void EncodeDataUriScheme(std::string& result,
+                             const std::string& mime,
+                             const std::string& content);
+#endif
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    std::string GetPathToExecutable();
+
+    std::string GetDirectoryOfExecutable();
+#endif
+
+    std::string ConvertToUtf8(const std::string& source,
+                              Encoding sourceEncoding);
+
+    std::string ConvertFromUtf8(const std::string& source,
+                                Encoding targetEncoding);
+
+    std::string ConvertToAscii(const std::string& source);
+
+    std::string StripSpaces(const std::string& source);
+
+#if BOOST_HAS_DATE_TIME == 1
+    std::string GetNowIsoString();
+
+    void GetNowDicom(std::string& date,
+                     std::string& time);
+#endif
+
+    // In-place percent-decoding for URL
+    void UrlDecode(std::string& s);
+
+    Endianness DetectEndianness();
+
+#if BOOST_HAS_REGEX == 1
+    std::string WildcardToRegularExpression(const std::string& s);
+#endif
+
+    void TokenizeString(std::vector<std::string>& result,
+                        const std::string& source,
+                        char separator);
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    void MakeDirectory(const std::string& path);
+#endif
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    bool IsExistingFile(const std::string& path);
+#endif
+
+#if ORTHANC_PUGIXML_ENABLED == 1
+    void JsonToXml(std::string& target,
+                   const Json::Value& source,
+                   const std::string& rootElement = "root",
+                   const std::string& arrayElement = "item");
+#endif
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    void ExecuteSystemCommand(const std::string& command,
+                              const std::vector<std::string>& arguments);
+#endif
+
+    bool IsInteger(const std::string& str);
+
+    void CopyJsonWithoutComments(Json::Value& target,
+                                 const Json::Value& source);
+
+    bool StartsWith(const std::string& str,
+                    const std::string& prefix);
+
+    int GetProcessId();
+
+#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
+    bool IsRegularFile(const std::string& path);
+#endif
+
+    FILE* OpenFile(const std::string& path,
+                   FileMode mode);
+
+    void UriEncode(std::string& target,
+                   const std::string& source);
+
+    std::string GetJsonStringField(const ::Json::Value& json,
+                                   const std::string& key,
+                                   const std::string& defaultValue);
+
+    bool GetJsonBooleanField(const ::Json::Value& json,
+                             const std::string& key,
+                             bool defaultValue);
+
+    int GetJsonIntegerField(const ::Json::Value& json,
+                            const std::string& key,
+                            int defaultValue);
+
+    unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json,
+                                             const std::string& key,
+                                             unsigned int defaultValue);
+  }
+}
diff --git a/Framework/Orthanc/Core/Uuid.cpp b/Framework/Orthanc/Core/Uuid.cpp
new file mode 100644
index 0000000..8681095
--- /dev/null
+++ b/Framework/Orthanc/Core/Uuid.cpp
@@ -0,0 +1,162 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "Uuid.h"
+
+// http://stackoverflow.com/a/1626302
+
+extern "C"
+{
+#ifdef WIN32
+#include <rpc.h>
+#else
+#include <uuid/uuid.h>
+#endif
+}
+
+#include <boost/filesystem.hpp>
+
+namespace Orthanc
+{
+  namespace Toolbox
+  {
+    std::string GenerateUuid()
+    {
+#ifdef WIN32
+      UUID uuid;
+      UuidCreate ( &uuid );
+
+      unsigned char * str;
+      UuidToStringA ( &uuid, &str );
+
+      std::string s( ( char* ) str );
+
+      RpcStringFreeA ( &str );
+#else
+      uuid_t uuid;
+      uuid_generate_random ( uuid );
+      char s[37];
+      uuid_unparse ( uuid, s );
+#endif
+      return s;
+    }
+
+
+    bool IsUuid(const std::string& str)
+    {
+      if (str.size() != 36)
+      {
+        return false;
+      }
+
+      for (size_t i = 0; i < str.length(); i++)
+      {
+        if (i == 8 || i == 13 || i == 18 || i == 23)
+        {
+          if (str[i] != '-')
+            return false;
+        }
+        else
+        {
+          if (!isalnum(str[i]))
+            return false;
+        }
+      }
+
+      return true;
+    }
+
+
+    bool StartsWithUuid(const std::string& str)
+    {
+      if (str.size() < 36)
+      {
+        return false;
+      }
+
+      if (str.size() == 36)
+      {
+        return IsUuid(str);
+      }
+
+      assert(str.size() > 36);
+      if (!isspace(str[36]))
+      {
+        return false;
+      }
+
+      return IsUuid(str.substr(0, 36));
+    }
+
+
+    static std::string CreateTemporaryPath(const char* extension)
+    {
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+      boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path();
+#elif defined(__linux__)
+      boost::filesystem::path tmpDir("/tmp");
+#else
+#error Support your platform here
+#endif
+
+      // We use UUID to create unique path to temporary files
+      std::string filename = "Orthanc-" + Orthanc::Toolbox::GenerateUuid();
+
+      if (extension != NULL)
+      {
+        filename.append(extension);
+      }
+
+      tmpDir /= filename;
+      return tmpDir.string();
+    }
+
+
+    TemporaryFile::TemporaryFile() : 
+      path_(CreateTemporaryPath(NULL))
+    {
+    }
+
+
+    TemporaryFile::TemporaryFile(const char* extension) :
+      path_(CreateTemporaryPath(extension))
+    {
+    }
+
+
+    TemporaryFile::~TemporaryFile()
+    {
+      boost::filesystem::remove(path_);
+    }  
+  }
+}
diff --git a/Framework/Orthanc/Core/Uuid.h b/Framework/Orthanc/Core/Uuid.h
new file mode 100644
index 0000000..88819a3
--- /dev/null
+++ b/Framework/Orthanc/Core/Uuid.h
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+
+/**
+ * GUID vs. UUID
+ * The simple answer is: no difference, they are the same thing. Treat
+ * them as a 16 byte (128 bits) value that is used as a unique
+ * value. In Microsoft-speak they are called GUIDs, but call them
+ * UUIDs when not using Microsoft-speak.
+ * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid
+ **/
+
+#include "Toolbox.h"
+
+namespace Orthanc
+{
+  namespace Toolbox
+  {
+    std::string GenerateUuid();
+
+    bool IsUuid(const std::string& str);
+
+    bool StartsWithUuid(const std::string& str);
+
+    class TemporaryFile
+    {
+    private:
+      std::string path_;
+
+    public:
+      TemporaryFile();
+
+      TemporaryFile(const char* extension);
+
+      ~TemporaryFile();
+
+      const std::string& GetPath() const
+      {
+        return path_;
+      }
+
+      void Write(const std::string& content)
+      {
+        Toolbox::WriteFile(content, path_);
+      }
+
+      void Read(std::string& content) const
+      {
+        Toolbox::ReadFile(content, path_);
+      }
+    };
+  }
+}
diff --git a/Framework/Orthanc/Core/WebServiceParameters.cpp b/Framework/Orthanc/Core/WebServiceParameters.cpp
new file mode 100644
index 0000000..52e0ea6
--- /dev/null
+++ b/Framework/Orthanc/Core/WebServiceParameters.cpp
@@ -0,0 +1,265 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "WebServiceParameters.h"
+
+#include "../Core/Logging.h"
+#include "../Core/Toolbox.h"
+#include "../Core/OrthancException.h"
+
+#include <cassert>
+
+namespace Orthanc
+{
+  WebServiceParameters::WebServiceParameters() : 
+    advancedFormat_(false),
+    url_("http://127.0.0.1:8042/"),
+    pkcs11Enabled_(false)
+  {
+  }
+
+
+  void WebServiceParameters::ClearClientCertificate()
+  {
+    certificateFile_.clear();
+    certificateKeyFile_.clear();
+    certificateKeyPassword_.clear();
+  }
+
+
+  void WebServiceParameters::SetClientCertificate(const std::string& certificateFile,
+                                                  const std::string& certificateKeyFile,
+                                                  const std::string& certificateKeyPassword)
+  {
+    if (certificateFile.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (!Toolbox::IsRegularFile(certificateFile))
+    {
+      LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    if (!certificateKeyFile.empty() && 
+        !Toolbox::IsRegularFile(certificateKeyFile))
+    {
+      LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    advancedFormat_ = true;
+    certificateFile_ = certificateFile;
+    certificateKeyFile_ = certificateKeyFile;
+    certificateKeyPassword_ = certificateKeyPassword;
+  }
+
+
+  static void AddTrailingSlash(std::string& url)
+  {
+    if (url.size() != 0 && 
+        url[url.size() - 1] != '/')
+    {
+      url += '/';
+    }
+  }
+
+
+  void WebServiceParameters::FromJsonArray(const Json::Value& peer)
+  {
+    assert(peer.isArray());
+
+    advancedFormat_ = false;
+    pkcs11Enabled_ = false;
+
+    if (peer.size() != 1 && 
+        peer.size() != 3)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    std::string url = peer.get(0u, "").asString();
+    if (url.empty())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    AddTrailingSlash(url);
+    SetUrl(url);
+
+    if (peer.size() == 1)
+    {
+      SetUsername("");
+      SetPassword("");
+    }
+    else if (peer.size() == 3)
+    {
+      SetUsername(peer.get(1u, "").asString());
+      SetPassword(peer.get(2u, "").asString());
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  static std::string GetStringMember(const Json::Value& peer,
+                                     const std::string& key,
+                                     const std::string& defaultValue)
+  {
+    if (!peer.isMember(key))
+    {
+      return defaultValue;
+    }
+    else if (peer[key].type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      return peer[key].asString();
+    }
+  }
+
+
+  void WebServiceParameters::FromJsonObject(const Json::Value& peer)
+  {
+    assert(peer.isObject());
+    advancedFormat_ = true;
+
+    std::string url = GetStringMember(peer, "Url", "");
+    if (url.empty())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    AddTrailingSlash(url);
+    SetUrl(url);
+
+    SetUsername(GetStringMember(peer, "Username", ""));
+    SetPassword(GetStringMember(peer, "Password", ""));
+
+    if (peer.isMember("CertificateFile"))
+    {
+      SetClientCertificate(GetStringMember(peer, "CertificateFile", ""),
+                           GetStringMember(peer, "CertificateKeyFile", ""),
+                           GetStringMember(peer, "CertificateKeyPassword", ""));
+    }
+
+    if (peer.isMember("Pkcs11"))
+    {
+      if (peer["Pkcs11"].type() == Json::booleanValue)
+      {
+        pkcs11Enabled_ = peer["Pkcs11"].asBool();
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+  }
+
+
+  void WebServiceParameters::FromJson(const Json::Value& peer)
+  {
+    try
+    {
+      if (peer.isArray())
+      {
+        FromJsonArray(peer);
+      }
+      else if (peer.isObject())
+      {
+        FromJsonObject(peer);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+    catch (OrthancException&)
+    {
+      throw;
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void WebServiceParameters::ToJson(Json::Value& value) const
+  {
+    if (advancedFormat_)
+    {
+      value = Json::objectValue;
+      value["Url"] = url_;
+
+      if (!username_.empty() ||
+          !password_.empty())
+      {
+        value["Username"] = username_;
+        value["Password"] = password_;
+      }
+
+      if (!certificateFile_.empty())
+      {
+        value["CertificateFile"] = certificateFile_;
+      }
+
+      if (!certificateKeyFile_.empty())
+      {
+        value["CertificateKeyFile"] = certificateKeyFile_;
+      }
+
+      if (!certificateKeyPassword_.empty())
+      {
+        value["CertificateKeyPassword"] = certificateKeyPassword_;
+      }
+    }
+    else
+    {
+      value = Json::arrayValue;
+      value.append(url_);
+
+      if (!username_.empty() ||
+          !password_.empty())
+      {
+        value.append(username_);
+        value.append(password_);
+      }
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/WebServiceParameters.h b/Framework/Orthanc/Core/WebServiceParameters.h
new file mode 100644
index 0000000..40d6cf9
--- /dev/null
+++ b/Framework/Orthanc/Core/WebServiceParameters.h
@@ -0,0 +1,124 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class WebServiceParameters
+  {
+  private:
+    bool        advancedFormat_;
+    std::string url_;
+    std::string username_;
+    std::string password_;
+    std::string certificateFile_;
+    std::string certificateKeyFile_;
+    std::string certificateKeyPassword_;
+    bool        pkcs11Enabled_;
+
+    void FromJsonArray(const Json::Value& peer);
+
+    void FromJsonObject(const Json::Value& peer);
+
+  public:
+    WebServiceParameters();
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    const std::string& GetUsername() const
+    {
+      return username_;
+    }
+
+    void SetUsername(const std::string& username)
+    {
+      username_ = username;
+    }
+    
+    const std::string& GetPassword() const
+    {
+      return password_;
+    }
+
+    void SetPassword(const std::string& password)
+    {
+      password_ = password;
+    }
+
+    void ClearClientCertificate();
+
+    void SetClientCertificate(const std::string& certificateFile,
+                              const std::string& certificateKeyFile,
+                              const std::string& certificateKeyPassword);
+
+    const std::string& GetCertificateFile() const
+    {
+      return certificateFile_;
+    }
+
+    const std::string& GetCertificateKeyFile() const
+    {
+      return certificateKeyFile_;
+    }
+
+    const std::string& GetCertificateKeyPassword() const
+    {
+      return certificateKeyPassword_;
+    }
+
+    void SetPkcs11Enabled(bool pkcs11Enabled)
+    {
+      pkcs11Enabled_ = pkcs11Enabled;
+    }
+
+    bool IsPkcs11Enabled() const
+    {
+      return pkcs11Enabled_;
+    }
+
+    void FromJson(const Json::Value& peer);
+
+    void ToJson(Json::Value& value) const;
+  };
+}
diff --git a/Framework/Orthanc/OrthancServer/FromDcmtkBridge.cpp b/Framework/Orthanc/OrthancServer/FromDcmtkBridge.cpp
new file mode 100644
index 0000000..fde86e2
--- /dev/null
+++ b/Framework/Orthanc/OrthancServer/FromDcmtkBridge.cpp
@@ -0,0 +1,1767 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersServer.h"
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include "FromDcmtkBridge.h"
+#include "ToDcmtkBridge.h"
+#include "../Core/Logging.h"
+#include "../Core/Toolbox.h"
+#include "../Core/Uuid.h"
+#include "../Core/OrthancException.h"
+
+#include <list>
+#include <limits>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcdicent.h>
+#include <dcmtk/dcmdata/dcdict.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcuid.h>
+#include <dcmtk/dcmdata/dcistrmb.h>
+
+#include <dcmtk/dcmdata/dcvrae.h>
+#include <dcmtk/dcmdata/dcvras.h>
+#include <dcmtk/dcmdata/dcvrat.h>
+#include <dcmtk/dcmdata/dcvrcs.h>
+#include <dcmtk/dcmdata/dcvrda.h>
+#include <dcmtk/dcmdata/dcvrds.h>
+#include <dcmtk/dcmdata/dcvrdt.h>
+#include <dcmtk/dcmdata/dcvrfd.h>
+#include <dcmtk/dcmdata/dcvrfl.h>
+#include <dcmtk/dcmdata/dcvris.h>
+#include <dcmtk/dcmdata/dcvrlo.h>
+#include <dcmtk/dcmdata/dcvrlt.h>
+#include <dcmtk/dcmdata/dcvrpn.h>
+#include <dcmtk/dcmdata/dcvrsh.h>
+#include <dcmtk/dcmdata/dcvrsl.h>
+#include <dcmtk/dcmdata/dcvrss.h>
+#include <dcmtk/dcmdata/dcvrst.h>
+#include <dcmtk/dcmdata/dcvrtm.h>
+#include <dcmtk/dcmdata/dcvrui.h>
+#include <dcmtk/dcmdata/dcvrul.h>
+#include <dcmtk/dcmdata/dcvrus.h>
+#include <dcmtk/dcmdata/dcvrut.h>
+
+
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+#include <EmbeddedResources.h>
+#endif
+
+
+namespace Orthanc
+{
+  static inline uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+  static inline uint16_t GetTagValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+  static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
+                                     EmbeddedResources::FileResourceId resource)
+  {
+    std::string content;
+    EmbeddedResources::GetFileResource(content, resource);
+
+    Toolbox::TemporaryFile tmp;
+    tmp.Write(content);
+
+    if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
+    {
+      LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " 
+                 << "your TEMP directory does not contain special characters.";
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+                             
+#else
+  static void LoadExternalDictionary(DcmDataDictionary& dictionary,
+                                     const std::string& directory,
+                                     const std::string& filename)
+  {
+    boost::filesystem::path p = directory;
+    p = p / filename;
+
+    LOG(WARNING) << "Loading the external DICOM dictionary " << p;
+
+    if (!dictionary.loadDictionary(p.string().c_str()))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+                            
+#endif
+
+
+  namespace
+  {
+    class DictionaryLocker
+    {
+    private:
+      DcmDataDictionary& dictionary_;
+
+    public:
+      DictionaryLocker() : dictionary_(dcmDataDict.wrlock())
+      {
+      }
+
+      ~DictionaryLocker()
+      {
+        dcmDataDict.unlock();
+      }
+
+      DcmDataDictionary& operator*()
+      {
+        return dictionary_;
+      }
+
+      DcmDataDictionary* operator->()
+      {
+        return &dictionary_;
+      }
+    };
+  }
+
+
+  void FromDcmtkBridge::InitializeDictionary()
+  {
+    {
+      DictionaryLocker locker;
+
+      locker->clear();
+
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+      LOG(WARNING) << "Loading the embedded dictionaries";
+      /**
+       * Do not load DICONDE dictionary, it breaks the other tags. The
+       * command "strace storescu 2>&1 |grep dic" shows that DICONDE
+       * dictionary is not loaded by storescu.
+       **/
+      //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE);
+
+      LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM);
+      LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE);
+
+#elif defined(__linux__) || defined(__FreeBSD_kernel__)
+      std::string path = DCMTK_DICTIONARY_DIR;
+
+      const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
+      if (env != NULL)
+      {
+        path = std::string(env);
+      }
+
+      LoadExternalDictionary(*locker, path, "dicom.dic");
+      LoadExternalDictionary(*locker, path, "private.dic");
+
+#else
+#error Support your platform here
+#endif
+    }
+
+    /* make sure data dictionary is loaded */
+    if (!dcmDataDict.isDictionaryLoaded())
+    {
+      LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE;
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    {
+      // Test the dictionary with a simple DICOM tag
+      DcmTag key(0x0010, 0x1030); // This is PatientWeight
+      if (key.getEVR() != EVR_DS)
+      {
+        LOG(ERROR) << "The DICOM dictionary has not been correctly read";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag,
+                                              ValueRepresentation vr,
+                                              const std::string& name,
+                                              unsigned int minMultiplicity,
+                                              unsigned int maxMultiplicity)
+  {
+    if (minMultiplicity < 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    bool arbitrary = false;
+    if (maxMultiplicity == 0)
+    {
+      maxMultiplicity = DcmVariableVM;
+      arbitrary = true;
+    }
+    else if (maxMultiplicity < minMultiplicity)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    
+    DcmEVR evr = ToDcmtkBridge::Convert(vr);
+
+    LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " 
+              << name << " (multiplicity: " << minMultiplicity << "-" 
+              << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
+
+    std::auto_ptr<DcmDictEntry>  entry(new DcmDictEntry(tag.GetGroup(),
+                                                        tag.GetElement(),
+                                                        evr, name.c_str(),
+                                                        static_cast<int>(minMultiplicity),
+                                                        static_cast<int>(maxMultiplicity),
+                                                        NULL    /* version */,
+                                                        OFTrue  /* doCopyString */,
+                                                        NULL    /* private creator */));
+
+    entry->setGroupRangeRestriction(DcmDictRange_Unspecified);
+    entry->setElementRangeRestriction(DcmDictRange_Unspecified);
+
+    {
+      DictionaryLocker locker;
+      locker->addEntry(entry.release());
+    }
+  }
+
+
+  Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset,
+                                           Encoding defaultEncoding)
+  {
+    Encoding encoding = defaultEncoding;
+
+    OFString tmp;
+    if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good())
+    {
+      std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str()));
+
+      if (characterSet.empty())
+      {
+        // Empty specific character set tag: Use the default encoding
+      }
+      else if (GetDicomEncoding(encoding, characterSet.c_str()))
+      {
+        // The specific character set is supported by the Orthanc core
+      }
+      else
+      {
+        LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet
+                     << ", fallback to ASCII (remove all special characters)";
+        encoding = Encoding_Ascii;
+      }
+    }
+    else
+    {
+      // No specific character set tag: Use the default encoding
+    }
+
+    return encoding;
+  }
+
+
+  void FromDcmtkBridge::Convert(DicomMap& target, 
+                                DcmItem& dataset,
+                                unsigned int maxStringLength,
+                                Encoding defaultEncoding)
+  {
+    Encoding encoding = DetectEncoding(dataset, defaultEncoding);
+
+    target.Clear();
+    for (unsigned long i = 0; i < dataset.card(); i++)
+    {
+      DcmElement* element = dataset.getElement(i);
+      if (element && element->isLeaf())
+      {
+        target.SetValue(element->getTag().getGTag(),
+                        element->getTag().getETag(),
+                        ConvertLeafElement(*element, DicomToJsonFlags_Default, maxStringLength, encoding));
+      }
+    }
+  }
+
+
+  DicomTag FromDcmtkBridge::Convert(const DcmTag& tag)
+  {
+    return DicomTag(tag.getGTag(), tag.getETag());
+  }
+
+
+  DicomTag FromDcmtkBridge::GetTag(const DcmElement& element)
+  {
+    return DicomTag(element.getGTag(), element.getETag());
+  }
+
+
+  DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
+                                                  DicomToJsonFlags flags,
+                                                  unsigned int maxStringLength,
+                                                  Encoding encoding)
+  {
+    if (!element.isLeaf())
+    {
+      // This function is only applicable to leaf elements
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    char *c = NULL;
+    if (element.isaString() &&
+        element.getString(c).good())
+    {
+      if (c == NULL)  // This case corresponds to the empty string
+      {
+        return new DicomValue("", false);
+      }
+      else
+      {
+        std::string s(c);
+        std::string utf8 = Toolbox::ConvertToUtf8(s, encoding);
+
+        if (maxStringLength != 0 &&
+            utf8.size() > maxStringLength)
+        {
+          return new DicomValue;  // Create a NULL value
+        }
+        else
+        {
+          return new DicomValue(utf8, false);
+        }
+      }
+    }
+
+    try
+    {
+      // http://support.dcmtk.org/docs/dcvr_8h-source.html
+      switch (element.getVR())
+      {
+
+        /**
+         * Deal with binary data (including PixelData).
+         **/
+
+        case EVR_OB:  // other byte
+        case EVR_OF:  // other float
+        case EVR_OW:  // other word
+        case EVR_UN:  // unknown value representation
+        case EVR_ox:  // OB or OW depending on context
+        case EVR_DS:  // decimal string
+        case EVR_IS:  // integer string
+        case EVR_AS:  // age string
+        case EVR_DA:  // date string
+        case EVR_DT:  // date time string
+        case EVR_TM:  // time string
+        case EVR_AE:  // application entity title
+        case EVR_CS:  // code string
+        case EVR_SH:  // short string
+        case EVR_LO:  // long string
+        case EVR_ST:  // short text
+        case EVR_LT:  // long text
+        case EVR_UT:  // unlimited text
+        case EVR_PN:  // person name
+        case EVR_UI:  // unique identifier
+        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+        {
+          if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
+          {
+            Uint8* data = NULL;
+            if (element.getUint8Array(data) == EC_Normal)
+            {
+              return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
+            }
+          }
+
+          return new DicomValue;
+        }
+    
+        /**
+         * Numberic types
+         **/ 
+      
+        case EVR_SL:  // signed long
+        {
+          Sint32 f;
+          if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_SS:  // signed short
+        {
+          Sint16 f;
+          if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_UL:  // unsigned long
+        {
+          Uint32 f;
+          if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_US:  // unsigned short
+        {
+          Uint16 f;
+          if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_FL:  // float single-precision
+        {
+          Float32 f;
+          if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_FD:  // float double-precision
+        {
+          Float64 f;
+          if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+
+        /**
+         * Attribute tag.
+         **/
+
+        case EVR_AT:
+        {
+          DcmTagKey tag;
+          if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good())
+          {
+            DicomTag t(tag.getGroup(), tag.getElement());
+            return new DicomValue(t.Format(), false);
+          }
+          else
+          {
+            return new DicomValue;
+          }
+        }
+
+
+        /**
+         * Sequence types, should never occur at this point because of
+         * "element.isLeaf()".
+         **/
+
+        case EVR_SQ:  // sequence of items
+          return new DicomValue;
+
+
+          /**
+           * Internal to DCMTK.
+           **/ 
+
+        case EVR_xs:  // SS or US depending on context
+        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+        case EVR_na:  // na="not applicable", for data which has no VR
+        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+        case EVR_item:  // used internally for items
+        case EVR_metainfo:  // used internally for meta info datasets
+        case EVR_dataset:  // used internally for datasets
+        case EVR_fileFormat:  // used internally for DICOM files
+        case EVR_dicomDir:  // used internally for DICOMDIR objects
+        case EVR_dirRecord:  // used internally for DICOMDIR records
+        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+        case EVR_pixelItem:  // used internally for pixel items in a compressed image
+        case EVR_PixelData:  // used internally for uncompressed pixeld data
+        case EVR_OverlayData:  // used internally for overlay data
+          return new DicomValue;
+
+
+          /**
+           * Default case.
+           **/ 
+
+        default:
+          return new DicomValue;
+      }
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      return new DicomValue;
+    }
+    catch (std::bad_cast)
+    {
+      return new DicomValue;
+    }
+  }
+
+
+  static Json::Value& PrepareNode(Json::Value& parent,
+                                  DcmElement& element,
+                                  DicomToJsonFormat format)
+  {
+    assert(parent.type() == Json::objectValue);
+
+    DicomTag tag(FromDcmtkBridge::GetTag(element));
+    const std::string formattedTag = tag.Format();
+
+    if (format == DicomToJsonFormat_Short)
+    {
+      parent[formattedTag] = Json::nullValue;
+      return parent[formattedTag];
+    }
+
+    // This code gives access to the name of the private tags
+    const std::string tagName = FromDcmtkBridge::GetName(tag);
+    
+    switch (format)
+    {
+      case DicomToJsonFormat_Human:
+        parent[tagName] = Json::nullValue;
+        return parent[tagName];
+
+      case DicomToJsonFormat_Full:
+      {
+        parent[formattedTag] = Json::objectValue;
+        Json::Value& node = parent[formattedTag];
+
+        if (element.isLeaf())
+        {
+          node["Name"] = tagName;
+
+          if (element.getTag().getPrivateCreator() != NULL)
+          {
+            node["PrivateCreator"] = element.getTag().getPrivateCreator();
+          }
+
+          return node;
+        }
+        else
+        {
+          node["Name"] = tagName;
+          node["Type"] = "Sequence";
+          node["Value"] = Json::nullValue;
+          return node["Value"];
+        }
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static void LeafValueToJson(Json::Value& target,
+                              const DicomValue& value,
+                              DicomToJsonFormat format,
+                              DicomToJsonFlags flags,
+                              unsigned int maxStringLength)
+  {
+    Json::Value* targetValue = NULL;
+    Json::Value* targetType = NULL;
+
+    switch (format)
+    {
+      case DicomToJsonFormat_Short:
+      case DicomToJsonFormat_Human:
+      {
+        assert(target.type() == Json::nullValue);
+        targetValue = ⌖
+        break;
+      }      
+
+      case DicomToJsonFormat_Full:
+      {
+        assert(target.type() == Json::objectValue);
+        target["Value"] = Json::nullValue;
+        target["Type"] = Json::nullValue;
+        targetType = &target["Type"];
+        targetValue = &target["Value"];
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(targetValue != NULL);
+    assert(targetValue->type() == Json::nullValue);
+    assert(targetType == NULL || targetType->type() == Json::nullValue);
+
+    if (value.IsNull())
+    {
+      if (targetType != NULL)
+      {
+        *targetType = "Null";
+      }
+    }
+    else if (value.IsBinary())
+    {
+      if (flags & DicomToJsonFlags_ConvertBinaryToAscii)
+      {
+        *targetValue = Toolbox::ConvertToAscii(value.GetContent());
+      }
+      else
+      {
+        std::string s;
+        value.FormatDataUriScheme(s);
+        *targetValue = s;
+      }
+
+      if (targetType != NULL)
+      {
+        *targetType = "Binary";
+      }
+    }
+    else if (maxStringLength == 0 ||
+             value.GetContent().size() <= maxStringLength)
+    {
+      *targetValue = value.GetContent();
+
+      if (targetType != NULL)
+      {
+        *targetType = "String";
+      }
+    }
+    else
+    {
+      if (targetType != NULL)
+      {
+        *targetType = "TooLong";
+      }
+    }
+  }                              
+
+
+  static void DatasetToJson(Json::Value& parent,
+                            DcmItem& item,
+                            DicomToJsonFormat format,
+                            DicomToJsonFlags flags,
+                            unsigned int maxStringLength,
+                            Encoding encoding);
+
+
+  void FromDcmtkBridge::ToJson(Json::Value& parent,
+                               DcmElement& element,
+                               DicomToJsonFormat format,
+                               DicomToJsonFlags flags,
+                               unsigned int maxStringLength,
+                               Encoding encoding)
+  {
+    if (parent.type() == Json::nullValue)
+    {
+      parent = Json::objectValue;
+    }
+
+    assert(parent.type() == Json::objectValue);
+    Json::Value& target = PrepareNode(parent, element, format);
+
+    if (element.isLeaf())
+    {
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, maxStringLength, encoding));
+      LeafValueToJson(target, *v, format, flags, maxStringLength);
+    }
+    else
+    {
+      assert(target.type() == Json::nullValue);
+      target = Json::arrayValue;
+
+      // "All subclasses of DcmElement except for DcmSequenceOfItems
+      // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
+      // etc. are not." The following dynamic_cast is thus OK.
+      DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
+
+      for (unsigned long i = 0; i < sequence.card(); i++)
+      {
+        DcmItem* child = sequence.getItem(i);
+        Json::Value& v = target.append(Json::objectValue);
+        DatasetToJson(v, *child, format, flags, maxStringLength, encoding);
+      }
+    }
+  }
+
+
+  static void DatasetToJson(Json::Value& parent,
+                            DcmItem& item,
+                            DicomToJsonFormat format,
+                            DicomToJsonFlags flags,
+                            unsigned int maxStringLength,
+                            Encoding encoding)
+  {
+    assert(parent.type() == Json::objectValue);
+
+    for (unsigned long i = 0; i < item.card(); i++)
+    {
+      DcmElement* element = item.getElement(i);
+      if (element == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
+
+      /*element->getTag().isPrivate()*/
+      if (tag.IsPrivate() &&
+          !(flags & DicomToJsonFlags_IncludePrivateTags))    
+      {
+        continue;
+      }
+
+      if (!(flags & DicomToJsonFlags_IncludeUnknownTags))
+      {
+        DictionaryLocker locker;
+        if (locker->findEntry(element->getTag(), NULL) == NULL)
+        {
+          continue;
+        }
+      }
+
+      DcmEVR evr = element->getTag().getEVR();
+      if (evr == EVR_OB ||
+          evr == EVR_OF ||
+          evr == EVR_OW ||
+          evr == EVR_UN ||
+          evr == EVR_ox)
+      {
+        // This is a binary tag
+        if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
+            (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary)))
+        {
+          continue;
+        }
+      }
+
+      FromDcmtkBridge::ToJson(parent, *element, format, flags, maxStringLength, encoding);
+    }
+  }
+
+
+  void FromDcmtkBridge::ToJson(Json::Value& target, 
+                               DcmDataset& dataset,
+                               DicomToJsonFormat format,
+                               DicomToJsonFlags flags,
+                               unsigned int maxStringLength,
+                               Encoding defaultEncoding)
+  {
+    target = Json::objectValue;
+    DatasetToJson(target, dataset, format, flags, maxStringLength, DetectEncoding(dataset, defaultEncoding));
+  }
+
+
+  void FromDcmtkBridge::ToJson(Json::Value& target, 
+                               DcmMetaInfo& dataset,
+                               DicomToJsonFormat format,
+                               DicomToJsonFlags flags,
+                               unsigned int maxStringLength)
+  {
+    target = Json::objectValue;
+    DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii);
+  }
+
+
+  std::string FromDcmtkBridge::GetName(const DicomTag& t)
+  {
+    // Some patches for important tags because of different DICOM
+    // dictionaries between DCMTK versions
+    std::string n = t.GetMainTagsName();
+    if (n.size() != 0)
+    {
+      return n;
+    }
+    // End of patches
+
+#if 0
+    DcmTagKey tag(t.GetGroup(), t.GetElement());
+    const DcmDataDictionary& dict = dcmDataDict.rdlock();
+    const DcmDictEntry* entry = dict.findEntry(tag, NULL);
+
+    std::string s(DcmTag_ERROR_TagName);
+    if (entry != NULL)
+    {
+      s = std::string(entry->getTagName());
+    }
+
+    dcmDataDict.unlock();
+    return s;
+#else
+    DcmTag tag(t.GetGroup(), t.GetElement());
+    const char* name = tag.getTagName();
+    if (name == NULL)
+    {
+      return DcmTag_ERROR_TagName;
+    }
+    else
+    {
+      return std::string(name);
+    }
+#endif
+  }
+
+
+  DicomTag FromDcmtkBridge::ParseTag(const char* name)
+  {
+    if (strlen(name) == 9 &&
+        isxdigit(name[0]) &&
+        isxdigit(name[1]) &&
+        isxdigit(name[2]) &&
+        isxdigit(name[3]) &&
+        (name[4] == '-' || name[4] == ',') &&
+        isxdigit(name[5]) &&
+        isxdigit(name[6]) &&
+        isxdigit(name[7]) &&
+        isxdigit(name[8]))        
+    {
+      uint16_t group = GetTagValue(name);
+      uint16_t element = GetTagValue(name + 5);
+      return DicomTag(group, element);
+    }
+
+    if (strlen(name) == 8 &&
+        isxdigit(name[0]) &&
+        isxdigit(name[1]) &&
+        isxdigit(name[2]) &&
+        isxdigit(name[3]) &&
+        isxdigit(name[4]) &&
+        isxdigit(name[5]) &&
+        isxdigit(name[6]) &&
+        isxdigit(name[7]))        
+    {
+      uint16_t group = GetTagValue(name);
+      uint16_t element = GetTagValue(name + 4);
+      return DicomTag(group, element);
+    }
+
+#if 0
+    const DcmDataDictionary& dict = dcmDataDict.rdlock();
+    const DcmDictEntry* entry = dict.findEntry(name);
+
+    if (entry == NULL)
+    {
+      dcmDataDict.unlock();
+      throw OrthancException(ErrorCode_UnknownDicomTag);
+    }
+    else
+    {
+      DcmTagKey key = entry->getKey();
+      DicomTag tag(key.getGroup(), key.getElement());
+      dcmDataDict.unlock();
+      return tag;
+    }
+#else
+    DcmTag tag;
+    if (DcmTag::findTagFromName(name, tag).good())
+    {
+      return DicomTag(tag.getGTag(), tag.getETag());
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownDicomTag);
+    }
+#endif
+  }
+
+
+  bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag)
+  {
+    DcmTag tmp(tag.GetGroup(), tag.GetElement());
+    return tmp.isUnknownVR();
+  }
+
+
+  void FromDcmtkBridge::ToJson(Json::Value& result,
+                               const DicomMap& values,
+                               bool simplify)
+  {
+    if (result.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    result.clear();
+
+    for (DicomMap::Map::const_iterator 
+           it = values.map_.begin(); it != values.map_.end(); ++it)
+    {
+      if (simplify)
+      {
+        if (it->second->IsNull())
+        {
+          result[GetName(it->first)] = Json::nullValue;
+        }
+        else
+        {
+          // TODO IsBinary
+          result[GetName(it->first)] = it->second->GetContent();
+        }
+      }
+      else
+      {
+        Json::Value value = Json::objectValue;
+
+        value["Name"] = GetName(it->first);
+
+        if (it->second->IsNull())
+        {
+          value["Type"] = "Null";
+          value["Value"] = Json::nullValue;
+        }
+        else
+        {
+          // TODO IsBinary
+          value["Type"] = "String";
+          value["Value"] = it->second->GetContent();
+        }
+
+        result[it->first.Format()] = value;
+      }
+    }
+  }
+
+
+  std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level)
+  {
+    char uid[100];
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        // The "PatientID" field is of type LO (Long String), 64
+        // Bytes Maximum. An UUID is of length 36, thus it can be used
+        // as a random PatientID.
+        return Toolbox::GenerateUuid();
+
+      case ResourceType_Instance:
+        return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
+
+      case ResourceType_Series:
+        return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);
+
+      case ResourceType_Study:
+        return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
+                                           DcmDataset& dataSet)
+  {
+    // Determine the transfer syntax which shall be used to write the
+    // information to the file. We always switch to the Little Endian
+    // syntax, with explicit length.
+
+    // http://support.dcmtk.org/docs/dcxfer_8h-source.html
+
+
+    /**
+     * Note that up to Orthanc 0.7.1 (inclusive), the
+     * "EXS_LittleEndianExplicit" was always used to save the DICOM
+     * dataset into memory. We now keep the original transfer syntax
+     * (if available).
+     **/
+    E_TransferSyntax xfer = dataSet.getOriginalXfer();
+    if (xfer == EXS_Unknown)
+    {
+      // No information about the original transfer syntax: This is
+      // most probably a DICOM dataset that was read from memory.
+      xfer = EXS_LittleEndianExplicit;
+    }
+
+    E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
+
+    // Create the meta-header information
+    DcmFileFormat ff(&dataSet);
+    ff.validateMetaInfo(xfer);
+    ff.removeInvalidGroups();
+
+    // Create a memory buffer with the proper size
+    {
+      const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType);  // (*)
+      buffer.resize(estimatedSize);
+    }
+
+    DcmOutputBufferStream ob(&buffer[0], buffer.size());
+
+    // Fill the memory buffer with the meta-header and the dataset
+    ff.transferInit();
+    OFCondition c = ff.write(ob, xfer, encodingType, NULL,
+                             /*opt_groupLength*/ EGL_recalcGL,
+                             /*opt_paddingType*/ EPD_withoutPadding);
+    ff.transferEnd();
+
+    if (c.good())
+    {
+      // The DICOM file is successfully written, truncate the target
+      // buffer if its size was overestimated by (*)
+      ob.flush();
+
+      size_t effectiveSize = static_cast<size_t>(ob.tell());
+      if (effectiveSize < buffer.size())
+      {
+        buffer.resize(effectiveSize);
+      }
+
+      return true;
+    }
+    else
+    {
+      // Error
+      buffer.clear();
+      return false;
+    }
+  }
+
+
+  ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag)
+  {
+    DcmTag t(tag.GetGroup(), tag.GetElement());
+    return Convert(t.getEVR());
+  }
+
+  ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr)
+  {
+    switch (vr)
+    {
+      case EVR_AE:
+        return ValueRepresentation_ApplicationEntity;
+
+      case EVR_AS:
+        return ValueRepresentation_AgeString;
+
+      case EVR_AT:
+        return ValueRepresentation_AttributeTag;
+
+      case EVR_CS:
+        return ValueRepresentation_CodeString;
+
+      case EVR_DA:
+        return ValueRepresentation_Date;
+
+      case EVR_DS:
+        return ValueRepresentation_DecimalString;
+
+      case EVR_DT:
+        return ValueRepresentation_DateTime;
+
+      case EVR_FL:
+        return ValueRepresentation_FloatingPointSingle;
+
+      case EVR_FD:
+        return ValueRepresentation_FloatingPointDouble;
+
+      case EVR_IS:
+        return ValueRepresentation_IntegerString;
+
+      case EVR_LO:
+        return ValueRepresentation_LongString;
+
+      case EVR_LT:
+        return ValueRepresentation_LongText;
+
+      case EVR_OB:
+        return ValueRepresentation_OtherByte;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_OD:
+          return ValueRepresentation_OtherDouble;*/
+
+      case EVR_OF:
+        return ValueRepresentation_OtherFloat;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_OL:
+          return ValueRepresentation_OtherLong;*/
+
+      case EVR_OW:
+        return ValueRepresentation_OtherWord;
+
+      case EVR_PN:
+        return ValueRepresentation_PersonName;
+
+      case EVR_SH:
+        return ValueRepresentation_ShortString;
+
+      case EVR_SL:
+        return ValueRepresentation_SignedLong;
+
+      case EVR_SQ:
+        return ValueRepresentation_Sequence;
+
+      case EVR_SS:
+        return ValueRepresentation_SignedShort;
+
+      case EVR_ST:
+        return ValueRepresentation_ShortText;
+
+      case EVR_TM:
+        return ValueRepresentation_Time;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_UC:
+          return ValueRepresentation_UnlimitedCharacters;*/
+
+      case EVR_UI:
+        return ValueRepresentation_UniqueIdentifier;
+
+      case EVR_UL:
+        return ValueRepresentation_UnsignedLong;
+
+      case EVR_UN:
+        return ValueRepresentation_Unknown;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_UR:
+          return ValueRepresentation_UniversalResource;*/
+
+      case EVR_US:
+        return ValueRepresentation_UnsignedShort;
+
+      case EVR_UT:
+        return ValueRepresentation_UnlimitedText;
+
+      default:
+        return ValueRepresentation_NotSupported;
+    }
+  }
+
+
+  static bool IsBinaryTag(const DcmTag& key)
+  {
+    return (key.isUnknownVR() || 
+            key.getEVR() == EVR_OB ||
+            key.getEVR() == EVR_OF ||
+            key.getEVR() == EVR_OW ||
+            key.getEVR() == EVR_UN ||
+            key.getEVR() == EVR_ox);
+  }
+
+
+  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
+  {
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (tag.IsPrivate() ||
+        IsBinaryTag(key))
+    {
+      return new DcmOtherByteOtherWord(key);
+    }
+
+    switch (key.getEVR())
+    {
+      // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+      /**
+       * Binary types, handled above
+       **/
+    
+      case EVR_OB:  // other byte
+      case EVR_OF:  // other float
+      case EVR_OW:  // other word
+      case EVR_UN:  // unknown value representation
+      case EVR_ox:  // OB or OW depending on context
+        throw OrthancException(ErrorCode_InternalError);
+
+
+      /**
+       * String types.
+       * http://support.dcmtk.org/docs/classDcmByteString.html
+       **/
+      
+      case EVR_AS:  // age string
+        return new DcmAgeString(key);
+
+      case EVR_AE:  // application entity title
+        return new DcmApplicationEntity(key);
+
+      case EVR_CS:  // code string
+        return new DcmCodeString(key);        
+
+      case EVR_DA:  // date string
+        return new DcmDate(key);
+        
+      case EVR_DT:  // date time string
+        return new DcmDateTime(key);
+
+      case EVR_DS:  // decimal string
+        return new DcmDecimalString(key);
+
+      case EVR_IS:  // integer string
+        return new DcmIntegerString(key);
+
+      case EVR_TM:  // time string
+        return new DcmTime(key);
+
+      case EVR_UI:  // unique identifier
+        return new DcmUniqueIdentifier(key);
+
+      case EVR_ST:  // short text
+        return new DcmShortText(key);
+
+      case EVR_LO:  // long string
+        return new DcmLongString(key);
+
+      case EVR_LT:  // long text
+        return new DcmLongText(key);
+
+      case EVR_UT:  // unlimited text
+        return new DcmUnlimitedText(key);
+
+      case EVR_SH:  // short string
+        return new DcmShortString(key);
+
+      case EVR_PN:  // person name
+        return new DcmPersonName(key);
+
+        
+      /**
+       * Numerical types
+       **/ 
+      
+      case EVR_SL:  // signed long
+        return new DcmSignedLong(key);
+
+      case EVR_SS:  // signed short
+        return new DcmSignedShort(key);
+
+      case EVR_UL:  // unsigned long
+        return new DcmUnsignedLong(key);
+
+      case EVR_US:  // unsigned short
+        return new DcmUnsignedShort(key);
+
+      case EVR_FL:  // float single-precision
+        return new DcmFloatingPointSingle(key);
+
+      case EVR_FD:  // float double-precision
+        return new DcmFloatingPointDouble(key);
+
+
+      /**
+       * Sequence types, should never occur at this point.
+       **/
+
+      case EVR_SQ:  // sequence of items
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+      /**
+       * TODO
+       **/
+
+      case EVR_AT:  // attribute tag
+        throw OrthancException(ErrorCode_NotImplemented);
+
+
+      /**
+       * Internal to DCMTK.
+       **/ 
+
+      case EVR_xs:  // SS or US depending on context
+      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+      case EVR_na:  // na="not applicable", for data which has no VR
+      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+      case EVR_item:  // used internally for items
+      case EVR_metainfo:  // used internally for meta info datasets
+      case EVR_dataset:  // used internally for datasets
+      case EVR_fileFormat:  // used internally for DICOM files
+      case EVR_dicomDir:  // used internally for DICOMDIR objects
+      case EVR_dirRecord:  // used internally for DICOMDIR records
+      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+      case EVR_pixelItem:  // used internally for pixel items in a compressed image
+      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+      case EVR_PixelData:  // used internally for uncompressed pixeld data
+      case EVR_OverlayData:  // used internally for overlay data
+      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+      default:
+        break;
+    }
+
+    throw OrthancException(ErrorCode_InternalError);          
+  }
+
+
+
+  void FromDcmtkBridge::FillElementWithString(DcmElement& element,
+                                              const DicomTag& tag,
+                                              const std::string& utf8Value,
+                                              bool decodeDataUriScheme,
+                                              Encoding dicomEncoding)
+  {
+    std::string binary;
+    const std::string* decoded = &utf8Value;
+
+    if (decodeDataUriScheme &&
+        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      decoded = &binary;
+    }
+    else if (dicomEncoding != Encoding_Utf8)
+    {
+      binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding);
+      decoded = &binary;
+    }
+
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (tag.IsPrivate() ||
+        IsBinaryTag(key))
+    {
+      if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good())
+      {
+        return;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    bool ok = false;
+    
+    try
+    {
+      switch (key.getEVR())
+      {
+        // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+        /**
+         * TODO.
+         **/
+
+        case EVR_OB:  // other byte
+        case EVR_OF:  // other float
+        case EVR_OW:  // other word
+        case EVR_AT:  // attribute tag
+          throw OrthancException(ErrorCode_NotImplemented);
+    
+        case EVR_UN:  // unknown value representation
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+        /**
+         * String types.
+         **/
+      
+        case EVR_DS:  // decimal string
+        case EVR_IS:  // integer string
+        case EVR_AS:  // age string
+        case EVR_DA:  // date string
+        case EVR_DT:  // date time string
+        case EVR_TM:  // time string
+        case EVR_AE:  // application entity title
+        case EVR_CS:  // code string
+        case EVR_SH:  // short string
+        case EVR_LO:  // long string
+        case EVR_ST:  // short text
+        case EVR_LT:  // long text
+        case EVR_UT:  // unlimited text
+        case EVR_PN:  // person name
+        case EVR_UI:  // unique identifier
+        {
+          ok = element.putString(decoded->c_str()).good();
+          break;
+        }
+
+        
+        /**
+         * Numerical types
+         **/ 
+      
+        case EVR_SL:  // signed long
+        {
+          ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_SS:  // signed short
+        {
+          ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_UL:  // unsigned long
+        {
+          ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_US:  // unsigned short
+        {
+          ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FL:  // float single-precision
+        {
+          ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FD:  // float double-precision
+        {
+          ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
+          break;
+        }
+
+
+        /**
+         * Sequence types, should never occur at this point.
+         **/
+
+        case EVR_SQ:  // sequence of items
+        {
+          ok = false;
+          break;
+        }
+
+
+        /**
+         * Internal to DCMTK.
+         **/ 
+
+        case EVR_ox:  // OB or OW depending on context
+        case EVR_xs:  // SS or US depending on context
+        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+        case EVR_na:  // na="not applicable", for data which has no VR
+        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+        case EVR_item:  // used internally for items
+        case EVR_metainfo:  // used internally for meta info datasets
+        case EVR_dataset:  // used internally for datasets
+        case EVR_fileFormat:  // used internally for DICOM files
+        case EVR_dicomDir:  // used internally for DICOMDIR objects
+        case EVR_dirRecord:  // used internally for DICOMDIR records
+        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+        case EVR_pixelItem:  // used internally for pixel items in a compressed image
+        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+        case EVR_PixelData:  // used internally for uncompressed pixeld data
+        case EVR_OverlayData:  // used internally for overlay data
+        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+        default:
+          break;
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      ok = false;
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
+                                        const Json::Value& value,
+                                        bool decodeDataUriScheme,
+                                        Encoding dicomEncoding)
+  {
+    std::auto_ptr<DcmElement> element;
+
+    switch (value.type())
+    {
+      case Json::stringValue:
+        element.reset(CreateElementForTag(tag));
+        FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding);
+        break;
+
+      case Json::arrayValue:
+      {
+        DcmTag key(tag.GetGroup(), tag.GetElement());
+        if (key.getEVR() != EVR_SQ)
+        {
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key, value.size());
+        element.reset(sequence);
+        
+        for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
+        {
+          std::auto_ptr<DcmItem> item(new DcmItem);
+
+          Json::Value::Members members = value[i].getMemberNames();
+          for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
+          {
+            item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding));
+          }
+
+          sequence->append(item.release());
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    return element.release();
+  }
+
+
+  DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset)
+  {
+    DcmElement *element = NULL;
+    if (!dataset.findAndGetElement(DCM_PixelData, element).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
+    DcmPixelSequence* pixelSequence = NULL;
+    if (!pixelData.getEncapsulatedRepresentation
+        (dataset.getOriginalXfer(), NULL, pixelSequence).good())
+    {
+      return NULL;
+    }
+    else
+    {
+      return pixelSequence;
+    }
+  }
+
+
+  Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json,
+                                            Encoding defaultEncoding)
+  {
+    if (json.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    Encoding encoding = defaultEncoding;
+
+    const Json::Value::Members tags = json.getMemberNames();
+    
+    // Look for SpecificCharacterSet (0008,0005) in the JSON file
+    for (size_t i = 0; i < tags.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
+      if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        const Json::Value& value = json[tags[i]];
+        if (value.type() != Json::stringValue ||
+            !GetDicomEncoding(encoding, value.asCString()))
+        {
+          LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value;
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+      }
+    }
+
+    return encoding;
+  } 
+
+
+  static void SetString(DcmDataset& target,
+                        const DcmTag& tag,
+                        const std::string& value)
+  {
+    if (!target.putAndInsertString(tag, value.c_str()).good())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json,  // Encoded using UTF-8
+                                        bool generateIdentifiers,
+                                        bool decodeDataUriScheme,
+                                        Encoding defaultEncoding)
+  {
+    std::auto_ptr<DcmDataset> result(new DcmDataset);
+    Encoding encoding = ExtractEncoding(json, defaultEncoding);
+
+    SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding));
+
+    const Json::Value::Members tags = json.getMemberNames();
+    
+    bool hasPatientId = false;
+    bool hasStudyInstanceUid = false;
+    bool hasSeriesInstanceUid = false;
+    bool hasSopInstanceUid = false;
+
+    for (size_t i = 0; i < tags.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
+      const Json::Value& value = json[tags[i]];
+
+      if (tag == DICOM_TAG_PATIENT_ID)
+      {
+        hasPatientId = true;
+      }
+      else if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
+      {
+        hasStudyInstanceUid = true;
+      }
+      else if (tag == DICOM_TAG_SERIES_INSTANCE_UID)
+      {
+        hasSeriesInstanceUid = true;
+      }
+      else if (tag == DICOM_TAG_SOP_INSTANCE_UID)
+      {
+        hasSopInstanceUid = true;
+      }
+
+      if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding));
+        const DcmTagKey& tag = element->getTag();
+
+        result->findAndDeleteElement(tag);
+
+        DcmElement* tmp = element.release();
+        if (!result->insert(tmp, false, false).good())
+        {
+          delete tmp;
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    }
+
+    if (!hasPatientId &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient));
+    }
+
+    if (!hasStudyInstanceUid &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study));
+    }
+
+    if (!hasSeriesInstanceUid &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series));
+    }
+
+    if (!hasSopInstanceUid &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance));
+    }
+
+    return result.release();
+  }
+
+
+  DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer,
+                                                       size_t size)
+  {
+    DcmInputBufferStream is;
+    if (size > 0)
+    {
+      is.setBuffer(buffer, size);
+    }
+    is.setEos();
+
+    std::auto_ptr<DcmFileFormat> result(new DcmFileFormat);
+
+    result->transferInit();
+    if (!result->read(is).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    result->loadAllDataIntoMemory();
+    result->transferEnd();
+
+    return result.release();
+  }
+
+
+  void FromDcmtkBridge::FromJson(DicomMap& target,
+                                 const Json::Value& source)
+  {
+    if (source.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    target.Clear();
+
+    Json::Value::Members members = source.getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& value = source[members[i]];
+
+      if (value.type() != Json::stringValue)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+      
+      target.SetValue(ParseTag(members[i]), value.asString(), false);
+    }
+  }
+}
diff --git a/Framework/Orthanc/OrthancServer/FromDcmtkBridge.h b/Framework/Orthanc/OrthancServer/FromDcmtkBridge.h
new file mode 100644
index 0000000..eff3b81
--- /dev/null
+++ b/Framework/Orthanc/OrthancServer/FromDcmtkBridge.h
@@ -0,0 +1,167 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ServerEnumerations.h"
+
+#include "../Core/DicomFormat/DicomMap.h"
+
+#include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class FromDcmtkBridge
+  {
+  public:
+    static void InitializeDictionary();
+
+    static void RegisterDictionaryTag(const DicomTag& tag,
+                                      ValueRepresentation vr,
+                                      const std::string& name,
+                                      unsigned int minMultiplicity,
+                                      unsigned int maxMultiplicity);
+
+    static Encoding DetectEncoding(DcmItem& dataset,
+                                   Encoding defaultEncoding);
+
+    static void Convert(DicomMap& target, 
+                        DcmItem& dataset,
+                        unsigned int maxStringLength,
+                        Encoding defaultEncoding);
+
+    static DicomTag Convert(const DcmTag& tag);
+
+    static DicomTag GetTag(const DcmElement& element);
+
+    static bool IsUnknownTag(const DicomTag& tag);
+
+    static DicomValue* ConvertLeafElement(DcmElement& element,
+                                          DicomToJsonFlags flags,
+                                          unsigned int maxStringLength,
+                                          Encoding encoding);
+
+    static void ToJson(Json::Value& parent,
+                       DcmElement& element,
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength,
+                       Encoding dicomEncoding);
+
+    static void ToJson(Json::Value& target, 
+                       DcmDataset& dataset,
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength,
+                       Encoding defaultEncoding);
+
+    static void ToJson(Json::Value& target, 
+                       DcmMetaInfo& header,
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength);
+
+    static std::string GetName(const DicomTag& tag);
+
+    static DicomTag ParseTag(const char* name);
+
+    static DicomTag ParseTag(const std::string& name)
+    {
+      return ParseTag(name.c_str());
+    }
+
+    static bool HasTag(const DicomMap& fields,
+                       const std::string& tagName)
+    {
+      return fields.HasTag(ParseTag(tagName));
+    }
+
+    static const DicomValue& GetValue(const DicomMap& fields,
+                                      const std::string& tagName)
+    {
+      return fields.GetValue(ParseTag(tagName));
+    }
+
+    static void SetValue(DicomMap& target,
+                         const std::string& tagName,
+                         DicomValue* value)
+    {
+      target.SetValue(ParseTag(tagName), value);
+    }
+
+    static void ToJson(Json::Value& result,
+                       const DicomMap& values,
+                       bool simplify);
+
+    static std::string GenerateUniqueIdentifier(ResourceType level);
+
+    static bool SaveToMemoryBuffer(std::string& buffer,
+                                   DcmDataset& dataSet);
+
+    static ValueRepresentation Convert(DcmEVR vr);
+
+    static ValueRepresentation LookupValueRepresentation(const DicomTag& tag);
+
+    static DcmElement* CreateElementForTag(const DicomTag& tag);
+    
+    static void FillElementWithString(DcmElement& element,
+                                      const DicomTag& tag,
+                                      const std::string& utf8alue,  // Encoded using UTF-8
+                                      bool decodeDataUriScheme,
+                                      Encoding dicomEncoding);
+
+    static DcmElement* FromJson(const DicomTag& tag,
+                                const Json::Value& element,  // Encoded using UTF-8
+                                bool decodeDataUriScheme,
+                                Encoding dicomEncoding);
+
+    static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset);
+
+    static Encoding ExtractEncoding(const Json::Value& json,
+                                    Encoding defaultEncoding);
+
+    static DcmDataset* FromJson(const Json::Value& json,  // Encoded using UTF-8
+                                bool generateIdentifiers,
+                                bool decodeDataUriScheme,
+                                Encoding defaultEncoding);
+
+    static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer,
+                                               size_t size);
+
+    static void FromJson(DicomMap& values,
+                         const Json::Value& result);
+  };
+}
diff --git a/Framework/Orthanc/OrthancServer/PrecompiledHeadersServer.h b/Framework/Orthanc/OrthancServer/PrecompiledHeadersServer.h
new file mode 100644
index 0000000..49e8719
--- /dev/null
+++ b/Framework/Orthanc/OrthancServer/PrecompiledHeadersServer.h
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/PrecompiledHeaders.h"
+
+#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
+
+// DCMTK
+#include <dcmtk/dcmdata/dcchrstr.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcdicent.h>
+#include <dcmtk/dcmdata/dcdict.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcistrmb.h>
+#include <dcmtk/dcmdata/dcistrmf.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+#include <dcmtk/dcmdata/dcuid.h>
+#include <dcmtk/dcmdata/dcvrae.h>
+#include <dcmtk/dcmdata/dcvras.h>
+#include <dcmtk/dcmdata/dcvrcs.h>
+#include <dcmtk/dcmdata/dcvrda.h>
+#include <dcmtk/dcmdata/dcvrds.h>
+#include <dcmtk/dcmdata/dcvrdt.h>
+#include <dcmtk/dcmdata/dcvrfd.h>
+#include <dcmtk/dcmdata/dcvrfl.h>
+#include <dcmtk/dcmdata/dcvris.h>
+#include <dcmtk/dcmdata/dcvrlo.h>
+#include <dcmtk/dcmdata/dcvrlt.h>
+#include <dcmtk/dcmdata/dcvrpn.h>
+#include <dcmtk/dcmdata/dcvrsh.h>
+#include <dcmtk/dcmdata/dcvrsl.h>
+#include <dcmtk/dcmdata/dcvrss.h>
+#include <dcmtk/dcmdata/dcvrst.h>
+#include <dcmtk/dcmdata/dcvrtm.h>
+#include <dcmtk/dcmdata/dcvrui.h>
+#include <dcmtk/dcmdata/dcvrul.h>
+#include <dcmtk/dcmdata/dcvrus.h>
+#include <dcmtk/dcmdata/dcvrut.h>
+#include <dcmtk/dcmnet/dcasccfg.h>
+#include <dcmtk/dcmnet/diutil.h>
+
+#endif
diff --git a/Framework/Orthanc/OrthancServer/ServerEnumerations.cpp b/Framework/Orthanc/OrthancServer/ServerEnumerations.cpp
new file mode 100644
index 0000000..8e3fdf3
--- /dev/null
+++ b/Framework/Orthanc/OrthancServer/ServerEnumerations.cpp
@@ -0,0 +1,467 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersServer.h"
+#include "ServerEnumerations.h"
+
+#include "../Core/OrthancException.h"
+#include "../Core/EnumerationDictionary.h"
+#include "../Core/Logging.h"
+#include "../Core/Toolbox.h"
+
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  typedef std::map<FileContentType, std::string>  MimeTypes;
+
+  static boost::mutex enumerationsMutex_;
+  static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_;
+  static Toolbox::EnumerationDictionary<FileContentType> dictContentType_;
+  static MimeTypes  mimeTypes_;
+
+  void InitializeServerEnumerations()
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    dictMetadataType_.Clear();
+    dictContentType_.Clear();
+    
+    dictMetadataType_.Add(MetadataType_Instance_IndexInSeries, "IndexInSeries");
+    dictMetadataType_.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
+    dictMetadataType_.Add(MetadataType_Instance_RemoteAet, "RemoteAET");
+    dictMetadataType_.Add(MetadataType_Series_ExpectedNumberOfInstances, "ExpectedNumberOfInstances");
+    dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom");
+    dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom");
+    dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate");
+    dictMetadataType_.Add(MetadataType_Instance_Origin, "Origin");
+
+    dictContentType_.Add(FileContentType_Dicom, "dicom");
+    dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json");
+  }
+
+  void RegisterUserMetadata(int metadata,
+                            const std::string& name)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    MetadataType type = static_cast<MetadataType>(metadata);
+
+    if (metadata < 0 || 
+        !IsUserMetadata(type))
+    {
+      LOG(ERROR) << "A user content type must have index between "
+                 << static_cast<int>(MetadataType_StartUser) << " and "
+                 << static_cast<int>(MetadataType_EndUser) << ", but \""
+                 << name << "\" has index " << metadata;
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (dictMetadataType_.Contains(type))
+    {
+      LOG(ERROR) << "Cannot associate user content type \""
+                 << name << "\" with index " << metadata 
+                 << ", as this index is already used";
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictMetadataType_.Add(type, name);
+  }
+
+  std::string EnumerationToString(MetadataType type)
+  {
+    // This function MUST return a "std::string" and not "const
+    // char*", as the result is not a static string
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictMetadataType_.Translate(type);
+  }
+
+  MetadataType StringToMetadata(const std::string& str)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictMetadataType_.Translate(str);
+  }
+
+  void RegisterUserContentType(int contentType,
+                               const std::string& name,
+                               const std::string& mime)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    FileContentType type = static_cast<FileContentType>(contentType);
+
+    if (contentType < 0 || 
+        !IsUserContentType(type))
+    {
+      LOG(ERROR) << "A user content type must have index between "
+                 << static_cast<int>(FileContentType_StartUser) << " and "
+                 << static_cast<int>(FileContentType_EndUser) << ", but \""
+                 << name << "\" has index " << contentType;
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (dictContentType_.Contains(type))
+    {
+      LOG(ERROR) << "Cannot associate user content type \""
+                 << name << "\" with index " << contentType 
+                 << ", as this index is already used";
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictContentType_.Add(type, name);
+    mimeTypes_[type] = mime;
+  }
+
+  std::string EnumerationToString(FileContentType type)
+  {
+    // This function MUST return a "std::string" and not "const
+    // char*", as the result is not a static string
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictContentType_.Translate(type);
+  }
+
+  std::string GetFileContentMime(FileContentType type)
+  {
+    if (type >= FileContentType_StartUser &&
+        type <= FileContentType_EndUser)
+    {
+      boost::mutex::scoped_lock lock(enumerationsMutex_);
+      
+      MimeTypes::const_iterator it = mimeTypes_.find(type);
+      if (it != mimeTypes_.end())
+      {
+        return it->second;
+      }
+    }
+
+    switch (type)
+    {
+      case FileContentType_Dicom:
+        return "application/dicom";
+
+      case FileContentType_DicomAsJson:
+        return "application/json";
+
+      default:
+        return "application/octet-stream";
+    }
+  }
+
+  FileContentType StringToContentType(const std::string& str)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictContentType_.Translate(str);
+  }
+
+  std::string GetBasePath(ResourceType type,
+                          const std::string& publicId)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "/patients/" + publicId;
+
+      case ResourceType_Study:
+        return "/studies/" + publicId;
+
+      case ResourceType_Series:
+        return "/series/" + publicId;
+
+      case ResourceType_Instance:
+        return "/instances/" + publicId;
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  const char* EnumerationToString(SeriesStatus status)
+  {
+    switch (status)
+    {
+      case SeriesStatus_Complete:
+        return "Complete";
+
+      case SeriesStatus_Missing:
+        return "Missing";
+
+      case SeriesStatus_Inconsistent:
+        return "Inconsistent";
+
+      case SeriesStatus_Unknown:
+        return "Unknown";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  const char* EnumerationToString(StoreStatus status)
+  {
+    switch (status)
+    {
+      case StoreStatus_Success:
+        return "Success";
+
+      case StoreStatus_AlreadyStored:
+        return "AlreadyStored";
+
+      case StoreStatus_Failure:
+        return "Failure";
+
+      case StoreStatus_FilteredOut:
+        return "FilteredOut";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ChangeType type)
+  {
+    switch (type)
+    {
+      case ChangeType_CompletedSeries:
+        return "CompletedSeries";
+
+      case ChangeType_NewInstance:
+        return "NewInstance";
+
+      case ChangeType_NewPatient:
+        return "NewPatient";
+
+      case ChangeType_NewSeries:
+        return "NewSeries";
+
+      case ChangeType_NewStudy:
+        return "NewStudy";
+
+      case ChangeType_AnonymizedStudy:
+        return "AnonymizedStudy";
+
+      case ChangeType_AnonymizedSeries:
+        return "AnonymizedSeries";
+
+      case ChangeType_ModifiedStudy:
+        return "ModifiedStudy";
+
+      case ChangeType_ModifiedSeries:
+        return "ModifiedSeries";
+
+      case ChangeType_AnonymizedPatient:
+        return "AnonymizedPatient";
+
+      case ChangeType_ModifiedPatient:
+        return "ModifiedPatient";
+
+      case ChangeType_StablePatient:
+        return "StablePatient";
+
+      case ChangeType_StableStudy:
+        return "StableStudy";
+
+      case ChangeType_StableSeries:
+        return "StableSeries";
+
+      case ChangeType_Deleted:
+        return "Deleted";
+
+      case ChangeType_NewChildInstance:
+        return "NewChildInstance";
+
+      case ChangeType_UpdatedAttachment:
+        return "UpdatedAttachment";
+
+      case ChangeType_UpdatedMetadata:
+        return "UpdatedMetadata";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ModalityManufacturer manufacturer)
+  {
+    switch (manufacturer)
+    {
+      case ModalityManufacturer_Generic:
+        return "Generic";
+
+      case ModalityManufacturer_StoreScp:
+        return "StoreScp";
+      
+      case ModalityManufacturer_ClearCanvas:
+        return "ClearCanvas";
+      
+      case ModalityManufacturer_MedInria:
+        return "MedInria";
+
+      case ModalityManufacturer_Dcm4Chee:
+        return "Dcm4Chee";
+      
+      case ModalityManufacturer_SyngoVia:
+        return "SyngoVia";
+      
+      case ModalityManufacturer_AgfaImpax:
+        return "AgfaImpax";
+      
+      case ModalityManufacturer_EFilm2:
+        return "EFilm2";
+      
+      case ModalityManufacturer_Vitrea:
+        return "Vitrea";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(DicomRequestType type)
+  {
+    switch (type)
+    {
+      case DicomRequestType_Echo:
+        return "Echo";
+        break;
+
+      case DicomRequestType_Find:
+        return "Find";
+        break;
+
+      case DicomRequestType_Get:
+        return "Get";
+        break;
+
+      case DicomRequestType_Move:
+        return "Move";
+        break;
+
+      case DicomRequestType_Store:
+        return "Store";
+        break;
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer)
+  {
+    if (manufacturer == "Generic")
+    {
+      return ModalityManufacturer_Generic;
+    }
+    else if (manufacturer == "ClearCanvas")
+    {
+      return ModalityManufacturer_ClearCanvas;
+    }
+    else if (manufacturer == "StoreScp")
+    {
+      return ModalityManufacturer_StoreScp;
+    }
+    else if (manufacturer == "MedInria")
+    {
+      return ModalityManufacturer_MedInria;
+    }
+    else if (manufacturer == "Dcm4Chee")
+    {
+      return ModalityManufacturer_Dcm4Chee;
+    }
+    else if (manufacturer == "SyngoVia")
+    {
+      return ModalityManufacturer_SyngoVia;
+    }
+    else if (manufacturer == "AgfaImpax")
+    {
+      return ModalityManufacturer_AgfaImpax;
+    }
+    else if (manufacturer == "Vitrea")
+    {
+      return ModalityManufacturer_Vitrea;
+    }
+    else if (manufacturer == "EFilm2")
+    {
+      return ModalityManufacturer_EFilm2;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(TransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case TransferSyntax_Deflated:
+        return "Deflated";
+
+      case TransferSyntax_Jpeg:
+        return "JPEG";
+
+      case TransferSyntax_Jpeg2000:
+        return "JPEG2000";
+
+      case TransferSyntax_JpegLossless:
+        return "JPEG Lossless";
+
+      case TransferSyntax_Jpip:
+        return "JPIP";
+
+      case TransferSyntax_Mpeg2:
+        return "MPEG2";
+
+      case TransferSyntax_Rle:
+        return "RLE";
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool IsUserMetadata(MetadataType metadata)
+  {
+    return (metadata >= MetadataType_StartUser &&
+            metadata <= MetadataType_EndUser);
+  }
+}
diff --git a/Framework/Orthanc/OrthancServer/ServerEnumerations.h b/Framework/Orthanc/OrthancServer/ServerEnumerations.h
new file mode 100644
index 0000000..8765680
--- /dev/null
+++ b/Framework/Orthanc/OrthancServer/ServerEnumerations.h
@@ -0,0 +1,232 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <string>
+#include <map>
+
+#include "../Core/Enumerations.h"
+#include "../Core/DicomFormat/DicomTag.h"
+
+namespace Orthanc
+{
+  enum SeriesStatus
+  {
+    SeriesStatus_Complete,
+    SeriesStatus_Missing,
+    SeriesStatus_Inconsistent,
+    SeriesStatus_Unknown
+  };
+
+  enum StoreStatus
+  {
+    StoreStatus_Success,
+    StoreStatus_AlreadyStored,
+    StoreStatus_Failure,
+    StoreStatus_FilteredOut     // Removed by NewInstanceFilter
+  };
+
+  enum ModalityManufacturer
+  {
+    ModalityManufacturer_Generic,
+    ModalityManufacturer_StoreScp,
+    ModalityManufacturer_ClearCanvas,
+    ModalityManufacturer_MedInria,
+    ModalityManufacturer_Dcm4Chee,
+    ModalityManufacturer_SyngoVia,
+    ModalityManufacturer_AgfaImpax,
+    ModalityManufacturer_EFilm2,
+    ModalityManufacturer_Vitrea
+  };
+
+  enum DicomRequestType
+  {
+    DicomRequestType_Echo,
+    DicomRequestType_Find,
+    DicomRequestType_Get,
+    DicomRequestType_Move,
+    DicomRequestType_Store
+  };
+
+  enum DicomReplaceMode
+  {
+    DicomReplaceMode_InsertIfAbsent,
+    DicomReplaceMode_ThrowIfAbsent,
+    DicomReplaceMode_IgnoreIfAbsent
+  };
+
+  enum TransferSyntax
+  {
+    TransferSyntax_Deflated,
+    TransferSyntax_Jpeg,
+    TransferSyntax_Jpeg2000,
+    TransferSyntax_JpegLossless,
+    TransferSyntax_Jpip,
+    TransferSyntax_Mpeg2,
+    TransferSyntax_Rle
+  };
+
+  enum DicomToJsonFormat
+  {
+    DicomToJsonFormat_Full,
+    DicomToJsonFormat_Short,
+    DicomToJsonFormat_Human
+  };
+
+  enum DicomToJsonFlags
+  {
+    DicomToJsonFlags_IncludeBinary         = (1 << 0),
+    DicomToJsonFlags_IncludePrivateTags    = (1 << 1),
+    DicomToJsonFlags_IncludeUnknownTags    = (1 << 2),
+    DicomToJsonFlags_IncludePixelData      = (1 << 3),
+    DicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),
+    DicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),
+
+    // Some predefined combinations
+    DicomToJsonFlags_None     = 0,
+    DicomToJsonFlags_Default  = (DicomToJsonFlags_IncludeBinary |
+                                 DicomToJsonFlags_IncludePixelData | 
+                                 DicomToJsonFlags_IncludePrivateTags | 
+                                 DicomToJsonFlags_IncludeUnknownTags | 
+                                 DicomToJsonFlags_ConvertBinaryToNull)
+  };
+
+  enum DicomFromJsonFlags
+  {
+    DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0),
+    DicomFromJsonFlags_GenerateIdentifiers = (1 << 1)
+  };
+
+  enum IdentifierConstraintType
+  {
+    IdentifierConstraintType_Equal,
+    IdentifierConstraintType_SmallerOrEqual,
+    IdentifierConstraintType_GreaterOrEqual,
+    IdentifierConstraintType_Wildcard        /* Case sensitive, "*" or "?" are the only allowed wildcards */
+  };
+
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum GlobalProperty
+  {
+    GlobalProperty_DatabaseSchemaVersion = 1,   // Unused in the Orthanc core as of Orthanc 0.9.5
+    GlobalProperty_FlushSleep = 2,
+    GlobalProperty_AnonymizationSequence = 3
+  };
+
+  enum MetadataType
+  {
+    MetadataType_Instance_IndexInSeries = 1,
+    MetadataType_Instance_ReceptionDate = 2,
+    MetadataType_Instance_RemoteAet = 3,
+    MetadataType_Series_ExpectedNumberOfInstances = 4,
+    MetadataType_ModifiedFrom = 5,
+    MetadataType_AnonymizedFrom = 6,
+    MetadataType_LastUpdate = 7,
+    MetadataType_Instance_Origin = 8,   // New in Orthanc 0.9.5
+
+    // Make sure that the value "65535" can be stored into this enumeration
+    MetadataType_StartUser = 1024,
+    MetadataType_EndUser = 65535
+  };
+
+  enum ChangeType
+  {
+    ChangeType_CompletedSeries = 1,
+    ChangeType_NewInstance = 2,
+    ChangeType_NewPatient = 3,
+    ChangeType_NewSeries = 4,
+    ChangeType_NewStudy = 5,
+    ChangeType_AnonymizedStudy = 6,
+    ChangeType_AnonymizedSeries = 7,
+    ChangeType_ModifiedStudy = 8,
+    ChangeType_ModifiedSeries = 9,
+    ChangeType_AnonymizedPatient = 10,
+    ChangeType_ModifiedPatient = 11,
+    ChangeType_StablePatient = 12,
+    ChangeType_StableStudy = 13,
+    ChangeType_StableSeries = 14,
+    ChangeType_UpdatedAttachment = 15,
+    ChangeType_UpdatedMetadata = 16,
+
+    ChangeType_INTERNAL_LastLogged = 4095,
+
+    // The changes below this point are not logged into the database
+    ChangeType_Deleted = 4096,
+    ChangeType_NewChildInstance = 4097
+  };
+
+
+
+  void InitializeServerEnumerations();
+
+  void RegisterUserMetadata(int metadata,
+                            const std::string& name);
+
+  MetadataType StringToMetadata(const std::string& str);
+
+  std::string EnumerationToString(MetadataType type);
+
+  void RegisterUserContentType(int contentType,
+                               const std::string& name,
+                               const std::string& mime);
+
+  FileContentType StringToContentType(const std::string& str);
+
+  std::string EnumerationToString(FileContentType type);
+
+  std::string GetFileContentMime(FileContentType type);
+
+  std::string GetBasePath(ResourceType type,
+                          const std::string& publicId);
+
+  const char* EnumerationToString(SeriesStatus status);
+
+  const char* EnumerationToString(StoreStatus status);
+
+  const char* EnumerationToString(ChangeType type);
+
+  const char* EnumerationToString(ModalityManufacturer manufacturer);
+
+  const char* EnumerationToString(DicomRequestType type);
+
+  const char* EnumerationToString(TransferSyntax syntax);
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
+
+  bool IsUserMetadata(MetadataType type);
+}
diff --git a/Framework/Orthanc/OrthancServer/ToDcmtkBridge.cpp b/Framework/Orthanc/OrthancServer/ToDcmtkBridge.cpp
new file mode 100644
index 0000000..708293a
--- /dev/null
+++ b/Framework/Orthanc/OrthancServer/ToDcmtkBridge.cpp
@@ -0,0 +1,167 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersServer.h"
+#include "ToDcmtkBridge.h"
+
+#include <memory>
+#include <dcmtk/dcmnet/diutil.h>
+
+#include "../Core/OrthancException.h"
+
+
+namespace Orthanc
+{
+  DcmDataset* ToDcmtkBridge::Convert(const DicomMap& map)
+  {
+    std::auto_ptr<DcmDataset> result(new DcmDataset);
+
+    for (DicomMap::Map::const_iterator 
+           it = map.map_.begin(); it != map.map_.end(); ++it)
+    {
+      if (!it->second->IsNull())
+      {
+        std::string s = it->second->GetContent();
+        DU_putStringDOElement(result.get(), Convert(it->first), s.c_str());
+      }
+    }
+
+    return result.release();
+  }
+
+
+  DcmEVR ToDcmtkBridge::Convert(ValueRepresentation vr)
+  {
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:
+        return EVR_AE;
+
+      case ValueRepresentation_AgeString:
+        return EVR_AS;
+
+      case ValueRepresentation_AttributeTag:
+        return EVR_AT;
+
+      case ValueRepresentation_CodeString:
+        return EVR_CS;
+
+      case ValueRepresentation_Date:
+        return EVR_DA;
+
+      case ValueRepresentation_DecimalString:
+        return EVR_DS;
+
+      case ValueRepresentation_DateTime:
+        return EVR_DT;
+
+      case ValueRepresentation_FloatingPointSingle:
+        return EVR_FL;
+
+      case ValueRepresentation_FloatingPointDouble:
+        return EVR_FD;
+
+      case ValueRepresentation_IntegerString:
+        return EVR_IS;
+
+      case ValueRepresentation_LongString:
+        return EVR_LO;
+
+      case ValueRepresentation_LongText:
+        return EVR_LT;
+
+      case ValueRepresentation_OtherByte:
+        return EVR_OB;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_OtherDouble:
+          return EVR_OD;*/
+
+      case ValueRepresentation_OtherFloat:
+        return EVR_OF;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_OtherLong:
+          return EVR_OL;*/
+
+      case ValueRepresentation_OtherWord:
+        return EVR_OW;
+
+      case ValueRepresentation_PersonName:
+        return EVR_PN;
+
+      case ValueRepresentation_ShortString:
+        return EVR_SH;
+
+      case ValueRepresentation_SignedLong:
+        return EVR_SL;
+
+      case ValueRepresentation_Sequence:
+        return EVR_SQ;
+
+      case ValueRepresentation_SignedShort:
+        return EVR_SS;
+
+      case ValueRepresentation_ShortText:
+        return EVR_ST;
+
+      case ValueRepresentation_Time:
+        return EVR_TM;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_UnlimitedCharacters:
+          return EVR_UC;*/
+
+      case ValueRepresentation_UniqueIdentifier:
+        return EVR_UI;
+
+      case ValueRepresentation_UnsignedLong:
+        return EVR_UL;
+
+      case ValueRepresentation_Unknown:
+        return EVR_UN;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_UniversalResource:
+          return EVR_UR;*/
+
+      case ValueRepresentation_UnsignedShort:
+        return EVR_US;
+
+      case ValueRepresentation_UnlimitedText:
+        return EVR_UT;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
diff --git a/Framework/Orthanc/OrthancServer/ToDcmtkBridge.h b/Framework/Orthanc/OrthancServer/ToDcmtkBridge.h
new file mode 100644
index 0000000..fff8bd4
--- /dev/null
+++ b/Framework/Orthanc/OrthancServer/ToDcmtkBridge.h
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/DicomFormat/DicomMap.h"
+#include <dcmtk/dcmdata/dcdatset.h>
+
+namespace Orthanc
+{
+  class ToDcmtkBridge
+  {
+  public:
+    static DcmTagKey Convert(const DicomTag& tag)
+    {
+      return DcmTagKey(tag.GetGroup(), tag.GetElement());
+    }
+
+    static DcmDataset* Convert(const DicomMap& map);
+
+    static DcmEVR Convert(ValueRepresentation vr);
+  };
+}
diff --git a/Framework/Orthanc/Plugins/Engine/SharedLibrary.cpp b/Framework/Orthanc/Plugins/Engine/SharedLibrary.cpp
new file mode 100644
index 0000000..7fefc49
--- /dev/null
+++ b/Framework/Orthanc/Plugins/Engine/SharedLibrary.cpp
@@ -0,0 +1,140 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../OrthancServer/PrecompiledHeadersServer.h"
+#include "SharedLibrary.h"
+
+#if ORTHANC_PLUGINS_ENABLED != 1
+#error The plugin support is disabled
+#endif
+
+
+#include "../../Core/Logging.h"
+#include "../../Core/Toolbox.h"
+
+#include <boost/filesystem.hpp>
+
+#if defined(_WIN32)
+#include <windows.h>
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#include <dlfcn.h>
+#else
+#error Support your platform here
+#endif
+
+namespace Orthanc
+{
+  SharedLibrary::SharedLibrary(const std::string& path) : 
+    path_(path), 
+    handle_(NULL)
+  {
+#if defined(_WIN32)
+    handle_ = ::LoadLibraryA(path_.c_str());
+    if (handle_ == NULL)
+    {
+      LOG(ERROR) << "LoadLibrary(" << path_ << ") failed: Error " << ::GetLastError();
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+    handle_ = ::dlopen(path_.c_str(), RTLD_NOW);
+    if (handle_ == NULL) 
+    {
+      std::string explanation;
+      const char *tmp = ::dlerror();
+      if (tmp)
+      {
+        explanation = ": Error " + std::string(tmp);
+      }
+
+      LOG(ERROR) << "dlopen(" << path_ << ") failed" << explanation;
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#else
+#error Support your platform here
+#endif   
+  }
+
+  SharedLibrary::~SharedLibrary()
+  {
+    if (handle_)
+    {
+#if defined(_WIN32)
+      ::FreeLibrary((HMODULE)handle_);
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+      ::dlclose(handle_);
+#else
+#error Support your platform here
+#endif
+    }
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunctionInternal(const std::string& name)
+  {
+    if (!handle_)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+#if defined(_WIN32)
+    return ::GetProcAddress((HMODULE)handle_, name.c_str());
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+    return ::dlsym(handle_, name.c_str());
+#else
+#error Support your platform here
+#endif
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunction(const std::string& name)
+  {
+    SharedLibrary::FunctionPointer result = GetFunctionInternal(name);
+  
+    if (result == NULL)
+    {
+      LOG(ERROR) << "Shared library does not expose function \"" << name << "\"";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  bool SharedLibrary::HasFunction(const std::string& name)
+  {
+    return GetFunctionInternal(name) != NULL;
+  }
+}
diff --git a/Framework/Orthanc/Plugins/Engine/SharedLibrary.h b/Framework/Orthanc/Plugins/Engine/SharedLibrary.h
new file mode 100644
index 0000000..5a7913a
--- /dev/null
+++ b/Framework/Orthanc/Plugins/Engine/SharedLibrary.h
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_PLUGINS_ENABLED == 1
+
+#include "../../Core/OrthancException.h"
+
+#include <boost/noncopyable.hpp>
+
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
+namespace Orthanc
+{
+  class SharedLibrary : public boost::noncopyable
+  {
+  public:
+#if defined(_WIN32)
+    typedef FARPROC FunctionPointer;
+#else
+    typedef void* FunctionPointer;
+#endif
+
+  private:
+    std::string path_;
+    void *handle_;
+
+    FunctionPointer GetFunctionInternal(const std::string& name);
+
+  public:
+    SharedLibrary(const std::string& path);
+
+    ~SharedLibrary();
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    bool HasFunction(const std::string& name);
+
+    FunctionPointer GetFunction(const std::string& name);
+  };
+}
+
+#endif
diff --git a/Framework/Orthanc/Plugins/Samples/Common/ExportedSymbols.list b/Framework/Orthanc/Plugins/Samples/Common/ExportedSymbols.list
new file mode 100644
index 0000000..f76e0e8
--- /dev/null
+++ b/Framework/Orthanc/Plugins/Samples/Common/ExportedSymbols.list
@@ -0,0 +1,7 @@
+# This is the list of the symbols that must be exported by Orthanc
+# plugins, if targeting OS X
+
+_OrthancPluginInitialize
+_OrthancPluginFinalize
+_OrthancPluginGetName
+_OrthancPluginGetVersion
diff --git a/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp b/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
new file mode 100644
index 0000000..50412d7
--- /dev/null
+++ b/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
@@ -0,0 +1,1002 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancPluginCppWrapper.h"
+
+#include <json/reader.h>
+#include <json/writer.h>
+
+
+namespace OrthancPlugins
+{
+  const char* PluginException::GetErrorDescription(OrthancPluginContext* context) const
+  {
+    const char* description = OrthancPluginGetErrorDescription(context, code_);
+    if (description)
+    {
+      return description;
+    }
+    else
+    {
+      return "No description available";
+    }
+  }
+
+  
+  void PluginException::Check(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      throw PluginException(code);
+    }
+  }
+
+
+  void MemoryBuffer::Check(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+      throw PluginException(code);
+    }
+  }
+
+
+  MemoryBuffer::MemoryBuffer(OrthancPluginContext* context) : 
+    context_(context)
+  {
+    buffer_.data = NULL;
+    buffer_.size = 0;
+  }
+
+
+  void MemoryBuffer::Clear()
+  {
+    if (buffer_.data != NULL)
+    {
+      OrthancPluginFreeMemoryBuffer(context_, &buffer_);
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+  }
+
+
+  void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
+  {
+    Clear();
+
+    buffer_.data = other.data;
+    buffer_.size = other.size;
+
+    other.data = NULL;
+    other.size = 0;
+  }
+
+
+  void MemoryBuffer::ToString(std::string& target) const
+  {
+    if (buffer_.size == 0)
+    {
+      target.clear();
+    }
+    else
+    {
+      target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
+    }
+  }
+
+
+  void MemoryBuffer::ToJson(Json::Value& target) const
+  {
+    if (buffer_.data == NULL ||
+        buffer_.size == 0)
+    {
+      throw PluginException(OrthancPluginErrorCode_InternalError);
+    }
+
+    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
+
+    Json::Reader reader;
+    if (!reader.parse(tmp, tmp + buffer_.size, target))
+    {
+      OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
+      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiGetAfterPlugins(context_, &buffer_, uri.c_str());
+    }
+    else
+    {
+      error = OrthancPluginRestApiGet(context_, &buffer_, uri.c_str());
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      throw PluginException(error);
+    }
+  }
+
+  
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const char* body,
+                                 size_t bodySize,
+                                 bool applyPlugins)
+  {
+    Clear();
+
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiPostAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize);
+    }
+    else
+    {
+      error = OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body, bodySize);
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      throw PluginException(error);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const char* body,
+                                size_t bodySize,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiPutAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize);
+    }
+    else
+    {
+      error = OrthancPluginRestApiPut(context_, &buffer_, uri.c_str(), body, bodySize);
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      throw PluginException(error);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const Json::Value& body,
+                                 bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const Json::Value& body,
+                                bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(uri, writer.write(body), applyPlugins);
+  }
+
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+    
+    Check(OrthancPluginCreateDicom(context_, &buffer_, s.c_str(), NULL, flags));
+  }
+
+
+  void MemoryBuffer::ReadFile(const std::string& path)
+  {
+    Clear();
+    Check(OrthancPluginReadFile(context_, &buffer_, path.c_str()));
+  }
+
+
+  OrthancString::OrthancString(OrthancPluginContext* context,
+                               char* str) :
+    context_(context),
+    str_(str)
+  {
+  }
+
+
+  void OrthancString::Clear()
+  {
+    if (str_ != NULL)
+    {
+      OrthancPluginFreeString(context_, str_);
+      str_ = NULL;
+    }
+  }
+
+
+  void OrthancString::ToString(std::string& target) const
+  {
+    if (str_ == NULL)
+    {
+      target.clear();
+    }
+    else
+    {
+      target.assign(str_);
+    }
+  }
+
+
+  void OrthancString::ToJson(Json::Value& target) const
+  {
+    if (str_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot convert an empty memory buffer to JSON");
+      throw PluginException(OrthancPluginErrorCode_InternalError);
+    }
+
+    Json::Reader reader;
+    if (!reader.parse(str_, target))
+    {
+      OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
+      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+    }
+  }
+  
+
+  OrthancConfiguration::OrthancConfiguration(OrthancPluginContext* context) : 
+    context_(context)
+  {
+    OrthancString str(context, OrthancPluginGetConfiguration(context));
+
+    if (str.GetContent() == NULL)
+    {
+      OrthancPluginLogError(context, "Cannot access the Orthanc configuration");
+      throw PluginException(OrthancPluginErrorCode_InternalError);
+    }
+
+    str.ToJson(configuration_);
+
+    if (configuration_.type() != Json::objectValue)
+    {
+      OrthancPluginLogError(context, "Unable to read the Orthanc configuration");
+      throw PluginException(OrthancPluginErrorCode_InternalError);
+    }
+  }
+
+
+  OrthancPluginContext* OrthancConfiguration::GetContext() const
+  {
+    if (context_ == NULL)
+    {
+      throw PluginException(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      return context_;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetPath(const std::string& key) const
+  {
+    if (path_.empty())
+    {
+      return key;
+    }
+    else
+    {
+      return path_ + "." + key;
+    }
+  }
+
+
+  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
+                                        const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.context_ = context_;
+    target.path_ = GetPath(key);
+
+    if (!configuration_.isMember(key))
+    {
+      target.configuration_ = Json::objectValue;
+    }
+    else
+    {
+      if (configuration_[key].type() != Json::objectValue)
+      {
+        if (context_ != NULL)
+        {
+          std::string s = "The configuration section \"" + target.path_ + "\" is not an associative array as expected";
+          OrthancPluginLogError(context_, s.c_str());
+        }
+
+        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      }
+
+      target.configuration_ = configuration_[key];
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupStringValue(std::string& target,
+                                               const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    if (configuration_[key].type() != Json::stringValue)
+    {
+      if (context_ != NULL)
+      {
+        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a string as expected";
+        OrthancPluginLogError(context_, s.c_str());
+      }
+
+      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+    }
+
+    target = configuration_[key].asString();
+    return true;
+  }
+
+
+  bool OrthancConfiguration::LookupIntegerValue(int& target,
+                                                const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::intValue:
+        target = configuration_[key].asInt();
+        return true;
+        
+      case Json::uintValue:
+        target = configuration_[key].asUInt();
+        return true;
+        
+      default:
+        if (context_ != NULL)
+        {
+          std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected";
+          OrthancPluginLogError(context_, s.c_str());
+        }
+
+        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
+                                                        const std::string& key) const
+  {
+    int tmp;
+    if (!LookupIntegerValue(tmp, key))
+    {
+      return false;
+    }
+
+    if (tmp < 0)
+    {
+      if (context_ != NULL)
+      {
+        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a positive integer as expected";
+        OrthancPluginLogError(context_, s.c_str());
+      }
+
+      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      target = static_cast<unsigned int>(tmp);
+      return true;
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupBooleanValue(bool& target,
+                                                const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    if (configuration_[key].type() != Json::booleanValue)
+    {
+      if (context_ != NULL)
+      {
+        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a Boolean as expected";
+        OrthancPluginLogError(context_, s.c_str());
+      }
+
+      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+    }
+
+    target = configuration_[key].asBool();
+    return true;
+  }
+
+
+  bool OrthancConfiguration::LookupFloatValue(float& target,
+                                              const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::realValue:
+        target = configuration_[key].asFloat();
+        return true;
+        
+      case Json::intValue:
+        target = static_cast<float>(configuration_[key].asInt());
+        return true;
+        
+      case Json::uintValue:
+        target = static_cast<float>(configuration_[key].asUInt());
+        return true;
+        
+      default:
+        if (context_ != NULL)
+        {
+          std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected";
+          OrthancPluginLogError(context_, s.c_str());
+        }
+
+        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+    }
+  }
+
+  
+  std::string OrthancConfiguration::GetStringValue(const std::string& key,
+                                                   const std::string& defaultValue) const
+  {
+    std::string tmp;
+    if (LookupStringValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  int OrthancConfiguration::GetIntegerValue(const std::string& key,
+                                            int defaultValue) const
+  {
+    int tmp;
+    if (LookupIntegerValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
+                                                             unsigned int defaultValue) const
+  {
+    unsigned int tmp;
+    if (LookupUnsignedIntegerValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool OrthancConfiguration::GetBooleanValue(const std::string& key,
+                                             bool defaultValue) const
+  {
+    bool tmp;
+    if (LookupBooleanValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  float OrthancConfiguration::GetFloatValue(const std::string& key,
+                                            float defaultValue) const
+  {
+    float tmp;
+    if (LookupFloatValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  void OrthancImage::Clear()
+  {
+    if (image_ != NULL)
+    {
+      OrthancPluginFreeImage(context_, image_);
+      image_ = NULL;
+    }
+  }
+
+
+  void OrthancImage::CheckImageAvailable()
+  {
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Trying to access a NULL image");
+      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginContext*  context) :
+    context_(context),
+    image_(NULL)
+  {
+    if (context == NULL)
+    {
+      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginContext*  context,
+                             OrthancPluginImage*    image) :
+    context_(context),
+    image_(image)
+  {
+    if (context == NULL)
+    {
+      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+  
+
+  OrthancImage::OrthancImage(OrthancPluginContext*     context,
+                             OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height) :
+    context_(context)
+  {
+    if (context == NULL)
+    {
+      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      image_ = OrthancPluginCreateImage(context, format, width, height);
+    }
+  }
+
+
+  void OrthancImage::UncompressPngImage(const void* data,
+                                        size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Png);
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot uncompress a PNG image");
+      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::UncompressJpegImage(const void* data,
+                                         size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Jpeg);
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot uncompress a JPEG image");
+      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::DecodeDicomImage(const void* data,
+                                      size_t size,
+                                      unsigned int frame)
+  {
+    Clear();
+    image_ = OrthancPluginDecodeDicomImage(context_, data, size, frame);
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot uncompress a DICOM image");
+      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancPluginPixelFormat OrthancImage::GetPixelFormat()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePixelFormat(context_, image_);
+  }
+
+
+  unsigned int OrthancImage::GetWidth()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageWidth(context_, image_);
+  }
+
+
+  unsigned int OrthancImage::GetHeight()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageHeight(context_, image_);
+  }
+
+
+  unsigned int OrthancImage::GetPitch()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePitch(context_, image_);
+  }
+
+    
+  const void* OrthancImage::GetBuffer()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageBuffer(context_, image_);
+  }
+
+
+  void OrthancImage::CompressPngImage(MemoryBuffer& target)
+  {
+    CheckImageAvailable();
+    
+    OrthancPluginMemoryBuffer tmp;
+    OrthancPluginCompressPngImage(context_, &tmp, GetPixelFormat(), 
+                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+
+    target.Assign(tmp);
+  }
+
+
+  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
+                                       uint8_t quality)
+  {
+    CheckImageAvailable();
+    
+    OrthancPluginMemoryBuffer tmp;
+    OrthancPluginCompressJpegImage(context_, &tmp, GetPixelFormat(), 
+                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+    
+    target.Assign(tmp);
+  }
+
+
+  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output)
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerPngImage(context_, output, GetPixelFormat(),
+                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+  }
+
+
+  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
+                                     uint8_t quality)
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerJpegImage(context_, output, GetPixelFormat(),
+                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+  }
+
+
+  bool RestApiGet(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer(context);
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToJson(result);
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const char* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer(context);
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToJson(result);
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(result, context, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  const char* body,
+                  size_t bodySize,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer(context);
+    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToJson(result);
+      return true;
+    }
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(result, context, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiDelete(OrthancPluginContext* context,
+                     const std::string& uri,
+                     bool applyPlugins)
+  {
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiDeleteAfterPlugins(context, uri.c_str());
+    }
+    else
+    {
+      error = OrthancPluginRestApiDelete(context, uri.c_str());
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      throw PluginException(error);
+    }
+  }
+
+
+  static void ReportIncompatibleVersion(OrthancPluginContext* context,
+                                        unsigned int major,
+                                        unsigned int minor,
+                                        unsigned int revision)
+  {
+    char buf[128];
+    sprintf(buf, "Your version of the Orthanc core (%s) is too old to run this plugin (%d.%d.%d is required)",
+            context->orthancVersion, major, minor, revision);
+    OrthancPluginLogError(context, buf);
+  }
+
+
+  bool CheckMinimalOrthancVersion(OrthancPluginContext* context,
+                                  unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision)
+  {
+    if (context == NULL)
+    {
+      OrthancPluginLogError(context, "Bad Orthanc context in the plugin");      
+      return false;
+    }
+
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      // Assume compatibility with the mainline
+      return true;
+    }
+
+    // Parse the version of the Orthanc core
+    int aa, bb, cc;
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
+    {
+      throw false;
+    }
+
+    unsigned int a = static_cast<unsigned int>(aa);
+    unsigned int b = static_cast<unsigned int>(bb);
+    unsigned int c = static_cast<unsigned int>(cc);
+
+    // Check the major version number
+
+    if (a > major)
+    {
+      return true;
+    }
+
+    if (a < major)
+    {
+      ReportIncompatibleVersion(context, major, minor, revision);
+      return false;
+    }
+
+
+    // Check the minor version number
+    assert(a == major);
+
+    if (b > minor)
+    {
+      return true;
+    }
+
+    if (b < minor)
+    {
+      ReportIncompatibleVersion(context, major, minor, revision);
+      return false;
+    }
+
+    // Check the patch level version number
+    assert(a == major && b == minor);
+
+    if (c >= revision)
+    {
+      return true;
+    }
+    else
+    {
+      ReportIncompatibleVersion(context, major, minor, revision);
+      return false;
+    }
+  }
+}
+
diff --git a/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h b/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
new file mode 100644
index 0000000..6a6ab91
--- /dev/null
+++ b/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
@@ -0,0 +1,445 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <orthanc/OrthancCPlugin.h>
+#include <boost/noncopyable.hpp>
+#include <boost/lexical_cast.hpp>
+#include <json/value.h>
+
+#if HAS_ORTHANC_EXCEPTION == 1
+#  include "../../../Core/OrthancException.h"
+#endif
+
+
+namespace OrthancPlugins
+{
+  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
+                                const char* url,
+                                const OrthancPluginHttpRequest* request);
+
+
+  class PluginException
+  {
+  private:
+    OrthancPluginErrorCode  code_;
+
+  public:
+    PluginException(OrthancPluginErrorCode code) : code_(code)
+    {
+    }
+
+    OrthancPluginErrorCode GetErrorCode() const
+    {
+      return code_;
+    }
+
+    const char* GetErrorDescription(OrthancPluginContext* context) const;
+
+    static void Check(OrthancPluginErrorCode code);
+  };
+
+
+  class MemoryBuffer : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*      context_;
+    OrthancPluginMemoryBuffer  buffer_;
+
+    void Check(OrthancPluginErrorCode code);
+
+  public:
+    MemoryBuffer(OrthancPluginContext* context);
+
+    ~MemoryBuffer()
+    {
+      Clear();
+    }
+
+    OrthancPluginMemoryBuffer* operator*()
+    {
+      return &buffer_;
+    }
+
+    // This transfers ownership
+    void Assign(OrthancPluginMemoryBuffer& other);
+
+    const char* GetData() const
+    {
+      if (buffer_.size > 0)
+      {
+        return reinterpret_cast<const char*>(buffer_.data);
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return buffer_.size;
+    }
+
+    void Clear();
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+
+    bool RestApiGet(const std::string& uri,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const char* body,
+                     size_t bodySize,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const char* body,
+                    size_t bodySize,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const Json::Value& body,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const Json::Value& body,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const std::string& body,
+                     bool applyPlugins)
+    {
+      return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
+    }
+
+    bool RestApiPut(const std::string& uri,
+                    const std::string& body,
+                    bool applyPlugins)
+    {
+      return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
+    }
+
+    void CreateDicom(const Json::Value& tags,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void ReadFile(const std::string& path);
+  };
+
+
+  class OrthancString : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*  context_;
+    char*                  str_;
+
+  public:
+    OrthancString(OrthancPluginContext* context,
+                  char* str);
+
+    ~OrthancString()
+    {
+      Clear();
+    }
+
+    void Clear();
+
+    const char* GetContent() const
+    {
+      return str_;
+    }
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+  };
+
+
+  class OrthancConfiguration : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*  context_;
+    Json::Value            configuration_;
+    std::string            path_;
+
+    std::string GetPath(const std::string& key) const;
+
+  public:
+    OrthancConfiguration() : context_(NULL)
+    {
+    }
+
+    OrthancConfiguration(OrthancPluginContext* context);
+
+    OrthancPluginContext* GetContext() const;
+
+    const Json::Value& GetJson() const
+    {
+      return configuration_;
+    }
+
+    void GetSection(OrthancConfiguration& target,
+                    const std::string& key) const;
+
+    bool LookupStringValue(std::string& target,
+                           const std::string& key) const;
+    
+    bool LookupIntegerValue(int& target,
+                            const std::string& key) const;
+
+    bool LookupUnsignedIntegerValue(unsigned int& target,
+                                    const std::string& key) const;
+
+    bool LookupBooleanValue(bool& target,
+                            const std::string& key) const;
+
+    bool LookupFloatValue(float& target,
+                          const std::string& key) const;
+
+    std::string GetStringValue(const std::string& key,
+                               const std::string& defaultValue) const;
+
+    int GetIntegerValue(const std::string& key,
+                        int defaultValue) const;
+
+    unsigned int GetUnsignedIntegerValue(const std::string& key,
+                                         unsigned int defaultValue) const;
+
+    bool GetBooleanValue(const std::string& key,
+                         bool defaultValue) const;
+
+    float GetFloatValue(const std::string& key,
+                        float defaultValue) const;
+  };
+
+  class OrthancImage
+  {
+  private:
+    OrthancPluginContext*  context_;
+    OrthancPluginImage*    image_;
+
+    void Clear();
+
+    void CheckImageAvailable();
+
+  public:
+    OrthancImage(OrthancPluginContext*  context);
+
+    OrthancImage(OrthancPluginContext*  context,
+                 OrthancPluginImage*    image);
+
+    OrthancImage(OrthancPluginContext*     context,
+                 OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height);
+
+    ~OrthancImage()
+    {
+      Clear();
+    }
+
+    void UncompressPngImage(const void* data,
+                            size_t size);
+
+    void UncompressJpegImage(const void* data,
+                             size_t size);
+
+    void DecodeDicomImage(const void* data,
+                          size_t size,
+                          unsigned int frame);
+
+    OrthancPluginPixelFormat GetPixelFormat();
+
+    unsigned int GetWidth();
+
+    unsigned int GetHeight();
+
+    unsigned int GetPitch();
+    
+    const void* GetBuffer();
+
+    void CompressPngImage(MemoryBuffer& target);
+
+    void CompressJpegImage(MemoryBuffer& target,
+                           uint8_t quality);
+
+    void AnswerPngImage(OrthancPluginRestOutput* output);
+
+    void AnswerJpegImage(OrthancPluginRestOutput* output,
+                         uint8_t quality);
+  };
+
+
+  bool RestApiGet(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const char* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins);
+
+  inline bool RestApiPost(Json::Value& result,
+                          OrthancPluginContext* context,
+                          const std::string& uri,
+                          const std::string& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, context, uri, body.empty() ? NULL : body.c_str(), 
+                       body.size(), applyPlugins);
+  }
+
+  bool RestApiPut(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  const char* body,
+                  size_t bodySize,
+                  bool applyPlugins);
+
+  bool RestApiPut(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins);
+
+  inline bool RestApiPut(Json::Value& result,
+                         OrthancPluginContext* context,
+                         const std::string& uri,
+                         const std::string& body,
+                         bool applyPlugins)
+  {
+    return RestApiPut(result, context, uri, body.empty() ? NULL : body.c_str(), 
+                      body.size(), applyPlugins);
+  }
+
+  bool RestApiDelete(OrthancPluginContext* context,
+                     const std::string& uri,
+                     bool applyPlugins);
+
+  bool RestApiDelete(OrthancPluginContext* context,
+                     const std::string& uri,
+                     bool applyPlugins);
+
+  inline void LogError(OrthancPluginContext* context,
+                       const std::string& message)
+  {
+    if (context != NULL)
+    {
+      OrthancPluginLogError(context, message.c_str());
+    }
+  }
+
+  inline void LogWarning(OrthancPluginContext* context,
+                         const std::string& message)
+  {
+    if (context != NULL)
+    {
+      OrthancPluginLogWarning(context, message.c_str());
+    }
+  }
+
+  inline void LogInfo(OrthancPluginContext* context,
+                      const std::string& message)
+  {
+    if (context != NULL)
+    {
+      OrthancPluginLogInfo(context, message.c_str());
+    }
+  }
+
+  bool CheckMinimalOrthancVersion(OrthancPluginContext* context,
+                                  unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision);
+
+
+  namespace Internals
+  {
+    template <RestCallback Callback>
+    OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
+                                   const char* url,
+                                   const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        Callback(output, url, request);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (OrthancPlugins::PluginException& e)
+      {
+        return e.GetErrorCode();
+      }
+#if HAS_ORTHANC_EXCEPTION == 1
+      catch (Orthanc::OrthancException& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+#endif
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+  }
+
+  
+  template <RestCallback Callback>
+  void RegisterRestCallback(OrthancPluginContext* context,
+                            const std::string& uri,
+                            bool isThreadSafe)
+  {
+    if (isThreadSafe)
+    {
+      OrthancPluginRegisterRestCallbackNoLock(context, uri.c_str(), Internals::Protect<Callback>);
+    }
+    else
+    {
+      OrthancPluginRegisterRestCallback(context, uri.c_str(), Internals::Protect<Callback>);
+    }
+  }
+}
diff --git a/Framework/Orthanc/Plugins/Samples/Common/VersionScript.map b/Framework/Orthanc/Plugins/Samples/Common/VersionScript.map
new file mode 100644
index 0000000..f36033b
--- /dev/null
+++ b/Framework/Orthanc/Plugins/Samples/Common/VersionScript.map
@@ -0,0 +1,12 @@
+# This is a version-script for Orthanc plugins
+
+{
+global:
+  OrthancPluginInitialize;
+  OrthancPluginFinalize;
+  OrthancPluginGetName;
+  OrthancPluginGetVersion;
+
+local:
+  *;
+};
diff --git a/Framework/Orthanc/README.txt b/Framework/Orthanc/README.txt
new file mode 100644
index 0000000..2e75395
--- /dev/null
+++ b/Framework/Orthanc/README.txt
@@ -0,0 +1,3 @@
+This folder contains an excerpt of the source code of Orthanc. It is
+automatically generated using the "../../Resources/SyncOrthancFolder.py"
+script.
diff --git a/Framework/Orthanc/Resources/CMake/AutoGeneratedCode.cmake b/Framework/Orthanc/Resources/CMake/AutoGeneratedCode.cmake
new file mode 100644
index 0000000..6dded7a
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/AutoGeneratedCode.cmake
@@ -0,0 +1,53 @@
+set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED")
+set(AUTOGENERATED_SOURCES)
+
+file(MAKE_DIRECTORY ${AUTOGENERATED_DIR})
+include_directories(${AUTOGENERATED_DIR})
+
+macro(EmbedResources)
+  # Convert a semicolon separated list to a whitespace separated string
+  set(SCRIPT_OPTIONS)
+  set(SCRIPT_ARGUMENTS)
+  set(DEPENDENCIES)
+  set(IS_PATH_NAME false)
+
+  # Loop over the arguments of the function
+  foreach(arg ${ARGN})
+    # Extract the first character of the argument
+    string(SUBSTRING "${arg}" 0 1 FIRST_CHAR)
+    if (${FIRST_CHAR} STREQUAL "-")
+      # If the argument starts with a dash "-", this is an option to
+      # EmbedResources.py
+      list(APPEND SCRIPT_OPTIONS ${arg})
+    else()
+      if (${IS_PATH_NAME})
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        list(APPEND DEPENDENCIES "${arg}")
+        set(IS_PATH_NAME false)
+      else()
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        set(IS_PATH_NAME true)
+      endif()
+    endif()
+  endforeach()
+
+  set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources")
+  add_custom_command(
+    OUTPUT
+    "${TARGET_BASE}.h"
+    "${TARGET_BASE}.cpp"
+    COMMAND 
+    ${PYTHON_EXECUTABLE}
+    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
+    ${SCRIPT_OPTIONS}
+    "${AUTOGENERATED_DIR}/EmbeddedResources"
+    ${SCRIPT_ARGUMENTS}
+    DEPENDS
+    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
+    ${DEPENDENCIES}
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    "${AUTOGENERATED_DIR}/EmbeddedResources.cpp"
+    ) 
+endmacro()
diff --git a/Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake b/Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake
new file mode 100644
index 0000000..efe0e70
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake
@@ -0,0 +1,225 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
+  set(BOOST_STATIC 1)
+else()
+  include(FindBoost)
+
+  set(BOOST_STATIC 0)
+  #set(Boost_DEBUG 1)
+  #set(Boost_USE_STATIC_LIBS ON)
+
+  find_package(Boost
+    COMPONENTS filesystem thread system date_time regex locale ${ORTHANC_BOOST_COMPONENTS})
+
+  if (NOT Boost_FOUND)
+    message(FATAL_ERROR "Unable to locate Boost on this system")
+  endif()
+
+  # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem
+  # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm
+  if (${Boost_VERSION} LESS 104400)
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=0
+      )
+  else()
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=1
+      -DBOOST_FILESYSTEM_VERSION=3
+      )
+  endif()
+
+  #if (${Boost_VERSION} LESS 104800)
+  # boost::locale is only available from 1.48.00
+  #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version")
+  #  set(BOOST_STATIC 1)
+  #endif()
+
+  include_directories(${Boost_INCLUDE_DIRS})
+  link_libraries(${Boost_LIBRARIES})
+endif()
+
+
+if (BOOST_STATIC)
+  # Parameters for Boost 1.60.0
+  set(BOOST_NAME boost_1_60_0)
+  set(BOOST_BCP_SUFFIX bcpdigest-1.0.1)
+  set(BOOST_MD5 "a789f8ec2056ad1c2d5f0cb64687cc7b")
+  set(BOOST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
+  set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") 
+  set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
+
+  DownloadPackage(${BOOST_MD5} ${BOOST_URL} "${BOOST_SOURCES_DIR}")
+
+  set(BOOST_SOURCES)
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/atomic/src/lockpool.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp
+      )
+    add_definitions(
+      -DBOOST_LOCALE_WITH_ICONV=1
+      -DBOOST_LOCALE_NO_WINAPI_BACKEND=1
+      -DBOOST_LOCALE_NO_STD_BACKEND=1
+      )
+
+    if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
+      add_definitions(-DBOOST_HAS_SCHED_YIELD=1)
+    endif()
+
+  elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp
+      )
+
+    # Starting with release 0.8.2, Orthanc statically links against
+    # libiconv, even on Windows. Indeed, the "WCONV" library of
+    # Windows XP seems not to support properly several codepages
+    # (notably "Latin3", "Hebrew", and "Arabic").
+
+    if (USE_BOOST_ICONV)
+      include(${ORTHANC_ROOT}/Resources/CMake/LibIconvConfiguration.cmake)
+    else()
+      add_definitions(-DBOOST_LOCALE_WITH_WCONV=1)
+    endif()
+
+    add_definitions(
+      -DBOOST_LOCALE_NO_POSIX_BACKEND=1
+      -DBOOST_LOCALE_NO_STD_BACKEND=1
+      )
+  else()
+    message(FATAL_ERROR "Support your platform here")
+  endif()
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp
+      )
+  endif()
+
+  aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES)
+
+  list(APPEND BOOST_SOURCES
+    ${BOOST_REGEX_SOURCES}
+    ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
+    ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
+    ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
+    )
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
+    # boost::filesystem is not available on PNaCl
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=0
+      -D__INTEGRITY=1
+      -DORTHANC_SANDBOXED=1
+      )
+  else()
+    add_definitions(-DBOOST_HAS_FILESYSTEM_V3=1)
+    list(APPEND BOOST_SOURCES
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
+      )
+  endif()
+
+  if (USE_BOOST_LOCALE_BACKENDS)
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/codecvt.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/converter.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/numeric.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/posix_backend.cpp
+        )
+    elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/collate.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/converter.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/lcid.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/numeric.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/win_backend.cpp
+        )
+    else()
+      message(FATAL_ERROR "Support your platform here")
+    endif()
+
+    list(APPEND BOOST_SOURCES
+      ${BOOST_REGEX_SOURCES}
+      ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
+      ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
+
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
+
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/generator.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/date_time.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/formatting.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/ids.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/localization_backend.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/message.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/mo_lambda.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/codecvt_converter.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/default_locale.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/gregorian.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/info.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/locale_data.cpp
+      )        
+  endif()
+
+  add_definitions(
+    # Static build of Boost
+    -DBOOST_ALL_NO_LIB 
+    -DBOOST_ALL_NOLIB 
+    -DBOOST_DATE_TIME_NO_LIB 
+    -DBOOST_THREAD_BUILD_LIB
+    -DBOOST_PROGRAM_OPTIONS_NO_LIB
+    -DBOOST_REGEX_NO_LIB
+    -DBOOST_SYSTEM_NO_LIB
+    -DBOOST_LOCALE_NO_LIB
+    -DBOOST_HAS_LOCALE=1
+    )
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    add_definitions(-isystem ${BOOST_SOURCES_DIR})
+  endif()
+
+  include_directories(
+    ${BOOST_SOURCES_DIR}
+    )
+
+  source_group(ThirdParty\\boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
+
+else()
+  add_definitions(
+    -DBOOST_HAS_LOCALE=1
+    )
+endif()
+
+
+add_definitions(
+  -DBOOST_HAS_DATE_TIME=1
+  -DBOOST_HAS_REGEX=1
+  )
diff --git a/Framework/Orthanc/Resources/CMake/Compiler.cmake b/Framework/Orthanc/Resources/CMake/Compiler.cmake
new file mode 100644
index 0000000..52f4fab
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/Compiler.cmake
@@ -0,0 +1,161 @@
+# This file sets all the compiler-related flags
+
+if (CMAKE_CROSSCOMPILING)
+  # Cross-compilation necessarily implies standalone and static build
+  SET(STATIC_BUILD ON)
+  SET(STANDALONE_BUILD ON)
+endif()
+
+if (CMAKE_COMPILER_IS_GNUCXX)
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration")  
+  # --std=c99 makes libcurl not to compile
+  # -pedantic gives a lot of warnings on OpenSSL 
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wno-variadic-macros")
+
+  if (CMAKE_CROSSCOMPILING)
+    # http://stackoverflow.com/a/3543845/881731
+    set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>")
+  endif()
+
+elseif (MSVC)
+  # Use static runtime under Visual Studio
+  # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace
+  # http://stackoverflow.com/a/6510446
+  foreach(flag_var
+    CMAKE_C_FLAGS_DEBUG
+    CMAKE_CXX_FLAGS_DEBUG
+    CMAKE_C_FLAGS_RELEASE 
+    CMAKE_CXX_FLAGS_RELEASE
+    CMAKE_C_FLAGS_MINSIZEREL 
+    CMAKE_CXX_FLAGS_MINSIZEREL 
+    CMAKE_C_FLAGS_RELWITHDEBINFO 
+    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
+    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
+  endforeach(flag_var)
+
+  # Add /Zm256 compiler option to Visual Studio to fix PCH errors
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256")
+
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    -D_CRT_SECURE_NO_DEPRECATE=1
+    )
+  include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
+  link_libraries(netapi32)
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+
+  if (NOT DEFINED ENABLE_PLUGINS_VERSION_SCRIPT OR 
+      ENABLE_PLUGINS_VERSION_SCRIPT)
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map")
+  endif()
+
+  # Remove the "-rdynamic" option
+  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
+  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
+  link_libraries(uuid pthread rt)
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed")
+    add_definitions(
+      -D_LARGEFILE64_SOURCE=1 
+      -D_FILE_OFFSET_BITS=64
+      )
+    link_libraries(dl)
+  endif()
+
+  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
+  if (NOT HAVE_UUID_H)
+    message(FATAL_ERROR "Please install the uuid-dev package")
+  endif()
+
+elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  if (MSVC)
+    message("MSVC compiler version = " ${MSVC_VERSION} "\n")
+    # Starting Visual Studio 2013 (version 1800), it is not possible
+    # to target Windows XP anymore
+    if (MSVC_VERSION LESS 1800)
+      add_definitions(
+        -DWINVER=0x0501
+        -D_WIN32_WINNT=0x0501
+        )
+    endif()
+  else()
+    add_definitions(
+      -DWINVER=0x0501
+      -D_WIN32_WINNT=0x0501
+      )
+  endif()
+
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    )
+  link_libraries(rpcrt4 ws2_32)
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    # Some additional C/C++ compiler flags for MinGW
+    SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}")
+
+    # This is a patch for MinGW64
+    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+    SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+
+    CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD)
+    if (HAVE_WIN_PTHREAD)
+      # This line is necessary to compile with recent versions of MinGW,
+      # otherwise "libwinpthread-1.dll" is not statically linked.
+      SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
+      add_definitions(-DHAVE_WIN_PTHREAD=1)
+    else()
+      add_definitions(-DHAVE_WIN_PTHREAD=0)
+    endif()
+  endif()
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${ORTHANC_ROOT}/Plugins/Samples/Common/ExportedSymbols.list")
+
+  add_definitions(
+    -D_XOPEN_SOURCE=1
+    )
+  link_libraries(iconv)
+
+  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
+  if (NOT HAVE_UUID_H)
+    message(FATAL_ERROR "Please install the uuid-dev package")
+  endif()
+
+endif()
+
+
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}")
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+  # In FreeBSD, the "/usr/local/" folder contains the ports and need to be imported
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib")
+endif()
+
+
+if (STATIC_BUILD)
+  add_definitions(-DORTHANC_STATIC=1)
+else()
+  add_definitions(-DORTHANC_STATIC=0)
+endif()
diff --git a/Framework/Orthanc/Resources/CMake/DcmtkConfiguration.cmake b/Framework/Orthanc/Resources/CMake/DcmtkConfiguration.cmake
new file mode 100644
index 0000000..6d48ccb
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/DcmtkConfiguration.cmake
@@ -0,0 +1,289 @@
+if (NOT DEFINED ENABLE_DCMTK_NETWORKING)
+    set(ENABLE_DCMTK_NETWORKING ON)
+endif()
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
+  if (USE_DCMTK_361)
+    SET(DCMTK_VERSION_NUMBER 361)
+    SET(DCMTK_PACKAGE_VERSION "3.6.1")
+    SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.1_20160216)
+    SET(DCMTK_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.1_20160216.tar.gz")
+    SET(DCMTK_MD5 "273c8a544b9fe09b8a4fb4eb51df8e52")
+    SET(DCMTK_PATCH_SPEED "${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.1-speed.patch")
+
+    macro(DCMTK_UNSET)
+    endmacro()
+
+    set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/)
+    set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/)
+    add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1)
+  else()
+    SET(DCMTK_VERSION_NUMBER 360)
+    SET(DCMTK_PACKAGE_VERSION "3.6.0")
+    SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0)
+    SET(DCMTK_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip")
+    SET(DCMTK_MD5 "219ad631b82031806147e4abbfba4fa4")
+    SET(DCMTK_PATCH_SPEED "${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.0-speed.patch")
+    SET(DCMTK_PATCH_MINGW64 "${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.0-mingw64.patch")
+  endif()
+
+  if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
+  DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}")
+
+  IF (CMAKE_CROSSCOMPILING)
+    SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.")
+  ENDIF()
+  SET(DCMTK_SOURCE_DIR ${DCMTK_SOURCES_DIR})
+  include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake)
+  include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake)
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    set(HAVE_SSTREAM 1)
+    set(HAVE_PROTOTYPE_BZERO 1)
+    set(HAVE_PROTOTYPE_GETHOSTNAME 1)
+    set(HAVE_PROTOTYPE_GETSOCKOPT 1)
+    set(HAVE_PROTOTYPE_SETSOCKOPT 1)
+    set(HAVE_PROTOTYPE_CONNECT 1)
+    set(HAVE_PROTOTYPE_BIND 1)
+    set(HAVE_PROTOTYPE_ACCEPT 1)
+    set(HAVE_PROTOTYPE_SETSOCKNAME 1)
+    set(HAVE_PROTOTYPE_GETSOCKNAME 1)
+  endif()
+
+  set(DCMTK_PACKAGE_VERSION_SUFFIX "")
+  set(DCMTK_PACKAGE_VERSION_NUMBER ${DCMTK_VERSION_NUMBER})
+
+  CONFIGURE_FILE(
+    ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in
+    ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h)
+
+  if (USE_DCMTK_361)
+    # This step must be after the generation of "osconfig.h"
+    INSPECT_FUNDAMENTAL_ARITHMETIC_TYPES()
+  endif()
+
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES)
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES)
+
+  if (ENABLE_DCMTK_NETWORKING)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES)
+    include_directories(
+      ${DCMTK_SOURCES_DIR}/dcmnet/include
+      )
+  endif()
+
+  if (ENABLE_JPEG)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc DCMTK_SOURCES)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 DCMTK_SOURCES)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12 DCMTK_SOURCES)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg16 DCMTK_SOURCES)
+    include_directories(
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/include
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg16
+      ${DCMTK_SOURCES_DIR}/dcmimgle/include
+      )
+    list(REMOVE_ITEM DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/ddpiimpl.cc
+
+      # Disable support for encoding JPEG (modification in Orthanc 1.0.1)
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc
+      )
+  endif()
+
+
+  if (ENABLE_JPEG_LOSSLESS)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libsrc DCMTK_SOURCES)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libcharls DCMTK_SOURCES)
+    include_directories(
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/include
+      ${DCMTK_SOURCES_DIR}/dcmjpls/include
+      ${DCMTK_SOURCES_DIR}/dcmjpls/libcharls
+      )
+    list(REMOVE_ITEM DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc
+
+      # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1)
+      ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc
+      )
+    list(APPEND DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc
+      )
+  endif()
+
+
+  # Source for the logging facility of DCMTK
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES)
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
+    list(REMOVE_ITEM DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/oflog/libsrc/clfsap.cc
+      ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc
+      ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc
+      )
+    
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${DCMTK_PATCH_SPEED}
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
+
+    if (Failure AND FirstRun)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+
+  elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    list(REMOVE_ITEM DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc
+      )
+
+    if (CMAKE_COMPILER_IS_GNUCXX AND DCMTK_PATCH_MINGW64)
+      # This is a patch for MinGW64
+      execute_process(
+        COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${DCMTK_PATCH_MINGW64}
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        )
+
+      if (Failure AND FirstRun)
+        message(FATAL_ERROR "Error while patching a file")
+      endif()
+    endif()
+
+    # This patch improves speed, even for Windows
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N 
+      INPUT_FILE ${DCMTK_PATCH_SPEED}
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
+
+    if (Failure AND FirstRun)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+  endif()
+
+  list(REMOVE_ITEM DCMTK_SOURCES 
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc
+    )
+
+  #set_source_files_properties(${DCMTK_SOURCES}
+  #  PROPERTIES COMPILE_DEFINITIONS
+  #  "PACKAGE_VERSION=\"${DCMTK_PACKAGE_VERSION}\";PACKAGE_VERSION_NUMBER=\"${DCMTK_VERSION_NUMBER}\"")
+
+  # This fixes crashes related to the destruction of the DCMTK OFLogger
+  # http://support.dcmtk.org/docs-snapshot/file_macros.html
+  add_definitions(
+    -DLOG4CPLUS_DISABLE_FATAL=1
+    -DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}
+    )
+
+  include_directories(
+    #${DCMTK_SOURCES_DIR}
+    ${DCMTK_SOURCES_DIR}/config/include
+    ${DCMTK_SOURCES_DIR}/ofstd/include
+    ${DCMTK_SOURCES_DIR}/oflog/include
+    ${DCMTK_SOURCES_DIR}/dcmdata/include
+    )
+
+  source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*)
+
+  set(DCMTK_BUNDLES_LOG4CPLUS 1)
+
+  if (STANDALONE_BUILD)
+    set(DCMTK_USE_EMBEDDED_DICTIONARIES 1)
+    set(DCMTK_DICTIONARIES
+      DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic
+      DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic
+      DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic
+      )
+  else()
+    set(DCMTK_USE_EMBEDDED_DICTIONARIES 0)
+  endif()
+
+else()
+  # The following line allows to manually add libraries at the
+  # command-line, which is necessary for Ubuntu/Debian packages
+  set(tmp "${DCMTK_LIBRARIES}")
+  include(FindDCMTK)
+  list(APPEND DCMTK_LIBRARIES "${tmp}")
+
+  include_directories(${DCMTK_INCLUDE_DIRS})
+
+  add_definitions(
+    -DHAVE_CONFIG_H=1
+    )
+
+  if (EXISTS "${DCMTK_config_INCLUDE_DIR}/cfunix.h")
+    set(DCMTK_CONFIGURATION_FILE "${DCMTK_config_INCLUDE_DIR}/cfunix.h")
+  elseif (EXISTS "${DCMTK_config_INCLUDE_DIR}/osconfig.h")  # This is for Arch Linux
+    set(DCMTK_CONFIGURATION_FILE "${DCMTK_config_INCLUDE_DIR}/osconfig.h")
+  else()
+    message(FATAL_ERROR "Please install libdcmtk*-dev")
+  endif()
+
+  # Autodetection of the version of DCMTK
+  file(STRINGS
+    "${DCMTK_CONFIGURATION_FILE}" 
+    DCMTK_VERSION_NUMBER1 REGEX
+    ".*PACKAGE_VERSION .*")    
+
+  string(REGEX REPLACE
+    ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$"
+    "\\1\\2\\3" 
+    DCMTK_VERSION_NUMBER 
+    ${DCMTK_VERSION_NUMBER1})
+
+  set(DCMTK_USE_EMBEDDED_DICTIONARIES 0)
+endif()
+
+
+add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER})
+message("DCMTK version: ${DCMTK_VERSION_NUMBER}")
+
+
+add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=${DCMTK_USE_EMBEDDED_DICTIONARIES})
+if (NOT DCMTK_USE_EMBEDDED_DICTIONARIES)
+  # Lookup for DICOM dictionaries, if none is specified by the user
+  if (DCMTK_DICTIONARY_DIR STREQUAL "")
+    find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic
+      /usr/share/dcmtk
+      /usr/share/libdcmtk1
+      /usr/share/libdcmtk2
+      /usr/share/libdcmtk3
+      /usr/share/libdcmtk4
+      /usr/share/libdcmtk5
+      /usr/share/libdcmtk6
+      /usr/share/libdcmtk7
+      /usr/share/libdcmtk8
+      /usr/share/libdcmtk9
+      /usr/local/share/dcmtk
+      )
+
+    if (${DCMTK_DICTIONARY_DIR_AUTO} MATCHES "DCMTK_DICTIONARY_DIR_AUTO-NOTFOUND")
+      message(FATAL_ERROR "Cannot locate the DICOM dictionary on this system")
+    endif()
+
+    message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}")
+    add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}")
+  else()
+    add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}")
+  endif()
+endif()
diff --git a/Framework/Orthanc/Resources/CMake/DownloadPackage.cmake b/Framework/Orthanc/Resources/CMake/DownloadPackage.cmake
new file mode 100644
index 0000000..b529cef
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/DownloadPackage.cmake
@@ -0,0 +1,168 @@
+macro(GetUrlFilename TargetVariable Url)
+  string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}")
+endmacro()
+
+
+macro(GetUrlExtension TargetVariable Url)
+  #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}")
+  string(REGEX REPLACE "^.*\\." "" TMP "${Url}")
+  string(TOLOWER "${TMP}" "${TargetVariable}")
+endmacro()
+
+
+
+##
+## Setup the patch command-line tool
+##
+
+if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+  set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe)
+  if (NOT EXISTS ${PATCH_EXECUTABLE})
+    message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc")
+  endif()
+
+else ()
+  find_program(PATCH_EXECUTABLE patch)
+  if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'patch' standard command-line tool")
+  endif()
+endif()
+
+
+
+##
+## Check the existence of the required decompression tools
+##
+
+if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+  find_program(ZIP_EXECUTABLE 7z 
+    PATHS 
+    "$ENV{ProgramFiles}/7-Zip"
+    "$ENV{ProgramW6432}/7-Zip"
+    )
+
+  if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
+  endif()
+
+else()
+  find_program(UNZIP_EXECUTABLE unzip)
+  if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'unzip' package")
+  endif()
+
+  find_program(TAR_EXECUTABLE tar)
+  if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'tar' package")
+  endif()
+endif()
+
+
+macro(DownloadPackage MD5 Url TargetDirectory)
+  if (NOT IS_DIRECTORY "${TargetDirectory}")
+    GetUrlFilename(TMP_FILENAME "${Url}")
+
+    set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
+    if (NOT EXISTS "${TMP_PATH}")
+      message("Downloading ${Url}")
+
+      # This fixes issue 6: "I think cmake shouldn't download the
+      # packages which are not in the system, it should stop and let
+      # user know."
+      # https://code.google.com/p/orthanc/issues/detail?id=6
+      if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+	message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+      endif()
+
+      file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}")
+    else()
+      message("Using local copy of ${Url}")
+    endif()
+
+    GetUrlExtension(TMP_EXTENSION "${Url}")
+    #message(${TMP_EXTENSION})
+    message("Uncompressing ${TMP_FILENAME}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      if (("${TMP_EXTENSION}" STREQUAL "gz") OR 
+          ("${TMP_EXTENSION}" STREQUAL "tgz") OR
+          ("${TMP_EXTENSION}" STREQUAL "xz"))
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+
+        if (Failure)
+          message(FATAL_ERROR "Error while running the uncompression tool")
+        endif()
+
+        if ("${TMP_EXTENSION}" STREQUAL "tgz")
+          string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "gz")
+          string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+          string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        endif()
+
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      else()
+        message(FATAL_ERROR "Support your platform here")
+      endif()
+
+    else()
+      if ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+        )
+      elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
+        #message("tar xvfz ${TMP_PATH}")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "bz2")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      else()
+        message(FATAL_ERROR "Unknown package format.")
+      endif()
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT IS_DIRECTORY "${TargetDirectory}")
+      message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endmacro()
diff --git a/Framework/Orthanc/Resources/CMake/JsonCppConfiguration.cmake b/Framework/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
new file mode 100644
index 0000000..bad61b8
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
@@ -0,0 +1,60 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
+  set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.5)
+  set(JSONCPP_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-0.10.5.tar.gz")
+  set(JSONCPP_MD5 "db146bac5a126ded9bd728ab7b61ed6b")
+
+  DownloadPackage(${JSONCPP_MD5} ${JSONCPP_URL} "${JSONCPP_SOURCES_DIR}")
+
+  set(JSONCPP_SOURCES
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
+    )
+
+  include_directories(
+    ${JSONCPP_SOURCES_DIR}/include
+    )
+
+  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
+
+else()
+  find_path(JSONCPP_INCLUDE_DIR json/reader.h
+    /usr/include/jsoncpp
+    /usr/local/include/jsoncpp
+    )
+
+  message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
+  include_directories(${JSONCPP_INCLUDE_DIR})
+  link_libraries(jsoncpp)
+
+  CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
+
+  # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
+  if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
+    file(STRINGS
+      "${JSONCPP_INCLUDE_DIR}/json/version.h" 
+      JSONCPP_VERSION_MAJOR1 REGEX
+      ".*define JSONCPP_VERSION_MAJOR.*")
+
+    if (NOT JSONCPP_VERSION_MAJOR1)
+      message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
+    endif()
+    
+    string(REGEX REPLACE
+      ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
+      JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
+    message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
+
+    if (CMAKE_COMPILER_IS_GNUCXX AND 
+        JSONCPP_VERSION_MAJOR GREATER 0)
+      message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
+      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations")
+    endif()
+  else()
+    message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
+  endif()
+
+endif()
diff --git a/Framework/Orthanc/Resources/CMake/LibCurlConfiguration.cmake b/Framework/Orthanc/Resources/CMake/LibCurlConfiguration.cmake
new file mode 100644
index 0000000..1e164c9
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/LibCurlConfiguration.cmake
@@ -0,0 +1,122 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_CURL)
+  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.50.3)
+  SET(CURL_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.50.3.tar.gz")
+  SET(CURL_MD5 "870e16fd88a88b52e26a4f04dfc161db")
+
+  DownloadPackage(${CURL_MD5} ${CURL_URL} "${CURL_SOURCES_DIR}")
+
+  include_directories(
+    ${CURL_SOURCES_DIR}/include
+    )
+
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES)
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vauth CURL_SOURCES)
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vtls CURL_SOURCES)
+  source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*)
+
+  add_definitions(
+    -DBUILDING_LIBCURL=1
+    -DCURL_STATICLIB=1
+    -DCURL_DISABLE_LDAPS=1
+    -DCURL_DISABLE_LDAP=1
+    -DCURL_DISABLE_DICT=1
+    -DCURL_DISABLE_FILE=1
+    -DCURL_DISABLE_FTP=1
+    -DCURL_DISABLE_GOPHER=1
+    -DCURL_DISABLE_LDAP=1
+    -DCURL_DISABLE_LDAPS=1
+    -DCURL_DISABLE_POP3=1
+    #-DCURL_DISABLE_PROXY=1
+    -DCURL_DISABLE_RTSP=1
+    -DCURL_DISABLE_TELNET=1
+    -DCURL_DISABLE_TFTP=1
+    )
+
+  if (ENABLE_SSL)
+    add_definitions(
+      #-DHAVE_LIBSSL=1
+      -DUSE_OPENSSL=1
+      -DHAVE_OPENSSL_ENGINE_H=1
+      -DUSE_SSLEAY=1
+      )
+  endif()
+
+  if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/curl_config.h")
+    file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "")
+
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h "#include \"../vauth.h\"\n")
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/digest.h "#include \"../digest.h\"\n")
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/ntlm.h "#include \"../ntlm.h\"\n")
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vtls/vtls.h "#include \"../../vtls/vtls.h\"\n")
+
+    file(GLOB CURL_LIBS_HEADERS ${CURL_SOURCES_DIR}/lib/*.h)
+    foreach (header IN LISTS CURL_LIBS_HEADERS)
+      get_filename_component(filename ${header} NAME)
+      file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/${filename} "#include \"../${filename}\"\n")
+      file(WRITE ${CURL_SOURCES_DIR}/lib/vtls/${filename} "#include \"../${filename}\"\n")
+    endforeach()
+  endif()
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
+    if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+      SET(TMP_OS "x86_64")
+    else()
+      SET(TMP_OS "x86")
+    endif()
+
+    set_property(
+      SOURCE ${CURL_SOURCES}
+      PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;HAVE_LONGLONG;OS=\"${TMP_OS}\""
+      )
+
+    if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+      add_definitions(
+        -DRECV_TYPE_ARG1=int
+        -DRECV_TYPE_ARG2=void*
+        -DRECV_TYPE_ARG3=size_t
+        -DRECV_TYPE_ARG4=int
+        -DRECV_TYPE_RETV=ssize_t
+        -DSEND_TYPE_ARG1=int
+        -DSEND_TYPE_ARG2=void*
+        -DSEND_QUAL_ARG2=const
+        -DSEND_TYPE_ARG3=size_t
+        -DSEND_TYPE_ARG4=int
+        -DSEND_TYPE_RETV=ssize_t
+        -DSIZEOF_SHORT=2
+        -DSIZEOF_INT=4
+        -DSIZEOF_SIZE_T=8
+        )
+    elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
+      add_definitions(
+        -DRECV_TYPE_ARG1=int
+        -DRECV_TYPE_ARG2=void*
+        -DRECV_TYPE_ARG3=size_t
+        -DRECV_TYPE_ARG4=int
+        -DRECV_TYPE_RETV=int
+        -DSEND_TYPE_ARG1=int
+        -DSEND_TYPE_ARG2=void*
+        -DSEND_QUAL_ARG2=const
+        -DSEND_TYPE_ARG3=size_t
+        -DSEND_TYPE_ARG4=int
+        -DSEND_TYPE_RETV=int
+        -DSIZEOF_SHORT=2
+        -DSIZEOF_INT=4
+        -DSIZEOF_SIZE_T=4
+        )
+    else()
+      message(FATAL_ERROR "Support your platform here")
+    endif()
+  endif()
+
+else()
+  include(FindCURL)
+  include_directories(${CURL_INCLUDE_DIRS})
+  link_libraries(${CURL_LIBRARIES})
+
+  if (NOT ${CURL_FOUND})
+    message(FATAL_ERROR "Unable to find LibCurl")
+  endif()
+endif()
diff --git a/Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake b/Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake
new file mode 100644
index 0000000..22f8919
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake
@@ -0,0 +1,95 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG)
+  set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a)
+  DownloadPackage(
+    "3353992aecaee1805ef4109aadd433e7"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jpegsrc.v9a.tar.gz"
+    "${LIBJPEG_SOURCES_DIR}")
+
+  include_directories(
+    ${LIBJPEG_SOURCES_DIR}
+    )
+
+  list(APPEND LIBJPEG_SOURCES 
+    ${LIBJPEG_SOURCES_DIR}/jaricom.c
+    ${LIBJPEG_SOURCES_DIR}/jcapimin.c
+    ${LIBJPEG_SOURCES_DIR}/jcapistd.c
+    ${LIBJPEG_SOURCES_DIR}/jcarith.c
+    ${LIBJPEG_SOURCES_DIR}/jccoefct.c
+    ${LIBJPEG_SOURCES_DIR}/jccolor.c
+    ${LIBJPEG_SOURCES_DIR}/jcdctmgr.c
+    ${LIBJPEG_SOURCES_DIR}/jchuff.c
+    ${LIBJPEG_SOURCES_DIR}/jcinit.c
+    ${LIBJPEG_SOURCES_DIR}/jcmarker.c
+    ${LIBJPEG_SOURCES_DIR}/jcmaster.c
+    ${LIBJPEG_SOURCES_DIR}/jcomapi.c
+    ${LIBJPEG_SOURCES_DIR}/jcparam.c
+    ${LIBJPEG_SOURCES_DIR}/jcprepct.c
+    ${LIBJPEG_SOURCES_DIR}/jcsample.c
+    ${LIBJPEG_SOURCES_DIR}/jctrans.c
+    ${LIBJPEG_SOURCES_DIR}/jdapimin.c
+    ${LIBJPEG_SOURCES_DIR}/jdapistd.c
+    ${LIBJPEG_SOURCES_DIR}/jdarith.c
+    ${LIBJPEG_SOURCES_DIR}/jdatadst.c
+    ${LIBJPEG_SOURCES_DIR}/jdatasrc.c
+    ${LIBJPEG_SOURCES_DIR}/jdcoefct.c
+    ${LIBJPEG_SOURCES_DIR}/jdcolor.c
+    ${LIBJPEG_SOURCES_DIR}/jddctmgr.c
+    ${LIBJPEG_SOURCES_DIR}/jdhuff.c
+    ${LIBJPEG_SOURCES_DIR}/jdinput.c
+    ${LIBJPEG_SOURCES_DIR}/jcmainct.c
+    ${LIBJPEG_SOURCES_DIR}/jdmainct.c
+    ${LIBJPEG_SOURCES_DIR}/jdmarker.c
+    ${LIBJPEG_SOURCES_DIR}/jdmaster.c
+    ${LIBJPEG_SOURCES_DIR}/jdmerge.c
+    ${LIBJPEG_SOURCES_DIR}/jdpostct.c
+    ${LIBJPEG_SOURCES_DIR}/jdsample.c
+    ${LIBJPEG_SOURCES_DIR}/jdtrans.c
+    ${LIBJPEG_SOURCES_DIR}/jerror.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctflt.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctfst.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctint.c
+    ${LIBJPEG_SOURCES_DIR}/jidctflt.c
+    ${LIBJPEG_SOURCES_DIR}/jidctfst.c
+    ${LIBJPEG_SOURCES_DIR}/jidctint.c
+    #${LIBJPEG_SOURCES_DIR}/jmemansi.c
+    #${LIBJPEG_SOURCES_DIR}/jmemdos.c
+    #${LIBJPEG_SOURCES_DIR}/jmemmac.c
+    ${LIBJPEG_SOURCES_DIR}/jmemmgr.c
+    #${LIBJPEG_SOURCES_DIR}/jmemname.c
+    ${LIBJPEG_SOURCES_DIR}/jmemnobs.c
+    ${LIBJPEG_SOURCES_DIR}/jquant1.c
+    ${LIBJPEG_SOURCES_DIR}/jquant2.c
+    ${LIBJPEG_SOURCES_DIR}/jutils.c
+
+    # ${LIBJPEG_SOURCES_DIR}/rdbmp.c
+    # ${LIBJPEG_SOURCES_DIR}/rdcolmap.c
+    # ${LIBJPEG_SOURCES_DIR}/rdgif.c
+    # ${LIBJPEG_SOURCES_DIR}/rdppm.c
+    # ${LIBJPEG_SOURCES_DIR}/rdrle.c
+    # ${LIBJPEG_SOURCES_DIR}/rdswitch.c
+    # ${LIBJPEG_SOURCES_DIR}/rdtarga.c
+    # ${LIBJPEG_SOURCES_DIR}/transupp.c
+    # ${LIBJPEG_SOURCES_DIR}/wrbmp.c
+    # ${LIBJPEG_SOURCES_DIR}/wrgif.c
+    # ${LIBJPEG_SOURCES_DIR}/wrppm.c
+    # ${LIBJPEG_SOURCES_DIR}/wrrle.c
+    # ${LIBJPEG_SOURCES_DIR}/wrtarga.c
+    )
+
+  configure_file(
+    ${LIBJPEG_SOURCES_DIR}/jconfig.txt
+    ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY
+    )
+
+  source_group(ThirdParty\\libjpeg REGULAR_EXPRESSION ${LIBJPEG_SOURCES_DIR}/.*)
+
+else()
+  include(FindJPEG)
+
+  if (NOT ${JPEG_FOUND})
+    message(FATAL_ERROR "Unable to find libjpeg")
+  endif()
+
+  include_directories(${JPEG_INCLUDE_DIR})
+  link_libraries(${JPEG_LIBRARIES})
+endif()
diff --git a/Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake b/Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake
new file mode 100644
index 0000000..df09dd6
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake
@@ -0,0 +1,61 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG)
+  SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12)
+  SET(LIBPNG_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz")
+  SET(LIBPNG_MD5 "8ea7f60347a306c5faf70b977fa80e28")
+
+  DownloadPackage(${LIBPNG_MD5} ${LIBPNG_URL} "${LIBPNG_SOURCES_DIR}")
+
+  include_directories(
+    ${LIBPNG_SOURCES_DIR}
+    )
+
+  configure_file(
+    ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt
+    ${LIBPNG_SOURCES_DIR}/pnglibconf.h
+    )
+
+  set(LIBPNG_SOURCES
+    #${LIBPNG_SOURCES_DIR}/example.c
+    ${LIBPNG_SOURCES_DIR}/png.c
+    ${LIBPNG_SOURCES_DIR}/pngerror.c
+    ${LIBPNG_SOURCES_DIR}/pngget.c
+    ${LIBPNG_SOURCES_DIR}/pngmem.c
+    ${LIBPNG_SOURCES_DIR}/pngpread.c
+    ${LIBPNG_SOURCES_DIR}/pngread.c
+    ${LIBPNG_SOURCES_DIR}/pngrio.c
+    ${LIBPNG_SOURCES_DIR}/pngrtran.c
+    ${LIBPNG_SOURCES_DIR}/pngrutil.c
+    ${LIBPNG_SOURCES_DIR}/pngset.c
+    #${LIBPNG_SOURCES_DIR}/pngtest.c
+    ${LIBPNG_SOURCES_DIR}/pngtrans.c
+    ${LIBPNG_SOURCES_DIR}/pngwio.c
+    ${LIBPNG_SOURCES_DIR}/pngwrite.c
+    ${LIBPNG_SOURCES_DIR}/pngwtran.c
+    ${LIBPNG_SOURCES_DIR}/pngwutil.c
+    )
+
+  #set_property(
+  #  SOURCE ${LIBPNG_SOURCES}
+  #  PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H)
+
+  add_definitions(
+    -DPNG_NO_CONSOLE_IO=1
+    -DPNG_NO_STDIO=1
+    # The following declaration avoids "__declspec(dllexport)" in
+    # libpng to prevent publicly exposing its symbols by the DLLs
+    -DPNG_IMPEXP=
+    )
+
+  source_group(ThirdParty\\libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*)
+
+else()
+  include(FindPNG)
+
+  if (NOT ${PNG_FOUND})
+    message(FATAL_ERROR "Unable to find libpng")
+  endif()
+
+  include_directories(${PNG_INCLUDE_DIRS})
+  link_libraries(${PNG_LIBRARIES})
+  add_definitions(${PNG_DEFINITIONS})
+endif()
diff --git a/Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake b/Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake
new file mode 100644
index 0000000..43432b6
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake
@@ -0,0 +1,230 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL)
+  # WARNING - We had to repack the upstream ".tar.gz" file to a ZIP
+  # file, as the upstream distribution ships symbolic links that are
+  # not always properly handled when uncompressing on Windows.
+
+  SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2d)
+  SET(OPENSSL_URL "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.2d.zip")
+  SET(OPENSSL_MD5 "4b2ac15fc6db17f3dadc54482d3eee85")
+
+  if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
+  DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}")
+
+  add_definitions(
+    -DOPENSSL_THREADS
+    -DOPENSSL_IA32_SSE2
+    -DOPENSSL_NO_ASM
+    -DOPENSSL_NO_DYNAMIC_ENGINE
+    -DNO_WINDOWS_BRAINDEATH
+
+    -DOPENSSL_NO_BF 
+    -DOPENSSL_NO_CAMELLIA
+    -DOPENSSL_NO_CAST 
+    -DOPENSSL_NO_EC_NISTP_64_GCC_128
+    -DOPENSSL_NO_GMP
+    -DOPENSSL_NO_GOST
+    -DOPENSSL_NO_HW
+    -DOPENSSL_NO_JPAKE
+    -DOPENSSL_NO_IDEA
+    -DOPENSSL_NO_KRB5 
+    -DOPENSSL_NO_MD2 
+    -DOPENSSL_NO_MDC2 
+    -DOPENSSL_NO_MD4
+    -DOPENSSL_NO_RC2 
+    -DOPENSSL_NO_RC4 
+    -DOPENSSL_NO_RC5 
+    -DOPENSSL_NO_RFC3779
+    -DOPENSSL_NO_SCTP
+    -DOPENSSL_NO_STORE
+    -DOPENSSL_NO_SEED
+    -DOPENSSL_NO_WHIRLPOOL
+    -DOPENSSL_NO_RIPEMD
+    )
+
+  include_directories(
+    ${OPENSSL_SOURCES_DIR}
+    ${OPENSSL_SOURCES_DIR}/crypto
+    ${OPENSSL_SOURCES_DIR}/crypto/asn1
+    ${OPENSSL_SOURCES_DIR}/crypto/modes
+    ${OPENSSL_SOURCES_DIR}/crypto/evp
+    ${OPENSSL_SOURCES_DIR}/include
+    )
+
+  set(OPENSSL_SOURCES_SUBDIRS
+    ${OPENSSL_SOURCES_DIR}/crypto
+    ${OPENSSL_SOURCES_DIR}/crypto/aes
+    ${OPENSSL_SOURCES_DIR}/crypto/asn1
+    ${OPENSSL_SOURCES_DIR}/crypto/bio
+    ${OPENSSL_SOURCES_DIR}/crypto/bn
+    ${OPENSSL_SOURCES_DIR}/crypto/buffer
+    ${OPENSSL_SOURCES_DIR}/crypto/cmac
+    ${OPENSSL_SOURCES_DIR}/crypto/cms
+    ${OPENSSL_SOURCES_DIR}/crypto/comp
+    ${OPENSSL_SOURCES_DIR}/crypto/conf
+    ${OPENSSL_SOURCES_DIR}/crypto/des
+    ${OPENSSL_SOURCES_DIR}/crypto/dh
+    ${OPENSSL_SOURCES_DIR}/crypto/dsa
+    ${OPENSSL_SOURCES_DIR}/crypto/dso
+    ${OPENSSL_SOURCES_DIR}/crypto/engine
+    ${OPENSSL_SOURCES_DIR}/crypto/err
+    ${OPENSSL_SOURCES_DIR}/crypto/evp
+    ${OPENSSL_SOURCES_DIR}/crypto/hmac
+    ${OPENSSL_SOURCES_DIR}/crypto/lhash
+    ${OPENSSL_SOURCES_DIR}/crypto/md5
+    ${OPENSSL_SOURCES_DIR}/crypto/modes
+    ${OPENSSL_SOURCES_DIR}/crypto/objects
+    ${OPENSSL_SOURCES_DIR}/crypto/ocsp
+    ${OPENSSL_SOURCES_DIR}/crypto/pem
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs12
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7
+    ${OPENSSL_SOURCES_DIR}/crypto/pqueue
+    ${OPENSSL_SOURCES_DIR}/crypto/rand
+    ${OPENSSL_SOURCES_DIR}/crypto/rsa
+    ${OPENSSL_SOURCES_DIR}/crypto/sha
+    ${OPENSSL_SOURCES_DIR}/crypto/srp
+    ${OPENSSL_SOURCES_DIR}/crypto/stack
+    ${OPENSSL_SOURCES_DIR}/crypto/ts
+    ${OPENSSL_SOURCES_DIR}/crypto/txt_db
+    ${OPENSSL_SOURCES_DIR}/crypto/ui
+    ${OPENSSL_SOURCES_DIR}/crypto/x509
+    ${OPENSSL_SOURCES_DIR}/crypto/x509v3
+    ${OPENSSL_SOURCES_DIR}/ssl
+    )
+
+  if (ENABLE_PKCS11)
+    list(APPEND OPENSSL_SOURCES_SUBDIRS
+      # EC, ECDH and ECDSA are necessary for PKCS11
+      ${OPENSSL_SOURCES_DIR}/crypto/ec
+      ${OPENSSL_SOURCES_DIR}/crypto/ecdh
+      ${OPENSSL_SOURCES_DIR}/crypto/ecdsa
+      )
+  else()
+    add_definitions(
+      -DOPENSSL_NO_EC
+      -DOPENSSL_NO_ECDH
+      -DOPENSSL_NO_ECDSA
+      )
+  endif()
+
+  foreach(d ${OPENSSL_SOURCES_SUBDIRS})
+    AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES)
+  endforeach()
+
+  list(REMOVE_ITEM OPENSSL_SOURCES
+    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c
+    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c
+    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c
+    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c
+    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c
+    ${OPENSSL_SOURCES_DIR}/crypto/armcap.c
+    ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp
+    ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c
+    ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c
+    ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c
+    ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/des/des.c
+    ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp
+    ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c
+    ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp
+    ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c
+    ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c
+    ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c
+    ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c
+    ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c
+    ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c
+    ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c
+    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c
+    ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c
+    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c
+    ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c
+    ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c
+    ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c
+    ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c
+    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c
+    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c
+    ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c
+    ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c
+
+    ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c
+    ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c
+    ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c
+    ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c
+    ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c
+    ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c
+    ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c
+
+    ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_extra_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/evp/verify_extra_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/x509/verify_extra_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3prin.c
+    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3nametest.c
+    ${OPENSSL_SOURCES_DIR}/crypto/constant_time_test.c
+    ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c
+
+    ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.c
+    )
+
+
+  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+    set_source_files_properties(
+      ${OPENSSL_SOURCES}
+      PROPERTIES COMPILE_DEFINITIONS
+      "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN")
+
+  elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -N ui_openssl.c -i ${ORTHANC_ROOT}/Resources/Patches/openssl-lsb.diff
+      WORKING_DIRECTORY ${OPENSSL_SOURCES_DIR}/crypto/ui
+      RESULT_VARIABLE Failure
+      )
+
+    if (Failure AND FirstRun)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+  endif()
+
+  source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*)
+
+else()
+  include(FindOpenSSL)
+
+  if (NOT ${OPENSSL_FOUND})
+    message(FATAL_ERROR "Unable to find OpenSSL")
+  endif()
+
+  include_directories(${OPENSSL_INCLUDE_DIR})
+  link_libraries(${OPENSSL_LIBRARIES})
+endif()
diff --git a/Framework/Orthanc/Resources/CMake/VisualStudioPrecompiledHeaders.cmake b/Framework/Orthanc/Resources/CMake/VisualStudioPrecompiledHeaders.cmake
new file mode 100644
index 0000000..08e59a6
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/VisualStudioPrecompiledHeaders.cmake
@@ -0,0 +1,14 @@
+macro(ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS PrecompiledHeaders PrecompiledSource Sources)
+  get_filename_component(PrecompiledBasename ${PrecompiledHeaders} NAME_WE)
+  set(PrecompiledBinary "${PrecompiledBasename}_$(ConfigurationName).pch")
+
+  set_source_files_properties(${PrecompiledSource}
+    PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeaders}\" /Fp\"${PrecompiledBinary}\""
+    OBJECT_OUTPUTS "${PrecompiledBinary}")
+
+  set_source_files_properties(${${Sources}}
+    PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledHeaders}\" /FI\"${PrecompiledHeaders}\" /Fp\"${PrecompiledBinary}\""
+    OBJECT_DEPENDS "${PrecompiledBinary}")
+
+  list(APPEND ${Sources} ${PrecompiledSource})
+endmacro()
diff --git a/Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake b/Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake
new file mode 100644
index 0000000..29ae103
--- /dev/null
+++ b/Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake
@@ -0,0 +1,36 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
+  SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
+  SET(ZLIB_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz")
+  SET(ZLIB_MD5 "60df6a37c56e7c1366cca812414f7b85")
+
+  DownloadPackage(${ZLIB_MD5} ${ZLIB_URL} "${ZLIB_SOURCES_DIR}")
+
+  include_directories(
+    ${ZLIB_SOURCES_DIR}
+    )
+
+  list(APPEND ZLIB_SOURCES 
+    ${ZLIB_SOURCES_DIR}/adler32.c
+    ${ZLIB_SOURCES_DIR}/compress.c
+    ${ZLIB_SOURCES_DIR}/crc32.c 
+    ${ZLIB_SOURCES_DIR}/deflate.c 
+    ${ZLIB_SOURCES_DIR}/gzclose.c 
+    ${ZLIB_SOURCES_DIR}/gzlib.c 
+    ${ZLIB_SOURCES_DIR}/gzread.c 
+    ${ZLIB_SOURCES_DIR}/gzwrite.c 
+    ${ZLIB_SOURCES_DIR}/infback.c 
+    ${ZLIB_SOURCES_DIR}/inffast.c 
+    ${ZLIB_SOURCES_DIR}/inflate.c 
+    ${ZLIB_SOURCES_DIR}/inftrees.c 
+    ${ZLIB_SOURCES_DIR}/trees.c 
+    ${ZLIB_SOURCES_DIR}/uncompr.c 
+    ${ZLIB_SOURCES_DIR}/zutil.c
+    )
+
+  source_group(ThirdParty\\zlib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
+
+else()
+  include(FindZLIB)
+  include_directories(${ZLIB_INCLUDE_DIRS})
+  link_libraries(${ZLIB_LIBRARIES})
+endif()
diff --git a/Framework/Orthanc/Resources/EmbedResources.py b/Framework/Orthanc/Resources/EmbedResources.py
new file mode 100755
index 0000000..d914d15
--- /dev/null
+++ b/Framework/Orthanc/Resources/EmbedResources.py
@@ -0,0 +1,426 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# In addition, as a special exception, the copyright holders of this
+# program give permission to link the code of its release with the
+# OpenSSL project's "OpenSSL" library (or with modified versions of it
+# that use the same license as the "OpenSSL" library), and distribute
+# the linked executables. You must obey the GNU General Public License
+# in all respects for all of the code used other than "OpenSSL". If you
+# modify file(s) with this exception, you may extend this exception to
+# your version of the file(s), but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from your
+# version. If you delete this exception statement from all source files
+# in the program, then also delete it here.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import sys
+import os
+import os.path
+import pprint
+import re
+
+UPCASE_CHECK = True
+USE_SYSTEM_EXCEPTION = False
+EXCEPTION_CLASS = 'OrthancException'
+OUT_OF_RANGE_EXCEPTION = 'OrthancException(ErrorCode_ParameterOutOfRange)'
+INEXISTENT_PATH_EXCEPTION = 'OrthancException(ErrorCode_InexistentItem)'
+NAMESPACE = 'Orthanc'
+
+ARGS = []
+for i in range(len(sys.argv)):
+    if not sys.argv[i].startswith('--'):
+        ARGS.append(sys.argv[i])
+    elif sys.argv[i].lower() == '--no-upcase-check':
+        UPCASE_CHECK = False
+    elif sys.argv[i].lower() == '--system-exception':
+        USE_SYSTEM_EXCEPTION = True
+        EXCEPTION_CLASS = '::std::runtime_error'
+        OUT_OF_RANGE_EXCEPTION = '%s("Parameter out of range")' % EXCEPTION_CLASS
+        INEXISTENT_PATH_EXCEPTION = '%s("Unknown path in a directory resource")' % EXCEPTION_CLASS
+    elif sys.argv[i].startswith('--namespace='):
+        NAMESPACE = sys.argv[i][sys.argv[i].find('=') + 1 : ]
+
+if len(ARGS) < 2 or len(ARGS) % 2 != 0:
+    print ('Usage:')
+    print ('python %s [--no-upcase-check] [--system-exception] [--namespace=<Namespace>] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0])
+    exit(-1)
+
+TARGET_BASE_FILENAME = ARGS[1]
+SOURCES = ARGS[2:]
+
+try:
+    # Make sure the destination directory exists
+    os.makedirs(os.path.normpath(os.path.join(TARGET_BASE_FILENAME, '..')))
+except:
+    pass
+
+
+#####################################################################
+## Read each resource file
+#####################################################################
+
+def CheckNoUpcase(s):
+    global UPCASE_CHECK
+    if (UPCASE_CHECK and
+        re.search('[A-Z]', s) != None):
+        raise Exception("Path in a directory with an upcase letter: %s" % s)
+
+resources = {}
+
+counter = 0
+i = 0
+while i < len(SOURCES):
+    resourceName = SOURCES[i].upper()
+    pathName = SOURCES[i + 1]
+
+    if not os.path.exists(pathName):
+        raise Exception("Non existing path: %s" % pathName)
+
+    if resourceName in resources:
+        raise Exception("Twice the same resource: " + resourceName)
+    
+    if os.path.isdir(pathName):
+        # The resource is a directory: Recursively explore its files
+        content = {}
+        for root, dirs, files in os.walk(pathName):
+            base = os.path.relpath(root, pathName)
+
+            # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
+            # Ignore folders whose name starts with a dot (".")
+            if base.find('/.') != -1:
+                print('Ignoring folder: %s' % root)
+                continue
+
+            for f in files:
+                if f.find('~') == -1:  # Ignore Emacs backup files
+                    if base == '.':
+                        r = f
+                    else:
+                        r = os.path.join(base, f)
+
+                    CheckNoUpcase(r)
+                    r = '/' + r.replace('\\', '/')
+                    if r in content:
+                        raise Exception("Twice the same filename (check case): " + r)
+
+                    content[r] = {
+                        'Filename' : os.path.join(root, f),
+                        'Index' : counter
+                        }
+                    counter += 1
+
+        resources[resourceName] = {
+            'Type' : 'Directory',
+            'Files' : content
+            }
+
+    elif os.path.isfile(pathName):
+        resources[resourceName] = {
+            'Type' : 'File',
+            'Index' : counter,
+            'Filename' : pathName
+            }
+        counter += 1
+
+    else:
+        raise Exception("Not a regular file, nor a directory: " + pathName)
+
+    i += 2
+
+#pprint.pprint(resources)
+
+
+#####################################################################
+## Write .h header
+#####################################################################
+
+header = open(TARGET_BASE_FILENAME + '.h', 'w')
+
+header.write("""
+#pragma once
+
+#include <string>
+#include <list>
+
+namespace %s
+{
+  namespace EmbeddedResources
+  {
+    enum FileResourceId
+    {
+""" % NAMESPACE)
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    enum DirectoryResourceId
+    {
+""")
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    const void* GetFileResourceBuffer(FileResourceId id);
+    size_t GetFileResourceSize(FileResourceId id);
+    void GetFileResource(std::string& result, FileResourceId id);
+
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path);
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path);
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path);
+
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id);
+  }
+}
+""")
+header.close()
+
+
+
+#####################################################################
+## Write the resource content in the .cpp source
+#####################################################################
+
+PYTHON_MAJOR_VERSION = sys.version_info[0]
+
+def WriteResource(cpp, item):
+    cpp.write('    static const uint8_t resource%dBuffer[] = {' % item['Index'])
+
+    f = open(item['Filename'], "rb")
+    content = f.read()
+    f.close()
+
+    # http://stackoverflow.com/a/1035360
+    pos = 0
+    for b in content:
+        if PYTHON_MAJOR_VERSION == 2:
+            c = ord(b[0])
+        else:
+            c = b
+
+        if pos > 0:
+            cpp.write(', ')
+
+        if (pos % 16) == 0:
+            cpp.write('\n    ')
+
+        if c < 0:
+            raise Exception("Internal error")
+
+        cpp.write("0x%02x" % c)
+        pos += 1
+
+    # Zero-size array are disallowed, so we put one single void character in it.
+    if pos == 0:
+        cpp.write('  0')
+
+    cpp.write('  };\n')
+    cpp.write('    static const size_t resource%dSize = %d;\n' % (item['Index'], pos))
+
+
+cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w')
+
+cpp.write('#include "%s.h"\n' % os.path.basename(TARGET_BASE_FILENAME))
+
+if USE_SYSTEM_EXCEPTION:
+    cpp.write('#include <stdexcept>')
+else:
+    cpp.write('#include "%s/Core/OrthancException.h"' % os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+cpp.write("""
+#include <stdint.h>
+#include <string.h>
+
+namespace %s
+{
+  namespace EmbeddedResources
+  {
+""" % NAMESPACE)
+
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        WriteResource(cpp, resources[name])
+    else:
+        for f in resources[name]['Files']:
+            WriteResource(cpp, resources[name]['Files'][f])
+
+
+
+#####################################################################
+## Write the accessors to the file resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetFileResourceBuffer(FileResourceId id)
+    {
+      switch (id)
+      {
+""")
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dBuffer;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+
+    size_t GetFileResourceSize(FileResourceId id)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dSize;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+#####################################################################
+## Write the accessors to the directory resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dBuffer;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dSize;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## List the resources in a directory
+#####################################################################
+
+cpp.write("""
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id)
+    {
+      result.clear();
+
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        for path in sorted(resources[name]['Files']):
+            cpp.write('        result.push_back("%s");\n' % path)
+        cpp.write('        break;\n\n')
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## Write the convenience wrappers in .cpp
+#####################################################################
+
+cpp.write("""
+    void GetFileResource(std::string& result, FileResourceId id)
+    {
+      size_t size = GetFileResourceSize(id);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetFileResourceBuffer(id), size);
+    }
+
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path)
+    {
+      size_t size = GetDirectoryResourceSize(id, path);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetDirectoryResourceBuffer(id, path), size);
+    }
+  }
+}
+""")
+cpp.close()
diff --git a/Framework/Orthanc/Resources/MinGW-W64-Toolchain32.cmake b/Framework/Orthanc/Resources/MinGW-W64-Toolchain32.cmake
new file mode 100644
index 0000000..043df32
--- /dev/null
+++ b/Framework/Orthanc/Resources/MinGW-W64-Toolchain32.cmake
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/Framework/Orthanc/Resources/MinGW-W64-Toolchain64.cmake b/Framework/Orthanc/Resources/MinGW-W64-Toolchain64.cmake
new file mode 100644
index 0000000..4dee010
--- /dev/null
+++ b/Framework/Orthanc/Resources/MinGW-W64-Toolchain64.cmake
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/Framework/Orthanc/Resources/MinGWToolchain.cmake b/Framework/Orthanc/Resources/MinGWToolchain.cmake
new file mode 100644
index 0000000..b120978
--- /dev/null
+++ b/Framework/Orthanc/Resources/MinGWToolchain.cmake
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
+set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
+set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-mingw64.patch b/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-mingw64.patch
new file mode 100644
index 0000000..3bfd752
--- /dev/null
+++ b/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-mingw64.patch
@@ -0,0 +1,22 @@
+diff -urEb dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h
+--- dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h	2010-12-17 11:50:30.000000000 +0100
++++ dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h	2013-07-19 15:56:25.688996134 +0200
+@@ -196,7 +196,7 @@
+   OFBool popen(const char *command, const char *modes)
+   {
+     if (file_) fclose();
+-#ifdef _WIN32
++#if 0
+     file_ = _popen(command, modes);
+ #else
+     file_ = :: popen(command, modes);
+@@ -258,7 +258,7 @@
+     {
+       if (popened_)
+       {
+-#ifdef _WIN32
++#if 0
+         result = _pclose(file_);
+ #else
+         result = :: pclose(file_);
+Only in dcmtk-3.6.0/ofstd/include/dcmtk/ofstd: offile.h~
diff --git a/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-speed.patch b/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-speed.patch
new file mode 100644
index 0000000..185dfe9
--- /dev/null
+++ b/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-speed.patch
@@ -0,0 +1,44 @@
+diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc dcmtk-3.6.0/dcmnet/libsrc/dul.cc
+--- dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc	2016-04-05 14:30:18.254459281 +0200
++++ dcmtk-3.6.0/dcmnet/libsrc/dul.cc	2016-04-05 14:32:07.246463713 +0200
+@@ -1770,7 +1770,7 @@
+                 // send number of socket handle in child process over anonymous pipe
+                 DWORD bytesWritten;
+                 char buf[20];
+-                sprintf(buf, "%i", OFreinterpret_cast(int, childSocketHandle));
++                sprintf(buf, "%i", OFstatic_cast(int, OFreinterpret_cast(size_t, childSocketHandle)));
+                 if (!WriteFile(hChildStdInWriteDup, buf, strlen(buf) + 1, &bytesWritten, NULL))
+                 {
+                     CloseHandle(hChildStdInWriteDup);
+@@ -1780,7 +1780,7 @@
+                 // return OF_ok status code DULC_FORKEDCHILD with descriptive text
+                 OFOStringStream stream;
+                 stream << "New child process started with pid " << OFstatic_cast(int, pi.dwProcessId)
+-                       << ", socketHandle " << OFreinterpret_cast(int, childSocketHandle) << OFStringStream_ends;
++                       << ", socketHandle " << OFstatic_cast(int, OFreinterpret_cast(size_t, childSocketHandle)) << OFStringStream_ends;
+                 OFSTRINGSTREAM_GETOFSTRING(stream, msg)
+                 return makeDcmnetCondition(DULC_FORKEDCHILD, OF_ok, msg.c_str());
+             }
+@@ -1840,7 +1840,7 @@
+     }
+ #endif
+ #endif
+-    setTCPBufferLength(sock);
++    //setTCPBufferLength(sock);
+ 
+ #ifndef DONT_DISABLE_NAGLE_ALGORITHM
+     /*
+Only in dcmtk-3.6.0/dcmnet/libsrc: dul.cc~
+diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc
+--- dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc	2016-04-05 14:30:18.250459281 +0200
++++ dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc	2016-04-05 14:32:20.566464254 +0200
+@@ -2417,7 +2417,7 @@
+           return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str());
+         }
+ #endif
+-        setTCPBufferLength(s);
++        //setTCPBufferLength(s);
+ 
+ #ifndef DONT_DISABLE_NAGLE_ALGORITHM
+         /*
+Only in dcmtk-3.6.0/dcmnet/libsrc: dulfsm.cc~
diff --git a/Framework/Orthanc/Resources/Patches/dcmtk-3.6.1-speed.patch b/Framework/Orthanc/Resources/Patches/dcmtk-3.6.1-speed.patch
new file mode 100644
index 0000000..97b4a25
--- /dev/null
+++ b/Framework/Orthanc/Resources/Patches/dcmtk-3.6.1-speed.patch
@@ -0,0 +1,26 @@
+diff -urEb dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dul.cc dcmtk-3.6.1_20160216/dcmnet/libsrc/dul.cc
+--- dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dul.cc	2016-04-05 12:56:28.962230391 +0200
++++ dcmtk-3.6.1_20160216/dcmnet/libsrc/dul.cc	2016-04-05 12:57:15.814232296 +0200
+@@ -1841,7 +1841,7 @@
+         return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str());
+     }
+ #endif
+-    setTCPBufferLength(sock);
++    //setTCPBufferLength(sock);
+ 
+ #ifndef DONT_DISABLE_NAGLE_ALGORITHM
+     /*
+Only in dcmtk-3.6.1_20160216/dcmnet/libsrc: dul.cc~
+diff -urEb dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dulfsm.cc dcmtk-3.6.1_20160216/dcmnet/libsrc/dulfsm.cc
+--- dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dulfsm.cc	2016-04-05 12:56:28.962230391 +0200
++++ dcmtk-3.6.1_20160216/dcmnet/libsrc/dulfsm.cc	2016-04-05 12:57:31.946232952 +0200
+@@ -2431,7 +2431,7 @@
+           return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str());
+         }
+ #endif
+-        setTCPBufferLength(s);
++        //setTCPBufferLength(s);
+ 
+ #ifndef DONT_DISABLE_NAGLE_ALGORITHM
+         /*
+Only in dcmtk-3.6.1_20160216/dcmnet/libsrc: dulfsm.cc~
diff --git a/Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h b/Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
new file mode 100644
index 0000000..4fe0ef9
--- /dev/null
+++ b/Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
@@ -0,0 +1,259 @@
+// ISO C9x  compliant stdint.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
+// 
+//  Copyright (c) 2006-2013 Alexander Chemeris
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 
+//   1. Redistributions of source code must retain the above copyright notice,
+//      this list of conditions and the following disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above copyright
+//      notice, this list of conditions and the following disclaimer in the
+//      documentation and/or other materials provided with the distribution.
+// 
+//   3. Neither the name of the product nor the names of its contributors may
+//      be used to endorse or promote products derived from this software
+//      without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_STDINT_H_ // [
+#define _MSC_STDINT_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#if _MSC_VER >= 1600 // [
+#include <stdint.h>
+#else // ] _MSC_VER >= 1600 [
+
+#include <limits.h>
+
+// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
+// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
+// or compiler give many errors like this:
+//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
+#ifdef __cplusplus
+extern "C" {
+#endif
+#  include <wchar.h>
+#ifdef __cplusplus
+}
+#endif
+
+// Define _W64 macros to mark types changing their size, like intptr_t.
+#ifndef _W64
+#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+#     define _W64 __w64
+#  else
+#     define _W64
+#  endif
+#endif
+
+
+// 7.18.1 Integer types
+
+// 7.18.1.1 Exact-width integer types
+
+// Visual Studio 6 and Embedded Visual C++ 4 doesn't
+// realize that, e.g. char has the same size as __int8
+// so we give up on __intX for them.
+#if (_MSC_VER < 1300)
+   typedef signed char       int8_t;
+   typedef signed short      int16_t;
+   typedef signed int        int32_t;
+   typedef unsigned char     uint8_t;
+   typedef unsigned short    uint16_t;
+   typedef unsigned int      uint32_t;
+#else
+   typedef signed __int8     int8_t;
+   typedef signed __int16    int16_t;
+   typedef signed __int32    int32_t;
+   typedef unsigned __int8   uint8_t;
+   typedef unsigned __int16  uint16_t;
+   typedef unsigned __int32  uint32_t;
+#endif
+typedef signed __int64       int64_t;
+typedef unsigned __int64     uint64_t;
+
+
+// 7.18.1.2 Minimum-width integer types
+typedef int8_t    int_least8_t;
+typedef int16_t   int_least16_t;
+typedef int32_t   int_least32_t;
+typedef int64_t   int_least64_t;
+typedef uint8_t   uint_least8_t;
+typedef uint16_t  uint_least16_t;
+typedef uint32_t  uint_least32_t;
+typedef uint64_t  uint_least64_t;
+
+// 7.18.1.3 Fastest minimum-width integer types
+typedef int8_t    int_fast8_t;
+typedef int16_t   int_fast16_t;
+typedef int32_t   int_fast32_t;
+typedef int64_t   int_fast64_t;
+typedef uint8_t   uint_fast8_t;
+typedef uint16_t  uint_fast16_t;
+typedef uint32_t  uint_fast32_t;
+typedef uint64_t  uint_fast64_t;
+
+// 7.18.1.4 Integer types capable of holding object pointers
+#ifdef _WIN64 // [
+   typedef signed __int64    intptr_t;
+   typedef unsigned __int64  uintptr_t;
+#else // _WIN64 ][
+   typedef _W64 signed int   intptr_t;
+   typedef _W64 unsigned int uintptr_t;
+#endif // _WIN64 ]
+
+// 7.18.1.5 Greatest-width integer types
+typedef int64_t   intmax_t;
+typedef uint64_t  uintmax_t;
+
+
+// 7.18.2 Limits of specified-width integer types
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
+
+// 7.18.2.1 Limits of exact-width integer types
+#define INT8_MIN     ((int8_t)_I8_MIN)
+#define INT8_MAX     _I8_MAX
+#define INT16_MIN    ((int16_t)_I16_MIN)
+#define INT16_MAX    _I16_MAX
+#define INT32_MIN    ((int32_t)_I32_MIN)
+#define INT32_MAX    _I32_MAX
+#define INT64_MIN    ((int64_t)_I64_MIN)
+#define INT64_MAX    _I64_MAX
+#define UINT8_MAX    _UI8_MAX
+#define UINT16_MAX   _UI16_MAX
+#define UINT32_MAX   _UI32_MAX
+#define UINT64_MAX   _UI64_MAX
+
+// 7.18.2.2 Limits of minimum-width integer types
+#define INT_LEAST8_MIN    INT8_MIN
+#define INT_LEAST8_MAX    INT8_MAX
+#define INT_LEAST16_MIN   INT16_MIN
+#define INT_LEAST16_MAX   INT16_MAX
+#define INT_LEAST32_MIN   INT32_MIN
+#define INT_LEAST32_MAX   INT32_MAX
+#define INT_LEAST64_MIN   INT64_MIN
+#define INT_LEAST64_MAX   INT64_MAX
+#define UINT_LEAST8_MAX   UINT8_MAX
+#define UINT_LEAST16_MAX  UINT16_MAX
+#define UINT_LEAST32_MAX  UINT32_MAX
+#define UINT_LEAST64_MAX  UINT64_MAX
+
+// 7.18.2.3 Limits of fastest minimum-width integer types
+#define INT_FAST8_MIN    INT8_MIN
+#define INT_FAST8_MAX    INT8_MAX
+#define INT_FAST16_MIN   INT16_MIN
+#define INT_FAST16_MAX   INT16_MAX
+#define INT_FAST32_MIN   INT32_MIN
+#define INT_FAST32_MAX   INT32_MAX
+#define INT_FAST64_MIN   INT64_MIN
+#define INT_FAST64_MAX   INT64_MAX
+#define UINT_FAST8_MAX   UINT8_MAX
+#define UINT_FAST16_MAX  UINT16_MAX
+#define UINT_FAST32_MAX  UINT32_MAX
+#define UINT_FAST64_MAX  UINT64_MAX
+
+// 7.18.2.4 Limits of integer types capable of holding object pointers
+#ifdef _WIN64 // [
+#  define INTPTR_MIN   INT64_MIN
+#  define INTPTR_MAX   INT64_MAX
+#  define UINTPTR_MAX  UINT64_MAX
+#else // _WIN64 ][
+#  define INTPTR_MIN   INT32_MIN
+#  define INTPTR_MAX   INT32_MAX
+#  define UINTPTR_MAX  UINT32_MAX
+#endif // _WIN64 ]
+
+// 7.18.2.5 Limits of greatest-width integer types
+#define INTMAX_MIN   INT64_MIN
+#define INTMAX_MAX   INT64_MAX
+#define UINTMAX_MAX  UINT64_MAX
+
+// 7.18.3 Limits of other integer types
+
+#ifdef _WIN64 // [
+#  define PTRDIFF_MIN  _I64_MIN
+#  define PTRDIFF_MAX  _I64_MAX
+#else  // _WIN64 ][
+#  define PTRDIFF_MIN  _I32_MIN
+#  define PTRDIFF_MAX  _I32_MAX
+#endif  // _WIN64 ]
+
+#define SIG_ATOMIC_MIN  INT_MIN
+#define SIG_ATOMIC_MAX  INT_MAX
+
+#ifndef SIZE_MAX // [
+#  ifdef _WIN64 // [
+#     define SIZE_MAX  _UI64_MAX
+#  else // _WIN64 ][
+#     define SIZE_MAX  _UI32_MAX
+#  endif // _WIN64 ]
+#endif // SIZE_MAX ]
+
+// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
+#ifndef WCHAR_MIN // [
+#  define WCHAR_MIN  0
+#endif  // WCHAR_MIN ]
+#ifndef WCHAR_MAX // [
+#  define WCHAR_MAX  _UI16_MAX
+#endif  // WCHAR_MAX ]
+
+#define WINT_MIN  0
+#define WINT_MAX  _UI16_MAX
+
+#endif // __STDC_LIMIT_MACROS ]
+
+
+// 7.18.4 Limits of other integer types
+
+#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
+
+// 7.18.4.1 Macros for minimum-width integer constants
+
+#define INT8_C(val)  val##i8
+#define INT16_C(val) val##i16
+#define INT32_C(val) val##i32
+#define INT64_C(val) val##i64
+
+#define UINT8_C(val)  val##ui8
+#define UINT16_C(val) val##ui16
+#define UINT32_C(val) val##ui32
+#define UINT64_C(val) val##ui64
+
+// 7.18.4.2 Macros for greatest-width integer constants
+// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>.
+// Check out Issue 9 for the details.
+#ifndef INTMAX_C //   [
+#  define INTMAX_C   INT64_C
+#endif // INTMAX_C    ]
+#ifndef UINTMAX_C //  [
+#  define UINTMAX_C  UINT64_C
+#endif // UINTMAX_C   ]
+
+#endif // __STDC_CONSTANT_MACROS ]
+
+#endif // _MSC_VER >= 1600 ]
+
+#endif // _MSC_STDINT_H_ ]
diff --git a/Framework/Orthanc/Resources/ThirdParty/base64/base64.cpp b/Framework/Orthanc/Resources/ThirdParty/base64/base64.cpp
new file mode 100644
index 0000000..33289c9
--- /dev/null
+++ b/Framework/Orthanc/Resources/ThirdParty/base64/base64.cpp
@@ -0,0 +1,128 @@
+/* 
+   base64.cpp and base64.h
+
+   Copyright (C) 2004-2008 Ren� Nyffenegger
+
+   This source code is provided 'as-is', without any express or implied
+   warranty. In no event will the author be held liable for any damages
+   arising from the use of this software.
+
+   Permission is granted to anyone to use this software for any purpose,
+   including commercial applications, and to alter it and redistribute it
+   freely, subject to the following restrictions:
+
+   1. The origin of this source code must not be misrepresented; you must not
+      claim that you wrote the original source code. If you use this source code
+      in a product, an acknowledgment in the product documentation would be
+      appreciated but is not required.
+
+   2. Altered source versions must be plainly marked as such, and must not be
+      misrepresented as being the original source code.
+
+   3. This notice may not be removed or altered from any source distribution.
+
+   Ren� Nyffenegger rene.nyffenegger at adp-gmbh.ch
+
+*/
+
+#include "base64.h"
+#include <string.h>
+
+static const std::string base64_chars = 
+             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+             "abcdefghijklmnopqrstuvwxyz"
+             "0123456789+/";
+
+
+static inline bool is_base64(unsigned char c) {
+  return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+std::string base64_encode(const std::string& stringToEncode) 
+{
+  const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*>
+    (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL);
+  unsigned int in_len = stringToEncode.size();
+  
+  std::string ret;
+  int i = 0;
+  int j = 0;
+  unsigned char char_array_3[3];
+  unsigned char char_array_4[4];
+
+  while (in_len--) {
+    char_array_3[i++] = *(bytes_to_encode++);
+    if (i == 3) {
+      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+      char_array_4[3] = char_array_3[2] & 0x3f;
+
+      for(i = 0; (i <4) ; i++)
+        ret += base64_chars[char_array_4[i]];
+      i = 0;
+    }
+  }
+
+  if (i)
+  {
+    for(j = i; j < 3; j++)
+      char_array_3[j] = '\0';
+
+    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+    char_array_4[3] = char_array_3[2] & 0x3f;
+
+    for (j = 0; (j < i + 1); j++)
+      ret += base64_chars[char_array_4[j]];
+
+    while((i++ < 3))
+      ret += '=';
+
+  }
+
+  return ret;
+}
+
+
+std::string base64_decode(const std::string& encoded_string) {
+  int in_len = encoded_string.size();
+  int i = 0;
+  int j = 0;
+  int in_ = 0;
+  unsigned char char_array_4[4], char_array_3[3];
+  std::string ret;
+
+  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
+    char_array_4[i++] = encoded_string[in_]; in_++;
+    if (i ==4) {
+      for (i = 0; i <4; i++)
+        char_array_4[i] = base64_chars.find(char_array_4[i]);
+
+      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+      for (i = 0; (i < 3); i++)
+        ret += char_array_3[i];
+      i = 0;
+    }
+  }
+
+  if (i) {
+    for (j = i; j <4; j++)
+      char_array_4[j] = 0;
+
+    for (j = 0; j <4; j++)
+      char_array_4[j] = base64_chars.find(char_array_4[j]);
+
+    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
+  }
+
+  return ret;
+}
diff --git a/Framework/Orthanc/Resources/ThirdParty/base64/base64.h b/Framework/Orthanc/Resources/ThirdParty/base64/base64.h
new file mode 100644
index 0000000..5e62f1a
--- /dev/null
+++ b/Framework/Orthanc/Resources/ThirdParty/base64/base64.h
@@ -0,0 +1,4 @@
+#include <string>
+
+std::string base64_encode(const std::string& stringToEncode);
+std::string base64_decode(const std::string& s);
diff --git a/Framework/Orthanc/Resources/WindowsResources.py b/Framework/Orthanc/Resources/WindowsResources.py
new file mode 100755
index 0000000..c56733b
--- /dev/null
+++ b/Framework/Orthanc/Resources/WindowsResources.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# In addition, as a special exception, the copyright holders of this
+# program give permission to link the code of its release with the
+# OpenSSL project's "OpenSSL" library (or with modified versions of it
+# that use the same license as the "OpenSSL" library), and distribute
+# the linked executables. You must obey the GNU General Public License
+# in all respects for all of the code used other than "OpenSSL". If you
+# modify file(s) with this exception, you may extend this exception to
+# your version of the file(s), but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from your
+# version. If you delete this exception statement from all source files
+# in the program, then also delete it here.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import os
+import sys
+import datetime
+
+if len(sys.argv) != 5:
+    sys.stderr.write('Usage: %s <Version> <ProductName> <Filename> <Description>\n\n' % sys.argv[0])
+    sys.stderr.write('Example: %s 0.9.1 Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging"\n' % sys.argv[0])
+    sys.exit(-1)
+
+SOURCE = os.path.join(os.path.dirname(__file__), 'WindowsResources.rc')
+
+VERSION = sys.argv[1]
+PRODUCT = sys.argv[2]
+FILENAME = sys.argv[3]
+DESCRIPTION = sys.argv[4]
+
+if VERSION == 'mainline':
+    VERSION = '999.999.999'
+    RELEASE = 'This is a mainline build, not an official release'
+else:
+    RELEASE = 'Release %s' % VERSION
+
+v = VERSION.split('.')
+if len(v) != 2 and len(v) != 3:
+    sys.stderr.write('Bad version number: %s\n' % VERSION)
+    sys.exit(-1)
+
+if len(v) == 2:
+    v.append('0')
+
+extension = os.path.splitext(FILENAME)[1]
+if extension.lower() == '.dll':
+    BLOCK = '040904E4'
+    TYPE = 'VFT_DLL'
+elif extension.lower() == '.exe':
+    #BLOCK = '040904B0'   # LANG_ENGLISH/SUBLANG_ENGLISH_US,
+    BLOCK = '040904E4'   # Lang=US English, CharSet=Windows Multilingual
+    TYPE = 'VFT_APP'
+else:
+    sys.stderr.write('Unsupported extension (.EXE or .DLL only): %s\n' % extension)
+    sys.exit(-1)
+
+
+with open(SOURCE, 'r') as source:
+    content = source.read()
+    content = content.replace('${VERSION_MAJOR}', v[0])
+    content = content.replace('${VERSION_MINOR}', v[1])
+    content = content.replace('${VERSION_PATCH}', v[2])
+    content = content.replace('${RELEASE}', RELEASE)
+    content = content.replace('${DESCRIPTION}', DESCRIPTION)
+    content = content.replace('${PRODUCT}', PRODUCT)   
+    content = content.replace('${FILENAME}', FILENAME)   
+    content = content.replace('${YEAR}', str(datetime.datetime.now().year))
+    content = content.replace('${BLOCK}', BLOCK)
+    content = content.replace('${TYPE}', TYPE)
+
+    sys.stdout.write(content)
diff --git a/Framework/Orthanc/Resources/WindowsResources.rc b/Framework/Orthanc/Resources/WindowsResources.rc
new file mode 100644
index 0000000..895115c
--- /dev/null
+++ b/Framework/Orthanc/Resources/WindowsResources.rc
@@ -0,0 +1,30 @@
+#include <winver.h>
+
+VS_VERSION_INFO VERSIONINFO
+   FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,${VERSION_PATCH}
+   PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,0
+   FILEOS VOS_NT_WINDOWS32
+   FILETYPE ${TYPE}
+   BEGIN
+      BLOCK "StringFileInfo"
+      BEGIN
+         BLOCK "${BLOCK}"
+         BEGIN
+            VALUE "Comments", "${RELEASE}"
+            VALUE "CompanyName", "University Hospital of Liege, Belgium"
+            VALUE "FileDescription", "${DESCRIPTION}"
+            VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}"
+            VALUE "InternalName", "${PRODUCT}"
+            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Belgium"
+            VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/"
+            VALUE "OriginalFilename", "${FILENAME}"
+            VALUE "ProductName", "${PRODUCT}"
+            VALUE "ProductVersion", "${VERSION_MAJOR}.${VERSION_MINOR}"
+         END
+      END
+
+      BLOCK "VarFileInfo"
+      BEGIN
+        VALUE "Translation", 0x409, 1252  // U.S. English
+      END
+   END
diff --git a/Framework/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h b/Framework/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h
new file mode 100644
index 0000000..36fc61b
--- /dev/null
+++ b/Framework/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h
@@ -0,0 +1,4740 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup Worklists Worklists
+ * @brief Functions to register and manage worklists.
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     0
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
+
+
+
+/********************************************************************
+ ** Check that function inlining is properly supported. The use of
+ ** inlining is required, to avoid the duplication of object code
+ ** between two compilation modules that would use the Orthanc Plugin
+ ** API.
+ ********************************************************************/
+
+/* If the auto-detection of the "inline" keyword below does not work
+   automatically and that your compiler is known to properly support
+   inlining, uncomment the following #define and adapt the definition
+   of "static inline". */
+
+/* #define ORTHANC_PLUGIN_INLINE static inline */
+
+#ifndef ORTHANC_PLUGIN_INLINE
+#  if __STDC_VERSION__ >= 199901L
+/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__cplusplus)
+/*   This is C++ */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__GNUC__)
+/*   This is GCC running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  elif defined(_MSC_VER)
+/*   This is Visual Studio running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  else
+#    error Your compiler is not known to support the "inline" keyword
+#  endif
+#endif
+
+
+
+/********************************************************************
+ ** Inclusion of standard libraries.
+ ********************************************************************/
+
+/**
+ * For Microsoft Visual Studio, a compatibility "stdint.h" can be
+ * downloaded at the following URL:
+ * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h
+ **/
+#include <stdint.h>
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * The various error codes that can be returned by the Orthanc core.
+   **/
+  typedef enum
+  {
+    OrthancPluginErrorCode_InternalError = -1    /*!< Internal error */,
+    OrthancPluginErrorCode_Success = 0    /*!< Success */,
+    OrthancPluginErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    OrthancPluginErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    OrthancPluginErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    OrthancPluginErrorCode_NotEnoughMemory = 4    /*!< Not enough memory */,
+    OrthancPluginErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    OrthancPluginErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    OrthancPluginErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    OrthancPluginErrorCode_BadRequest = 8    /*!< Bad request */,
+    OrthancPluginErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    OrthancPluginErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    OrthancPluginErrorCode_Database = 11    /*!< Error with the database engine */,
+    OrthancPluginErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    OrthancPluginErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    OrthancPluginErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    OrthancPluginErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    OrthancPluginErrorCode_Timeout = 16    /*!< Timeout */,
+    OrthancPluginErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    OrthancPluginErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    OrthancPluginErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    OrthancPluginErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    OrthancPluginErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    OrthancPluginErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    OrthancPluginErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    OrthancPluginErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    OrthancPluginErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    OrthancPluginErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    OrthancPluginErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    OrthancPluginErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    OrthancPluginErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    OrthancPluginErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    OrthancPluginErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    OrthancPluginErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    OrthancPluginErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    OrthancPluginErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    OrthancPluginErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    OrthancPluginErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    OrthancPluginErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    OrthancPluginErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    OrthancPluginErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    OrthancPluginErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    OrthancPluginErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    OrthancPluginErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    OrthancPluginErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    OrthancPluginErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    OrthancPluginErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    OrthancPluginErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    OrthancPluginErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _OrthancPluginService_SendMultipartItem2 = 2012,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004,
+    _OrthancPluginService_LookupPatient = 3005,
+    _OrthancPluginService_LookupStudy = 3006,
+    _OrthancPluginService_LookupSeries = 3007,
+    _OrthancPluginService_LookupInstance = 3008,
+    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
+    _OrthancPluginService_RestApiGetAfterPlugins = 3010,
+    _OrthancPluginService_RestApiPostAfterPlugins = 3011,
+    _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
+    _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+    _OrthancPluginService_ReconstructMainDicomTags = 3014,
+    _OrthancPluginService_RestApiGet2 = 3015,
+
+    /* Access to DICOM instances */
+    _OrthancPluginService_GetInstanceRemoteAet = 4000,
+    _OrthancPluginService_GetInstanceSize = 4001,
+    _OrthancPluginService_GetInstanceData = 4002,
+    _OrthancPluginService_GetInstanceJson = 4003,
+    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
+    _OrthancPluginService_HasInstanceMetadata = 4005,
+    _OrthancPluginService_GetInstanceMetadata = 4006,
+    _OrthancPluginService_GetInstanceOrigin = 4007,
+
+    /* Services for plugins implementing a database back-end */
+    _OrthancPluginService_RegisterDatabaseBackend = 5000,
+    _OrthancPluginService_DatabaseAnswer = 5001,
+    _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,
+    _OrthancPluginService_StorageAreaCreate = 5003,
+    _OrthancPluginService_StorageAreaRead = 5004,
+    _OrthancPluginService_StorageAreaRemove = 5005,
+
+    /* Primitives for handling images */
+    _OrthancPluginService_GetImagePixelFormat = 6000,
+    _OrthancPluginService_GetImageWidth = 6001,
+    _OrthancPluginService_GetImageHeight = 6002,
+    _OrthancPluginService_GetImagePitch = 6003,
+    _OrthancPluginService_GetImageBuffer = 6004,
+    _OrthancPluginService_UncompressImage = 6005,
+    _OrthancPluginService_FreeImage = 6006,
+    _OrthancPluginService_CompressImage = 6007,
+    _OrthancPluginService_ConvertPixelFormat = 6008,
+    _OrthancPluginService_GetFontsCount = 6009,
+    _OrthancPluginService_GetFontInfo = 6010,
+    _OrthancPluginService_DrawText = 6011,
+    _OrthancPluginService_CreateImage = 6012,
+    _OrthancPluginService_CreateImageAccessor = 6013,
+    _OrthancPluginService_DecodeDicomImage = 6014,
+
+    /* Primitives for handling worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can happen to DICOM resources.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       remoteAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of this C header is
+   * compatible with the current version of Orthanc. The result of
+   * this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, it is up to the plugin to
+   * implement the required locking mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * script.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              subType,
+    const char*              contentType)
+  {
+    _OrthancPluginStartMultipartAnswer params;
+    params.output = output;
+    params.subType = subType;
+    params.contentType = contentType;
+    return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, &params);
+  }
+
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = NULL;
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*    target;
+    const void*                   source;
+    uint32_t                      size;
+    OrthancPluginCompressionType  compression;
+    uint8_t                       uncompress;
+  } _OrthancPluginBufferCompression;
+
+
+  /**
+   * @brief Compress or decompress a buffer.
+   *
+   * This function compresses or decompresses a buffer, using the
+   * version of the zlib library that is used by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param source The source buffer.
+   * @param size The size in bytes of the source buffer.
+   * @param compression The compression algorithm.
+   * @param uncompress If set to "0", the buffer must be compressed. 
+   * If set to "1", the buffer must be uncompressed.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    const void*                   source,
+    uint32_t                      size,
+    OrthancPluginCompressionType  compression,
+    uint8_t                       uncompress)
+  {
+    _OrthancPluginBufferCompression params;
+    params.target = target;
+    params.source = source;
+    params.size = size;
+    params.compression = compression;
+    params.uncompress = uncompress;
+
+    return context->InvokeService(context, _OrthancPluginService_BufferCompression, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 path;
+  } _OrthancPluginReadFile;
+
+  /**
+   * @brief Read a file.
+   * 
+   * Read the content of a file on the filesystem, and returns it into
+   * a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param path The path of the file to be read.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReadFile(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 path)
+  {
+    _OrthancPluginReadFile params;
+    params.target = target;
+    params.path = path;
+    return context->InvokeService(context, _OrthancPluginService_ReadFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char*  path;
+    const void*  data;
+    uint32_t     size;
+  } _OrthancPluginWriteFile;
+
+  /**
+   * @brief Write a file.
+   * 
+   * Write the content of a memory buffer to the filesystem.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path The path of the file to be written.
+   * @param data The content of the memory buffer.
+   * @param size The size of the memory buffer.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWriteFile(
+    OrthancPluginContext*  context,
+    const char*            path,
+    const void*            data,
+    uint32_t               size)
+  {
+    _OrthancPluginWriteFile params;
+    params.path = path;
+    params.data = data;
+    params.size = size;
+    return context->InvokeService(context, _OrthancPluginService_WriteFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char**            target;
+    OrthancPluginErrorCode  error;
+  } _OrthancPluginGetErrorDescription;
+
+  /**
+   * @brief Get the description of a given error code.
+   *
+   * This function returns the description of a given error code.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param error The error code of interest.
+   * @return The error description. This is a statically-allocated
+   * string, do not free it.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription(
+    OrthancPluginContext*    context,
+    OrthancPluginErrorCode   error)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetErrorDescription params;
+    params.target = &result;
+    params.error = error;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, &params) != OrthancPluginErrorCode_Success ||
+        result == NULL)
+    {
+      return "Unknown error code";
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+    const char*              body;
+    uint32_t                 bodySize;
+  } _OrthancPluginSendHttpStatus;
+
+  /**
+   * @brief Send a HTTP status, with a custom body.
+   *
+   * This function answers to a HTTP request by sending a HTTP status
+   * code (such as "400 - Bad Request"), together with a body
+   * describing the error. The body will only be returned if the
+   * configuration option "HttpDescribeErrors" of Orthanc is set to "true".
+   * 
+   * Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @param body The body of the answer.
+   * @param bodySize The size of the body.
+   * @see OrthancPluginSendHttpStatusCode()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status,
+    const char*              body,
+    uint32_t                 bodySize)
+  {
+    _OrthancPluginSendHttpStatus params;
+    params.output = output;
+    params.status = status;
+    params.body = body;
+    params.bodySize = bodySize;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatus, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const OrthancPluginImage*  image;
+    uint32_t*                  resultUint32;
+    OrthancPluginPixelFormat*  resultPixelFormat;
+    void**                     resultBuffer;
+  } _OrthancPluginGetImageInfo;
+
+
+  /**
+   * @brief Return the pixel format of an image.
+   *
+   * This function returns the type of memory layout for the pixels of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pixel format.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat  OrthancPluginGetImagePixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    OrthancPluginPixelFormat target;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultPixelFormat = ⌖
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return OrthancPluginPixelFormat_Unknown;
+    }
+    else
+    {
+      return (OrthancPluginPixelFormat) target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the width of an image.
+   *
+   * This function returns the width of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The width.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageWidth(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t width;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &width;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return width;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the height of an image.
+   *
+   * This function returns the height of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The height.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageHeight(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t height;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &height;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return height;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the pitch of an image.
+   *
+   * This function returns the pitch of the given image. The pitch is
+   * defined as the number of bytes between 2 successive lines of the
+   * image in the memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pitch.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImagePitch(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t pitch;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &pitch;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return pitch;
+    }
+  }
+
+
+
+  /**
+   * @brief Return a pointer to the content of an image.
+   *
+   * This function returns a pointer to the memory buffer that
+   * contains the pixels of the image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pointer.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void*  OrthancPluginGetImageBuffer(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    void* target = NULL;
+
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.resultBuffer = ⌖
+    params.image = image;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const void*                data;
+    uint32_t                   size;
+    OrthancPluginImageFormat   format;
+  } _OrthancPluginUncompressImage;
+
+
+  /**
+   * @brief Decode a compressed image.
+   *
+   * This function decodes a compressed image from a memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param data Pointer to a memory buffer containing the compressed image.
+   * @param size Size of the memory buffer containing the compressed image.
+   * @param format The file format of the compressed image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage(
+    OrthancPluginContext*      context,
+    const void*                data,
+    uint32_t                   size,
+    OrthancPluginImageFormat   format)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginUncompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = ⌖
+    params.data = data;
+    params.size = size;
+    params.format = format;
+
+    if (context->InvokeService(context, _OrthancPluginService_UncompressImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+  } _OrthancPluginFreeImage;
+
+  /**
+   * @brief Free an image.
+   *
+   * This function frees an image that was decoded with OrthancPluginUncompressImage().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeImage(
+    OrthancPluginContext* context, 
+    OrthancPluginImage*   image)
+  {
+    _OrthancPluginFreeImage params;
+    params.image = image;
+
+    context->InvokeService(context, _OrthancPluginService_FreeImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer* target;
+    OrthancPluginImageFormat   imageFormat;
+    OrthancPluginPixelFormat   pixelFormat;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    const void*                buffer;
+    uint8_t                    quality;
+  } _OrthancPluginCompressImage;
+
+
+  /**
+   * @brief Encode a PNG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the PNG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginCompressAndAnswerPngImage()
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* Unused for PNG */
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+  /**
+   * @brief Encode a JPEG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the JPEG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer,
+    uint8_t                       quality)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+
+  /**
+   * @brief Answer to a REST request with a JPEG image.
+   *
+   * This function answers to a REST request with a JPEG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a JPEG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer,
+    uint8_t                   quality)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    const char*                 username;
+    const char*                 password;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginCallHttpClient;
+
+
+  /**
+   * @brief Issue a HTTP GET call.
+   * 
+   * Make a HTTP GET call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiGet() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Get;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP POST call.
+   * 
+   * Make a HTTP POST call to the given URL. The result to the query
+   * is stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPost() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Post;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP PUT call.
+   * 
+   * Make a HTTP PUT call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPut() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Put;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP DELETE call.
+   * 
+   * Make a HTTP DELETE call to the given URL. Favor
+   * OrthancPluginRestApiDelete() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpDelete(
+    OrthancPluginContext*       context,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.method = OrthancPluginHttpMethod_Delete;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const OrthancPluginImage*  source;
+    OrthancPluginPixelFormat   targetFormat;
+  } _OrthancPluginConvertPixelFormat;
+
+
+  /**
+   * @brief Change the pixel format of an image.
+   *
+   * This function creates a new image, changing the memory layout of the pixels.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param source The source image.
+   * @param targetFormat The target pixel format.
+   * @return The resulting image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  source,
+    OrthancPluginPixelFormat   targetFormat)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginConvertPixelFormat params;
+    params.target = ⌖
+    params.source = source;
+    params.targetFormat = targetFormat;
+
+    if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the number of available fonts.
+   *
+   * This function returns the number of fonts that are built in the
+   * Orthanc core. These fonts can be used to draw texts on images
+   * through OrthancPluginDrawText().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of fonts.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    uint32_t      fontIndex; /* in */
+    const char**  name; /* out */
+    uint32_t*     size; /* out */
+  } _OrthancPluginGetFontInfo;
+
+  /**
+   * @brief Return the name of a font.
+   *
+   * This function returns the name of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font name. This is a statically-allocated string, do not free it.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.name = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the size of a font.
+   *
+   * This function returns the size of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font size.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    uint32_t result;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.size = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+    uint32_t              fontIndex;
+    const char*           utf8Text;
+    int32_t               x;
+    int32_t               y;
+    uint8_t               r;
+    uint8_t               g;
+    uint8_t               b;
+  } _OrthancPluginDrawText;
+
+
+  /**
+   * @brief Draw text on an image.
+   *
+   * This function draws some text on some image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image upon which to draw the text.
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string.
+   * @param x The X position of the text over the image.
+   * @param y The Y position of the text over the image.
+   * @param r The value of the red color channel of the text.
+   * @param g The value of the green color channel of the text.
+   * @param b The value of the blue color channel of the text.
+   * @return 0 if success, other value if error.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginDrawText(
+    OrthancPluginContext*  context,
+    OrthancPluginImage*    image,
+    uint32_t               fontIndex,
+    const char*            utf8Text,
+    int32_t                x,
+    int32_t                y,
+    uint8_t                r,
+    uint8_t                g,
+    uint8_t                b)
+  {
+    _OrthancPluginDrawText params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.fontIndex = fontIndex;
+    params.utf8Text = utf8Text;
+    params.x = x;
+    params.y = y;
+    params.r = r;
+    params.g = g;
+    params.b = b;
+
+    return context->InvokeService(context, _OrthancPluginService_DrawText, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    const void*                 content;
+    uint64_t                    size;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaCreate;
+
+
+  /**
+   * @brief Create a file inside the storage area.
+   *
+   * This function creates a new file inside the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be created.
+   * @param content The content to store in the newly created file.
+   * @param size The size of the content.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaCreate(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    const void*                 content,
+    uint64_t                    size,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaCreate params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.content = content;
+    params.size = size;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRead;
+
+
+  /**
+   * @brief Read a file from the storage area.
+   *
+   * This function reads the content of a given file from the storage
+   * area that is currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be read.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRead(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRead params;
+    params.target = target;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRemove;
+
+  /**
+   * @brief Remove a file from the storage area.
+   *
+   * This function removes a given file from the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be removed.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRemove(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRemove params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginErrorCode*  target;
+    int32_t                  code;
+    uint16_t                 httpStatus;
+    const char*              message;
+  } _OrthancPluginRegisterErrorCode;
+  
+  /**
+   * @brief Declare a custom error code for this plugin.
+   *
+   * This function declares a custom error code that can be generated
+   * by this plugin. This declaration is used to enrich the body of
+   * the HTTP answer in the case of an error, and to set the proper
+   * HTTP status code.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param code The error code that is internal to this plugin.
+   * @param httpStatus The HTTP status corresponding to this error.
+   * @param message The description of the error.
+   * @return The error code that has been assigned inside the Orthanc core.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterErrorCode(
+    OrthancPluginContext*    context,
+    int32_t                  code,
+    uint16_t                 httpStatus,
+    const char*              message)
+  {
+    OrthancPluginErrorCode target;
+
+    _OrthancPluginRegisterErrorCode params;
+    params.target = ⌖
+    params.code = code;
+    params.httpStatus = httpStatus;
+    params.message = message;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
+    {
+      return target;
+    }
+    else
+    {
+      /* There was an error while assigned the error. Use a generic code. */
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+  } _OrthancPluginRegisterDictionaryTag;
+  
+  /**
+   * @brief Register a new tag into the DICOM dictionary.
+   *
+   * This function declares a new tag in the dictionary of DICOM tags
+   * that are known to Orthanc. This function should be used in the
+   * OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity)
+  {
+    _OrthancPluginRegisterDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*  storageArea;
+    OrthancPluginResourceType  level;
+  } _OrthancPluginReconstructMainDicomTags;
+
+  /**
+   * @brief Reconstruct the main DICOM tags.
+   *
+   * This function requests the Orthanc core to reconstruct the main
+   * DICOM tags of all the resources of the given type. This function
+   * can only be used as a part of the upgrade of a custom database
+   * back-end
+   * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A
+   * database transaction will be automatically setup.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param level The type of the resources of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReconstructMainDicomTags(
+    OrthancPluginContext*      context,
+    OrthancPluginStorageArea*  storageArea,
+    OrthancPluginResourceType  level)
+  {
+    _OrthancPluginReconstructMainDicomTags params;
+    params.level = level;
+    params.storageArea = storageArea;
+
+    return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                          result;
+    const char*                     instanceId;
+    const char*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const char*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = ⌖
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = ⌖
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = ⌖
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    uint32_t                 headersCount;
+    const char* const*       headersKeys;
+    const char* const*       headersValues;
+  } _OrthancPluginSendMultipartItem2;
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer, with custom headers.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to
+   * OrthancPluginSendMultipartItem(), this function will set HTTP header associated
+   * with the item.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues)
+  {
+    _OrthancPluginSendMultipartItem2 params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;    
+
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, &params);
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
diff --git a/Framework/Outputs/DicomPyramidWriter.cpp b/Framework/Outputs/DicomPyramidWriter.cpp
new file mode 100644
index 0000000..7d887fb
--- /dev/null
+++ b/Framework/Outputs/DicomPyramidWriter.cpp
@@ -0,0 +1,207 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "DicomPyramidWriter.h"
+
+#include "../DicomToolbox.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/OrthancServer/FromDcmtkBridge.h"
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancWSI
+{
+  void DicomPyramidWriter::FlushInternal(MultiframeDicomWriter& writer,
+                                         bool force)
+  {
+    if (writer.GetFramesCount() > 0 &&
+        writer.GetSize() > 0 &&
+        (force || (maxSize_ != 0 && writer.GetSize() >= maxSize_)))
+    {
+      countInstances_ += 1;
+
+      std::string dicom;
+      writer.Flush(dicom, countInstances_);
+      target_.Write(dicom);
+    }
+  }
+
+
+  DcmItem* DicomPyramidWriter::CreateFunctionalGroup(unsigned int frame,
+                                                     unsigned int x, 
+                                                     unsigned int y,
+                                                     unsigned int totalWidth,
+                                                     unsigned int totalHeight,
+                                                     float physicalZ) const
+  {
+    float physicalX, physicalY;
+    volume_.GetLocation(physicalX, physicalY, x, y, totalWidth, totalHeight);
+
+    std::string tmpX = boost::lexical_cast<std::string>(physicalX);
+    std::string tmpY = boost::lexical_cast<std::string>(physicalY);
+    std::string tmpZ = boost::lexical_cast<std::string>(physicalZ);
+
+    std::auto_ptr<DcmItem> dimension(new DcmItem);
+    if (!dimension->putAndInsertUint32(DCM_DimensionIndexValues, frame).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    // From Supp 145: The column position of the top left pixel of the
+    // Total Pixel Matrix is 1. The row position of the top left pixel
+    // of the Total Pixel Matrix is 1.
+    std::auto_ptr<DcmItem> position(new DcmItem);
+    if (!position->putAndInsertSint32(DCM_ColumnPositionInTotalImagePixelMatrix, x + 1).good() ||
+        !position->putAndInsertSint32(DCM_RowPositionInTotalImagePixelMatrix, y + 1).good() ||
+        !position->putAndInsertString(DCM_XOffsetInSlideCoordinateSystem, tmpX.c_str()).good() ||
+        !position->putAndInsertString(DCM_YOffsetInSlideCoordinateSystem, tmpY.c_str()).good() ||
+        !position->putAndInsertString(DCM_ZOffsetInSlideCoordinateSystem, tmpZ.c_str()).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+
+    std::auto_ptr<DcmSequenceOfItems> sequencePosition(new DcmSequenceOfItems(DCM_PlanePositionSlideSequence));
+    if (!sequencePosition->insert(position.release(), false, false).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    std::auto_ptr<DcmSequenceOfItems> sequenceDimension(new DcmSequenceOfItems(DCM_FrameContentSequence));
+    if (!sequenceDimension->insert(dimension.release(), false, false).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    std::auto_ptr<DcmItem> item(new DcmItem);
+    if (!item->insert(sequencePosition.release(), false, false).good() ||
+        !item->insert(sequenceDimension.release(), false, false).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }  
+
+    return item.release();
+  }
+
+
+  void DicomPyramidWriter::WriteRawTileInternal(const std::string& tile,
+                                                const Level& level,
+                                                unsigned int x,
+                                                unsigned int y)
+  {
+    if (x >= level.countTilesX_ ||
+        y >= level.countTilesY_)
+    {
+      LOG(ERROR) << "Tile index out of range: " << x << "," << y
+                 << " (max: " << level.countTilesX_ << "," << level.countTilesY_ << ")";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    const unsigned int z = level.z_;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      
+      if (z >= writers_.size())
+      {
+        writers_.resize(z + 1);
+      }
+
+      MultiframeDicomWriter* writer = writers_[z];
+
+      if (writer == NULL)
+      {
+        writer = new MultiframeDicomWriter
+          (dataset_, GetImageCompression(), GetPixelFormat(), level.width_, level.height_, 
+           GetTileWidth(), GetTileHeight());
+        writers_[z] = writer;
+      }
+
+      std::auto_ptr<DcmItem> functionalGroup(CreateFunctionalGroup(writer->GetFramesCount() + 1,
+                                                                   x * GetTileWidth(), 
+                                                                   y * GetTileHeight(), 
+                                                                   writer->GetTotalWidth(),
+                                                                   writer->GetTotalHeight(),
+                                                                   0.0f /* TODO Z-plane */));
+
+      writer->AddFrame(tile, functionalGroup.release());
+      FlushInternal(*writer, false);
+
+      countTiles_ ++;
+    }
+  }
+
+
+  DicomPyramidWriter::DicomPyramidWriter(IFileTarget&  target,
+                                         const DcmDataset& dataset,
+                                         Orthanc::PixelFormat pixelFormat,
+                                         ImageCompression compression,
+                                         unsigned int tileWidth,
+                                         unsigned int tileHeight,
+                                         size_t maxSize,   // If "0", no automatic flushing
+                                         const ImagedVolumeParameters&  volume) :
+    PyramidWriterBase(pixelFormat, compression, tileWidth, tileHeight),
+    target_(target),
+    dataset_(dataset),
+    maxSize_(maxSize),
+    countTiles_(0),
+    countInstances_(0),
+    volume_(volume)
+  {
+  }
+
+
+  DicomPyramidWriter::~DicomPyramidWriter()
+  {
+    LOG(WARNING) << "Closing the DICOM pyramid (" << countTiles_ << " tiles were written)";
+
+    for (size_t i = 0; i < writers_.size(); i++)
+    {
+      if (writers_[i] != NULL)
+      {
+        try
+        {
+          FlushInternal(*writers_[i], true);
+        }
+        catch (Orthanc::OrthancException&)
+        {
+          LOG(ERROR) << "Cannot push the pending tiles to the DICOM pyramid while finalizing";
+        }
+
+        delete writers_[i];
+      }
+    }
+  }
+
+
+  void DicomPyramidWriter::Flush()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+      
+    for (size_t i = 0; i < writers_.size(); i++)
+    {
+      FlushInternal(*writers_[i], true);
+    }
+  }
+}
diff --git a/Framework/Outputs/DicomPyramidWriter.h b/Framework/Outputs/DicomPyramidWriter.h
new file mode 100644
index 0000000..48f3abc
--- /dev/null
+++ b/Framework/Outputs/DicomPyramidWriter.h
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PyramidWriterBase.h"
+#include "MultiframeDicomWriter.h"
+#include "../Messaging/IFileTarget.h"
+#include "../ImagedVolumeParameters.h"
+
+namespace OrthancWSI
+{
+  class DicomPyramidWriter : public PyramidWriterBase
+  {
+  private:
+    std::vector<MultiframeDicomWriter*>  writers_;
+
+    boost::mutex       mutex_;   // Protects the access to "writers_"
+    IFileTarget&       target_;
+    const DcmDataset&  dataset_;
+    size_t             maxSize_;
+    size_t             countTiles_;
+    unsigned int       countInstances_;
+
+    const ImagedVolumeParameters&  volume_;
+
+    void FlushInternal(MultiframeDicomWriter& writer,
+                       bool force);
+
+    DcmItem* CreateFunctionalGroup(unsigned int frame,
+                                   unsigned int x, 
+                                   unsigned int y,
+                                   unsigned int totalWidth,
+                                   unsigned int totalHeight,
+                                   float physicalZ) const;
+
+  protected:
+    virtual void WriteRawTileInternal(const std::string& tile,
+                                      const Level& level,
+                                      unsigned int x,
+                                      unsigned int y);
+
+    virtual void AddLevelInternal(const Level& level)
+    {
+    }
+
+  public:
+    DicomPyramidWriter(IFileTarget&  target,
+                       const DcmDataset& dataset,
+                       Orthanc::PixelFormat pixelFormat,
+                       ImageCompression compression,
+                       unsigned int tileWidth,
+                       unsigned int tileHeight,
+                       size_t maxSize,   // If "0", no automatic flushing
+                       const ImagedVolumeParameters&  volume);
+
+    virtual ~DicomPyramidWriter();
+
+    virtual void Flush();
+  };
+}
diff --git a/Framework/Outputs/HierarchicalTiffWriter.cpp b/Framework/Outputs/HierarchicalTiffWriter.cpp
new file mode 100644
index 0000000..adb7918
--- /dev/null
+++ b/Framework/Outputs/HierarchicalTiffWriter.cpp
@@ -0,0 +1,422 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "HierarchicalTiffWriter.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Uuid.h"
+
+namespace OrthancWSI
+{
+  class HierarchicalTiffWriter::PendingTile
+  {
+  private:
+    HierarchicalTiffWriter&          that_;
+    unsigned int                     level_;
+    unsigned int                     tileX_;
+    unsigned int                     tileY_;
+    Orthanc::Toolbox::TemporaryFile  file_;
+      
+  public:
+    PendingTile(HierarchicalTiffWriter& that,
+                unsigned int level,
+                unsigned int tileX,
+                unsigned int tileY,
+                const std::string& tile) :
+      that_(that),
+      level_(level),
+      tileX_(tileX),
+      tileY_(tileY)
+    {
+      file_.Write(tile);
+    }
+
+    unsigned int GetLevel() const
+    {
+      return level_;
+    }
+
+    unsigned int GetTileX() const
+    {
+      return tileX_;
+    }
+
+    unsigned int GetTileY() const
+    {
+      return tileY_;
+    }
+
+    void Store(TIFF* tiff)
+    {
+      std::string tile;
+      file_.Read(tile);
+      that_.StoreTile(tile, tileX_, tileY_);
+    }
+  };
+
+
+  struct HierarchicalTiffWriter::Comparator
+  {
+    inline bool operator() (PendingTile* const& a,
+                            PendingTile* const& b)
+    {
+      if (a->GetLevel() < b->GetLevel())
+      {
+        return true;
+      }
+
+      if (a->GetLevel() > b->GetLevel())
+      {
+        return false;
+      }
+
+      if (a->GetTileY() < b->GetTileY())
+      {
+        return true;
+      }
+
+      if (a->GetTileY() > b->GetTileY())
+      {
+        return false;
+      }
+
+      return a->GetTileX() < b->GetTileX();
+    }
+  };
+
+
+  static uint8_t GetUint8(const std::string& tile,
+                          size_t index)
+  {
+    if (index >= tile.size())
+    {
+      return 0;
+    }
+    else
+    {
+      return static_cast<uint8_t>(tile[index]);
+    }
+  }
+
+ 
+#if 0
+  static uint16_t GetUint16(const std::string& tile,
+                            size_t index)
+  {
+    if (index + 1 >= tile.size())
+    {
+      return 0;
+    }
+    else
+    {
+      return static_cast<uint16_t>(tile[index]) * 256 + static_cast<uint16_t>(tile[index + 1]);
+    }
+  }
+#endif
+
+
+  static void CheckJpegTile(const std::string& tile,
+                            Orthanc::PixelFormat pixelFormat)
+  {
+    // Check the sampling by accessing the "Start of Frame" header of JPEG
+
+    if (tile.size() < 3 ||
+        static_cast<uint8_t>(tile[0]) != 0xff ||
+        static_cast<uint8_t>(tile[1]) != 0xd8 ||
+        static_cast<uint8_t>(tile[2]) != 0xff)
+    {
+      LOG(WARNING) << "The source image does not contain JPEG tiles";
+      return;
+    }
+
+    // Look for the "Start of Frame" header (FF C0)
+    for (size_t i = 2; i + 1 < tile.size(); i++)
+    {
+      if (static_cast<uint8_t>(tile[i]) == 0xff &&
+          static_cast<uint8_t>(tile[i + 1]) == 0xc0)
+      {
+        uint8_t numberOfComponents = GetUint8(tile, i + 9);
+          
+        switch (pixelFormat)
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+            if (numberOfComponents != 3)
+            {
+              LOG(WARNING) << "The source image does not contain a grayscale image as expected";
+            }
+            break;
+
+          case Orthanc::PixelFormat_RGB24:
+          {
+            if (numberOfComponents != 3)
+            {
+              LOG(WARNING) << "The source image does not contain a RGB24 color image as expected";
+            }
+
+            // Read the header corresponding to the first component
+            const size_t component = 0;
+            const size_t offset = i + 10 + component * 3; 
+            const uint8_t sampling = GetUint8(tile, offset + 1);
+            const int samplingH = sampling / 16;
+            const int samplingV = sampling % 16;
+
+            LOG(WARNING) << "The source image uses chroma sampling " << samplingH << ":" << samplingV;
+
+            if (samplingH != 2 ||
+                samplingV != 2)
+            {
+              LOG(WARNING) << "The source image has not a chroma sampling of 2:2, "
+                           << "you should consider using option \"--reencode\"";
+            }
+
+            break;
+          }
+
+          default:
+            break;
+        }
+      }
+    }
+  }
+
+
+ 
+  void HierarchicalTiffWriter::StoreTile(const std::string& tile,
+                                         unsigned int tileX,
+                                         unsigned int tileY)
+  {
+    // Get the index of the tile
+    ttile_t index = TIFFComputeTile(tiff_, tileX * GetTileWidth(), tileY * GetTileHeight(), 0 /*z*/, 0 /*sample*/);
+
+    if (TIFFWriteRawTile(tiff_, index, tile.size() ? const_cast<char*>(&tile[0]) : NULL, tile.size()) != 
+        static_cast<tsize_t>(tile.size()))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+
+    if (isFirst_ &&
+        GetImageCompression() == ImageCompression_Jpeg)
+    {
+      CheckJpegTile(tile, GetPixelFormat());
+    }
+
+    isFirst_ = false;
+  }
+
+
+  void HierarchicalTiffWriter::ConfigureLevel(const Level& level,
+                                              bool createLevel)
+  {
+    if (createLevel && TIFFWriteDirectory(tiff_) != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+
+    if (TIFFFlush(tiff_) != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+
+    currentLevel_ = level.z_;
+    nextX_ = 0;
+    nextY_ = 0;
+
+    switch (GetImageCompression())
+    {
+      case ImageCompression_Jpeg:
+      {
+        uint16_t c = COMPRESSION_JPEG;
+
+        if (TIFFSetField(tiff_, TIFFTAG_COMPRESSION, c) != 1)
+        {
+          Close();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+
+    switch (GetPixelFormat())
+    {
+      case Orthanc::PixelFormat_RGB24:
+      {
+        uint16_t samplesPerPixel = 3;
+        uint16_t photometric = PHOTOMETRIC_YCBCR;
+        uint16_t planar = PLANARCONFIG_CONTIG;   // Interleaved RGB
+        uint16_t bpp = 8;
+        uint16_t subsampleHorizontal = 2;
+        uint16_t subsampleVertical = 2;
+
+        if (TIFFSetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_PHOTOMETRIC, photometric) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_BITSPERSAMPLE, bpp) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_PLANARCONFIG, planar) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_YCBCRSUBSAMPLING, subsampleHorizontal, subsampleVertical) != 1)
+        {
+          Close();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    uint32_t w = level.width_;
+    uint32_t h = level.height_;
+    uint32_t tw = GetTileWidth();
+    uint32_t th = GetTileHeight();
+
+    if (TIFFSetField(tiff_, TIFFTAG_IMAGEWIDTH, w) != 1 ||
+        TIFFSetField(tiff_, TIFFTAG_IMAGELENGTH, h) != 1 ||
+        TIFFSetField(tiff_, TIFFTAG_TILEWIDTH, tw) != 1 ||
+        TIFFSetField(tiff_, TIFFTAG_TILELENGTH, th) != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }          
+  }
+
+
+  void HierarchicalTiffWriter::AdvanceToNextTile()
+  {
+    assert(currentLevel_ < levels_.size());
+
+    nextX_ += 1;
+    if (nextX_ >= levels_[currentLevel_].countTilesX_)
+    {
+      nextX_ = 0;
+      nextY_ += 1;
+
+      if (nextY_ >= levels_[currentLevel_].countTilesY_)
+      {
+        currentLevel_ += 1;
+
+        if (currentLevel_ < levels_.size())
+        {
+          ConfigureLevel(levels_[currentLevel_], true);
+        }
+      }
+    }
+  }
+
+
+  void HierarchicalTiffWriter::ScanPending()
+  {
+    std::sort(pending_.begin(), pending_.end(), Comparator());
+
+    while (currentLevel_ < levels_.size() &&
+           !pending_.empty() &&
+           pending_.front()->GetLevel() == currentLevel_ &&
+           pending_.front()->GetTileX() == nextX_ &&
+           pending_.front()->GetTileY() == nextY_)
+    {
+      pending_.front()->Store(tiff_);
+      delete pending_.front();
+      pending_.pop_front();
+      AdvanceToNextTile();
+    }
+  }
+          
+
+  void HierarchicalTiffWriter::WriteRawTileInternal(const std::string& tile,
+                                                    const Level& level,
+                                                    unsigned int tileX,
+                                                    unsigned int tileY)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (level.z_ == currentLevel_ &&
+        tileX == nextX_ &&
+        tileY == nextY_)
+    {
+      StoreTile(tile, tileX, tileY);
+      AdvanceToNextTile();
+      ScanPending();
+    }
+    else
+    {
+      pending_.push_back(new PendingTile(*this, level.z_, tileX, tileY, tile));
+    }
+  }
+
+        
+  void HierarchicalTiffWriter::AddLevelInternal(const Level& level)
+  {
+    if (level.z_ == 0)
+    {
+      // Configure the finest level on initialization
+      ConfigureLevel(level, false);
+    }
+
+    levels_.push_back(level);
+  }
+
+
+  HierarchicalTiffWriter::HierarchicalTiffWriter(const std::string& path,
+                                                 Orthanc::PixelFormat pixelFormat, 
+                                                 ImageCompression compression,
+                                                 unsigned int tileWidth,
+                                                 unsigned int tileHeight) :
+    PyramidWriterBase(pixelFormat, compression, tileWidth, tileHeight),
+    currentLevel_(0),
+    isFirst_(true)
+  {
+    tiff_ = TIFFOpen(path.c_str(), "w");
+    if (tiff_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+  }
+
+
+  HierarchicalTiffWriter::~HierarchicalTiffWriter()
+  {
+    if (pending_.size())
+    {
+      LOG(ERROR) << "Some tiles (" << pending_.size() << ") were not written to the TIFF file";
+    }
+
+    for (size_t i = 0; i < pending_.size(); i++)
+    {
+      if (pending_[i])
+      {
+        delete pending_[i];
+      }
+    }
+
+    Close();
+  }
+
+
+  void HierarchicalTiffWriter::Flush()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    ScanPending();
+  }
+}
diff --git a/Framework/Outputs/HierarchicalTiffWriter.h b/Framework/Outputs/HierarchicalTiffWriter.h
new file mode 100644
index 0000000..8a920e4
--- /dev/null
+++ b/Framework/Outputs/HierarchicalTiffWriter.h
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PyramidWriterBase.h"
+
+#include <tiff.h>
+#include <tiffio.h>
+#include <deque>
+#include <vector>
+#include <boost/thread.hpp>
+
+namespace OrthancWSI
+{
+  class HierarchicalTiffWriter : public PyramidWriterBase
+  {
+  private:
+    class PendingTile;
+    struct Comparator;
+
+    TIFF* tiff_;
+
+    boost::mutex              mutex_;
+    std::deque<PendingTile*>  pending_;
+    std::vector<Level>        levels_;
+    unsigned int              currentLevel_;
+    unsigned int              nextX_;
+    unsigned int              nextY_;
+    bool                      isFirst_;
+ 
+    void Close()
+    {
+      TIFFClose(tiff_);
+    }
+
+    void StoreTile(const std::string& tile,
+                   unsigned int tileX,
+                   unsigned int tileY);
+
+    void ConfigureLevel(const Level& level,
+                        bool createLevel);
+
+    void AdvanceToNextTile();
+
+    void ScanPending();
+
+
+  protected:
+    virtual void WriteRawTileInternal(const std::string& tile,
+                                      const Level& level,
+                                      unsigned int tileX,
+                                      unsigned int tileY);
+        
+    virtual void AddLevelInternal(const Level& level);
+
+
+  public:
+    HierarchicalTiffWriter(const std::string& path,
+                           Orthanc::PixelFormat pixelFormat, 
+                           ImageCompression compression,
+                           unsigned int tileWidth,
+                           unsigned int tileHeight);
+
+    virtual ~HierarchicalTiffWriter();
+
+    virtual void Flush();
+  };
+}
diff --git a/Framework/Outputs/IPyramidWriter.h b/Framework/Outputs/IPyramidWriter.h
new file mode 100644
index 0000000..0e4229e
--- /dev/null
+++ b/Framework/Outputs/IPyramidWriter.h
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+#include "../Orthanc/Core/Images/ImageAccessor.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancWSI
+{
+  class IPyramidWriter : public boost::noncopyable
+  {
+  public:
+    virtual ~IPyramidWriter()
+    {
+    }
+
+    virtual unsigned int GetLevelCount() const = 0;
+    
+    virtual Orthanc::PixelFormat GetPixelFormat() const = 0;
+
+    virtual unsigned int GetTileWidth() const = 0;
+
+    virtual unsigned int GetTileHeight() const = 0;
+
+    virtual unsigned int GetCountTilesX(unsigned int level) const = 0;
+
+    virtual unsigned int GetCountTilesY(unsigned int level) const = 0;
+
+    virtual void WriteRawTile(const std::string& tile,
+                              ImageCompression compression,
+                              unsigned int level,
+                              unsigned int tileX,
+                              unsigned int tileY) = 0;
+
+    virtual void EncodeTile(const Orthanc::ImageAccessor& tile,
+                            unsigned int level,
+                            unsigned int tileX, 
+                            unsigned int tileY) = 0;
+  };
+}
diff --git a/Framework/Outputs/InMemoryTiledImage.cpp b/Framework/Outputs/InMemoryTiledImage.cpp
new file mode 100644
index 0000000..1990e09
--- /dev/null
+++ b/Framework/Outputs/InMemoryTiledImage.cpp
@@ -0,0 +1,172 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "InMemoryTiledImage.h"
+
+#include "../ImageToolbox.h"
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancWSI
+{
+  static void CheckLevel(unsigned int level)
+  {
+    if (level != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  InMemoryTiledImage::InMemoryTiledImage(Orthanc::PixelFormat format,
+                                         unsigned int countTilesX,
+                                         unsigned int countTilesY,
+                                         unsigned int tileWidth,
+                                         unsigned int tileHeight) :
+    format_(format),
+    countTilesX_(countTilesX),
+    countTilesY_(countTilesY),
+    tileWidth_(tileWidth),
+    tileHeight_(tileHeight)
+  {
+  }
+
+
+  InMemoryTiledImage::~InMemoryTiledImage()
+  {
+    for (Tiles::iterator it = tiles_.begin(); it != tiles_.end(); ++it)
+    {
+      delete it->second;
+    }
+  }
+
+
+  unsigned int InMemoryTiledImage::GetCountTilesX(unsigned int level) const
+  {
+    CheckLevel(level);
+    return countTilesX_;
+  }
+
+
+  unsigned int InMemoryTiledImage::GetCountTilesY(unsigned int level) const
+  {
+    CheckLevel(level);
+    return countTilesY_;
+  }
+
+
+  unsigned int InMemoryTiledImage::GetLevelWidth(unsigned int level) const
+  {
+    CheckLevel(level);
+    return tileWidth_ * countTilesX_;
+  }
+
+
+  unsigned int InMemoryTiledImage::GetLevelHeight(unsigned int level) const
+  { 
+    CheckLevel(level);
+    return tileHeight_ * countTilesY_;
+  }
+
+
+  bool InMemoryTiledImage::ReadRawTile(std::string& tile,
+                                       unsigned int level,
+                                       unsigned int tileX,
+                                       unsigned int tileY)
+  {
+    CheckLevel(level);
+    return false;  // Unavailable
+  }
+
+
+  Orthanc::ImageAccessor* InMemoryTiledImage::DecodeTile(unsigned int level,
+                                                         unsigned int tileX,
+                                                         unsigned int tileY)
+  {
+    CheckLevel(level);
+
+    if (tileX >= countTilesX_ ||
+        tileY >= countTilesY_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      Tiles::const_iterator it = tiles_.find(std::make_pair(tileX, tileY));
+      if (it != tiles_.end())
+      {
+        return new Orthanc::ImageAccessor(*it->second);
+      }
+      else
+      {
+        LOG(ERROR) << "The following tile has not been set: " << tileX << "," << tileY;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void InMemoryTiledImage::WriteRawTile(const std::string& raw,
+                                        ImageCompression compression,
+                                        unsigned int level,
+                                        unsigned int tileX,
+                                        unsigned int tileY)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(raw, compression));
+    EncodeTile(*decoded, level, tileX, tileY);
+  }
+      
+
+  void InMemoryTiledImage::EncodeTile(const Orthanc::ImageAccessor& tile,
+                                      unsigned int level,
+                                      unsigned int tileX,
+                                      unsigned int tileY)
+  {
+    CheckLevel(level);
+
+    if (tileX >= countTilesX_ ||
+        tileY >= countTilesY_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      Tiles::iterator it = tiles_.find(std::make_pair(tileX, tileY));
+      if (it == tiles_.end())
+      {
+        tiles_[std::make_pair(tileX, tileY)] = ImageToolbox::Clone(tile);
+      }
+      else
+      {
+        if (it->second)
+        {
+          delete it->second;
+        }
+
+        it->second = ImageToolbox::Clone(tile);
+      }
+    }
+  }
+}
diff --git a/Framework/Outputs/InMemoryTiledImage.h b/Framework/Outputs/InMemoryTiledImage.h
new file mode 100644
index 0000000..da471ae
--- /dev/null
+++ b/Framework/Outputs/InMemoryTiledImage.h
@@ -0,0 +1,109 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Inputs/ITiledPyramid.h"
+#include "../Outputs/IPyramidWriter.h"
+
+#include <map>
+#include <boost/thread.hpp>
+
+namespace OrthancWSI
+{
+  class InMemoryTiledImage : 
+    public ITiledPyramid, 
+    public IPyramidWriter
+  {
+  private:
+    typedef std::pair<unsigned int, unsigned int>        Location;
+    typedef std::map<Location, Orthanc::ImageAccessor*>  Tiles;
+
+    boost::mutex          mutex_;
+    Orthanc::PixelFormat  format_;
+    unsigned int          countTilesX_;
+    unsigned int          countTilesY_;
+    unsigned int          tileWidth_;
+    unsigned int          tileHeight_;
+    Tiles                 tiles_;
+
+  public:
+    InMemoryTiledImage(Orthanc::PixelFormat format,
+                       unsigned int countTilesX,
+                       unsigned int countTilesY,
+                       unsigned int tileWidth,
+                       unsigned int tileHeight);
+
+    virtual ~InMemoryTiledImage();
+
+    virtual unsigned int GetLevelCount() const
+    {
+      return 1;
+    }
+
+    virtual unsigned int GetCountTilesX(unsigned int level) const;
+
+    virtual unsigned int GetCountTilesY(unsigned int level) const;
+
+    virtual unsigned int GetLevelWidth(unsigned int level) const;
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const;
+
+    virtual unsigned int GetTileWidth() const
+    {
+      return tileWidth_;
+    }
+
+    virtual unsigned int GetTileHeight() const
+    {
+      return tileHeight_;
+    }
+
+    virtual bool ReadRawTile(std::string& tile,
+                             unsigned int level,
+                             unsigned int tileX,
+                             unsigned int tileY);
+
+    virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level,
+                                               unsigned int tileX,
+                                               unsigned int tileY);
+
+    virtual ImageCompression GetImageCompression() const
+    {
+      return ImageCompression_None;
+    }
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return format_;
+    }
+
+    virtual void WriteRawTile(const std::string& raw,
+                              ImageCompression compression,
+                              unsigned int level,
+                              unsigned int tileX,
+                              unsigned int tileY);
+
+    virtual void EncodeTile(const Orthanc::ImageAccessor& tile,
+                            unsigned int level,
+                            unsigned int tileX,
+                            unsigned int tileY);
+  };
+}
diff --git a/Framework/Outputs/MultiframeDicomWriter.cpp b/Framework/Outputs/MultiframeDicomWriter.cpp
new file mode 100644
index 0000000..c3dfb89
--- /dev/null
+++ b/Framework/Outputs/MultiframeDicomWriter.cpp
@@ -0,0 +1,305 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "MultiframeDicomWriter.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Logging.h"
+#include "../DicomToolbox.h"
+
+#include <dcmtk/dcmdata/dcuid.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcvrat.h>
+
+#include <boost/lexical_cast.hpp>
+
+#if DCMTK_VERSION_NUMBER <= 360
+#  define EXS_JPEGProcess1      EXS_JPEGProcess1TransferSyntax
+#endif
+
+
+namespace OrthancWSI
+{
+  static void SaveDicomToMemory(std::string& target,
+                                DcmFileFormat& dicom,
+                                E_TransferSyntax transferSyntax)
+  {
+    dicom.validateMetaInfo(transferSyntax);
+    dicom.removeInvalidGroups();
+
+    E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
+
+    // Create a memory buffer with the proper size
+    {
+      const uint32_t estimatedSize = dicom.calcElementLength(transferSyntax, encodingType);  // (*)
+      target.resize(estimatedSize);
+    }
+
+    DcmOutputBufferStream ob(&target[0], target.size());
+
+    // Fill the memory buffer with the meta-header and the dataset
+    dicom.transferInit();
+    OFCondition c = dicom.write(ob, transferSyntax, encodingType, NULL,
+                                /*opt_groupLength*/ EGL_recalcGL,
+                                /*opt_paddingType*/ EPD_withoutPadding);
+    dicom.transferEnd();
+
+    if (!c.good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    // The DICOM file is successfully written, truncate the target
+    // buffer if its size was overestimated by (*)
+    ob.flush();
+
+    size_t effectiveSize = static_cast<size_t>(ob.tell());
+    if (effectiveSize < target.size())
+    {
+      target.resize(effectiveSize);
+    }
+  }
+
+
+  void MultiframeDicomWriter::ResetImage()
+  {
+    perFrameFunctionalGroups_.reset(new DcmSequenceOfItems(DCM_PerFrameFunctionalGroupsSequence));
+
+    if (compression_ != ImageCompression_None)
+    {
+      compressedPixelSequence_.reset(new DcmPixelSequence(DcmTag(DCM_PixelData, EVR_OB)));
+
+      offsetTable_ = new DcmPixelItem(DCM_Item, EVR_OB);
+      compressedPixelSequence_->insert(offsetTable_);
+      offsetList_.reset(new DcmOffsetList);
+    }
+
+    writtenSize_ = 0;
+    framesCount_ = 0;
+  }
+
+
+  void MultiframeDicomWriter::InjectUncompressedPixelData(DcmFileFormat& dicom)
+  {
+    static const size_t GIGABYTE = 1024 * 1024 * 1024;
+
+    std::string pixelData;
+    uncompressedPixelData_.Flatten(pixelData);
+
+    // Prevent the creation of too large DICOM files
+    // (uncompressed DICOM files are limited to 2GB)
+    if (pixelData.size() > GIGABYTE)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+
+    std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(DCM_PixelData));
+
+    uint8_t* target = NULL;
+    if (!pixels->createUint8Array(pixelData.size(), target).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (pixelData.size() > 0)
+    {
+      if (target == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      // TODO Avoid this memcpy()
+      memcpy(target, pixelData.c_str(), pixelData.size());
+    }
+
+    if (!dicom.getDataset()->insert(pixels.release(), false, false).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  MultiframeDicomWriter::MultiframeDicomWriter(const DcmDataset& dataset,
+                                               ImageCompression compression,
+                                               Orthanc::PixelFormat pixelFormat,
+                                               unsigned int width,
+                                               unsigned int height,
+                                               unsigned int tileWidth,
+                                               unsigned int tileHeight) :
+    compression_(compression),
+    width_(width),
+    height_(height)
+  {
+    switch (compression)
+    {
+      case ImageCompression_None:
+        transferSyntax_ = EXS_LittleEndianImplicit;
+        break;
+
+      case ImageCompression_Jpeg:
+        // Default transfer syntax for lossy JPEG 8bit compression
+        transferSyntax_ = EXS_JPEGProcess1;
+        break;
+
+      case ImageCompression_Jpeg2000:
+        // JPEG2000 compression (lossless only)
+        transferSyntax_ = EXS_JPEG2000LosslessOnly;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (!sharedTags_.copyFrom(dataset).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    // We only take care of grayscale or RGB images in 8bpp
+    DicomToolbox::SetUint32Tag(sharedTags_, DCM_TotalPixelMatrixColumns, width);
+    DicomToolbox::SetUint32Tag(sharedTags_, DCM_TotalPixelMatrixRows, height);
+    DicomToolbox::SetUint16Tag(sharedTags_, DCM_PlanarConfiguration, 0);  // Interleaved RGB values
+    DicomToolbox::SetUint16Tag(sharedTags_, DCM_Columns, tileWidth);
+    DicomToolbox::SetUint16Tag(sharedTags_, DCM_Rows, tileHeight);
+    DicomToolbox::SetUint16Tag(sharedTags_, DCM_BitsAllocated, 8);
+    DicomToolbox::SetUint16Tag(sharedTags_, DCM_BitsStored, 8);
+    DicomToolbox::SetUint16Tag(sharedTags_, DCM_HighBit, 7);
+    DicomToolbox::SetUint16Tag(sharedTags_, DCM_PixelRepresentation, 0);   // Unsigned values
+
+    switch (pixelFormat)
+    {
+      case Orthanc::PixelFormat_RGB24:
+        uncompressedFrameSize_ = 3 * tileWidth * tileHeight;
+        DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 3);
+
+        if (compression_ == ImageCompression_Jpeg)
+        {
+          DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "YBR_FULL_422");
+        }
+        else
+        {
+          DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "RGB");
+        }
+
+        break;
+
+      case Orthanc::PixelFormat_Grayscale8:
+        uncompressedFrameSize_ = tileWidth * tileHeight;
+        DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 1);
+        DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "MONOCHROME2");
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    ResetImage();
+  }
+
+
+  void MultiframeDicomWriter::AddFrame(const std::string& frame,
+                                       DcmItem* functionalGroup)   // This takes the ownership
+  {
+    // Free the functional group on error
+    std::auto_ptr<DcmItem> functionalGroupRaii(functionalGroup);
+
+    if (compression_ == ImageCompression_None)
+    {
+      if (frame.size() != uncompressedFrameSize_)
+      {
+        LOG(ERROR) << "An uncompressed frame has not the proper size: " 
+                   << frame.size() << " instead of " << uncompressedFrameSize_;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+      }
+
+      uncompressedPixelData_.AddChunk(frame);
+    }
+    else
+    {
+      uint8_t* bytes = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(frame.c_str()));
+
+      compressedPixelSequence_->storeCompressedFrame(*offsetList_, bytes, frame.size(), 
+                                                     0 /* unlimited fragment size */);
+    }
+
+    if (functionalGroup != NULL)
+    {
+      perFrameFunctionalGroups_->insert(functionalGroupRaii.release());
+    }
+    else
+    {
+      perFrameFunctionalGroups_->insert(new DcmItem);
+    }
+
+    writtenSize_ += frame.size();
+    framesCount_ += 1;
+  }
+
+
+  void MultiframeDicomWriter::Flush(std::string& target,
+                                    unsigned int instanceNumber)
+  {
+    if (instanceNumber <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    std::string tmp = boost::lexical_cast<std::string>(instanceNumber);
+
+    std::auto_ptr<DcmFileFormat> dicom(new DcmFileFormat);
+
+    char uid[100];
+    dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
+
+    if (!dicom->getDataset()->copyFrom(sharedTags_).good() ||
+        !dicom->getDataset()->insert(perFrameFunctionalGroups_.release(), false, false).good() ||
+        !dicom->getDataset()->putAndInsertString(DCM_SOPInstanceUID, uid).good() ||
+        !dicom->getDataset()->putAndInsertString(DCM_InstanceNumber, tmp.c_str()).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    DicomToolbox::SetStringTag(*dicom->getDataset(), DCM_NumberOfFrames, boost::lexical_cast<std::string>(framesCount_));
+
+    switch (compression_)
+    {
+      case ImageCompression_None:
+        InjectUncompressedPixelData(*dicom);
+        break;
+
+      case ImageCompression_Jpeg:
+      case ImageCompression_Jpeg2000:
+        offsetTable_->createOffsetTable(*offsetList_);
+        dicom->getDataset()->insert(compressedPixelSequence_.release());
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    ResetImage();
+
+    SaveDicomToMemory(target, *dicom, transferSyntax_);
+  }
+}
diff --git a/Framework/Outputs/MultiframeDicomWriter.h b/Framework/Outputs/MultiframeDicomWriter.h
new file mode 100644
index 0000000..5aafc3e
--- /dev/null
+++ b/Framework/Outputs/MultiframeDicomWriter.h
@@ -0,0 +1,93 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+#include "../Orthanc/Core/ChunkedBuffer.h"
+
+#include <boost/noncopyable.hpp>
+#include <memory>
+#include <stdint.h>
+
+#include <dcmtk/dcmdata/dcpixseq.h>
+#include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+
+namespace OrthancWSI
+{
+  class MultiframeDicomWriter : public boost::noncopyable
+  {
+  private:
+    ImageCompression  compression_;
+    E_TransferSyntax  transferSyntax_;
+    DcmDataset        sharedTags_;
+    size_t            writtenSize_;
+    size_t            framesCount_;
+    size_t            uncompressedFrameSize_;
+    unsigned int      width_;
+    unsigned int      height_;
+
+    Orthanc::ChunkedBuffer             uncompressedPixelData_;
+    std::auto_ptr<DcmSequenceOfItems>  perFrameFunctionalGroups_;
+    std::auto_ptr<DcmPixelSequence>    compressedPixelSequence_;
+    DcmPixelItem*                      offsetTable_;
+    std::auto_ptr<DcmOffsetList>       offsetList_;
+
+    void ResetImage();
+
+    void InjectUncompressedPixelData(DcmFileFormat& dicom);
+
+  public:
+    MultiframeDicomWriter(const DcmDataset& dataset,
+                          ImageCompression compression,
+                          Orthanc::PixelFormat pixelFormat,
+                          unsigned int width,
+                          unsigned int height,
+                          unsigned int tileWidth,
+                          unsigned int tileHeight);
+
+    void AddFrame(const std::string& frame,
+                  DcmItem* functionalGroup);   // This takes the ownership
+
+    void Flush(std::string& target,
+               unsigned int instanceNumber);
+
+    unsigned int GetFramesCount() const
+    {
+      return framesCount_;
+    }
+
+    size_t GetSize() const
+    {
+      return writtenSize_;
+    }
+
+    unsigned int GetTotalWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetTotalHeight() const
+    {
+      return height_;
+    }
+  };
+}
diff --git a/Framework/Outputs/PyramidWriterBase.cpp b/Framework/Outputs/PyramidWriterBase.cpp
new file mode 100644
index 0000000..b7b91bc
--- /dev/null
+++ b/Framework/Outputs/PyramidWriterBase.cpp
@@ -0,0 +1,151 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "PyramidWriterBase.h"
+
+#include "../ImageToolbox.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Logging.h"
+
+namespace OrthancWSI
+{
+  PyramidWriterBase::Level PyramidWriterBase::GetLevel(unsigned int level) const
+  {
+    boost::mutex::scoped_lock lock(const_cast<PyramidWriterBase&>(*this).mutex_);
+
+    if (level >= levels_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return levels_[level];
+    }
+  }
+
+
+  PyramidWriterBase::PyramidWriterBase(Orthanc::PixelFormat pixelFormat,
+                                       ImageCompression compression,
+                                       unsigned int tileWidth,
+                                       unsigned int tileHeight) :
+    pixelFormat_(pixelFormat),
+    compression_(compression),
+    tileWidth_(tileWidth),
+    tileHeight_(tileHeight),
+    jpegQuality_(90),   // Default JPEG quality
+    first_(true)
+  {
+  }
+
+
+  void PyramidWriterBase::SetJpegQuality(int quality)
+  {
+    if (quality <= 0 || quality > 100)
+    {
+      LOG(ERROR) << "The JPEG quality must be in range [1;100], but " << quality << " is provided";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    jpegQuality_ = quality;
+  }
+
+
+  void PyramidWriterBase::AddLevel(unsigned int width,
+                                   unsigned int height)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+      
+    if (!first_)
+    {
+      LOG(ERROR) << "Cannot add pyramid levels after some tile has already been written";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (!levels_.empty())
+    {
+      const Level& previous = levels_[levels_.size() - 1];
+
+      if (width >= previous.width_ ||
+          height >= previous.height_ ||
+          width == 0 ||
+          height == 0)
+      {
+        LOG(ERROR) << "Levels must have strictly decreasing sizes";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    Level level;
+    level.z_ = levels_.size();
+    level.width_ = width;
+    level.height_ = height;
+    level.countTilesX_ = CeilingDivision(width, tileWidth_);
+    level.countTilesY_ = CeilingDivision(height, tileHeight_);
+    levels_.push_back(level);
+
+    AddLevelInternal(level);
+  }
+
+
+  unsigned int PyramidWriterBase::GetLevelCount() const
+  {
+    boost::mutex::scoped_lock lock(const_cast<PyramidWriterBase&>(*this).mutex_);
+    return levels_.size();
+  }
+
+
+  void PyramidWriterBase::WriteRawTile(const std::string& tile,
+                                       ImageCompression compression,
+                                       unsigned int z,
+                                       unsigned int x,
+                                       unsigned int y)
+  {
+    first_ = false;
+
+    const Level level = GetLevel(z);
+
+    if (compression != compression_)
+    {
+      std::string recoded;
+      ImageToolbox::ChangeTileCompression(recoded, tile, compression, compression_, jpegQuality_);
+      WriteRawTileInternal(recoded, level, x, y);
+    }
+    else
+    {
+      WriteRawTileInternal(tile, level, x, y);
+    }
+  }
+
+
+  void PyramidWriterBase::EncodeTile(const Orthanc::ImageAccessor& tile,
+                                     unsigned int z,
+                                     unsigned int x, 
+                                     unsigned int y)
+  {
+    first_ = false;
+
+    const Level level = GetLevel(z);
+
+    std::string raw;
+    ImageToolbox::EncodeTile(raw, tile, compression_, jpegQuality_);
+    WriteRawTileInternal(raw, level, x, y);
+  }
+}
diff --git a/Framework/Outputs/PyramidWriterBase.h b/Framework/Outputs/PyramidWriterBase.h
new file mode 100644
index 0000000..7d0f218
--- /dev/null
+++ b/Framework/Outputs/PyramidWriterBase.h
@@ -0,0 +1,124 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IPyramidWriter.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancWSI
+{
+  class PyramidWriterBase : public IPyramidWriter
+  {
+  protected:
+    struct Level
+    {
+      unsigned int  z_;
+      unsigned int  width_;
+      unsigned int  height_;
+      unsigned int  countTilesX_;
+      unsigned int  countTilesY_;
+    };
+
+    // WARNING: The following method can be called from multiple
+    // threads, locking must be implemented in derived classes.
+    virtual void WriteRawTileInternal(const std::string& tile,
+                                      const Level& level,
+                                      unsigned int tileX,
+                                      unsigned int tileY) = 0;
+
+    // This function is invoked before any call to WriteRawTileInternal()
+    virtual void AddLevelInternal(const Level& level) = 0;
+
+  private:
+    boost::mutex          mutex_;   // This mutex protects access to the levels
+    Orthanc::PixelFormat  pixelFormat_;
+    ImageCompression      compression_;
+    unsigned int          tileWidth_;
+    unsigned int          tileHeight_;
+    uint8_t               jpegQuality_;
+    std::vector<Level>    levels_;
+    bool                  first_;
+
+    Level GetLevel(unsigned int level) const;
+
+  public:
+    PyramidWriterBase(Orthanc::PixelFormat pixelFormat,
+                      ImageCompression compression,
+                      unsigned int tileWidth,
+                      unsigned int tileHeight);
+
+    uint8_t GetJpegQuality() const
+    {
+      return jpegQuality_;
+    }
+
+    void SetJpegQuality(int quality);
+
+    virtual void AddLevel(unsigned int width,
+                          unsigned int height);
+
+    virtual unsigned int GetTileWidth() const
+    {
+      return tileWidth_;
+    }
+
+    virtual unsigned int GetTileHeight() const 
+    {
+      return tileHeight_;
+    }
+
+    virtual unsigned int GetCountTilesX(unsigned int level) const
+    {
+      return GetLevel(level).countTilesX_;
+    }
+
+    virtual unsigned int GetCountTilesY(unsigned int level) const
+    {
+      return GetLevel(level).countTilesY_;
+    }
+
+    virtual unsigned int GetLevelCount() const;
+
+    ImageCompression GetImageCompression() const
+    {
+      return compression_;
+    }
+
+    virtual void WriteRawTile(const std::string& tile,
+                              ImageCompression compression,
+                              unsigned int z,
+                              unsigned int tileX,
+                              unsigned int tileY);
+
+    virtual void EncodeTile(const Orthanc::ImageAccessor& tile,
+                            unsigned int z,
+                            unsigned int tileX, 
+                            unsigned int tileY);
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const
+    {
+      return pixelFormat_;
+    }
+
+    virtual void Flush() = 0;
+  };
+}
diff --git a/Framework/Outputs/TruncatedPyramidWriter.cpp b/Framework/Outputs/TruncatedPyramidWriter.cpp
new file mode 100644
index 0000000..ec6f9ab
--- /dev/null
+++ b/Framework/Outputs/TruncatedPyramidWriter.cpp
@@ -0,0 +1,118 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersWSI.h"
+#include "TruncatedPyramidWriter.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancWSI
+{
+  TruncatedPyramidWriter::TruncatedPyramidWriter(IPyramidWriter& lower,
+                                                 unsigned int upperLevelIndex) :
+    lowerLevels_(lower),
+    upperLevel_(lower.GetPixelFormat(),
+                lower.GetCountTilesX(upperLevelIndex),
+                lower.GetCountTilesY(upperLevelIndex),
+                lower.GetTileWidth(),
+                lower.GetTileHeight()),
+    upperLevelIndex_(upperLevelIndex)
+  {
+    if (upperLevelIndex > lower.GetLevelCount())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int TruncatedPyramidWriter::GetCountTilesX(unsigned int level) const
+  {
+    if (level < upperLevelIndex_)
+    {
+      return lowerLevels_.GetCountTilesX(level);
+    }
+    else if (level == upperLevelIndex_)
+    {
+      return upperLevel_.GetCountTilesX(0);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int TruncatedPyramidWriter::GetCountTilesY(unsigned int level) const
+  {
+    if (level < upperLevelIndex_)
+    {
+      return lowerLevels_.GetCountTilesY(level);
+    }
+    else if (level == upperLevelIndex_)
+    {
+      return upperLevel_.GetCountTilesY(0);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void TruncatedPyramidWriter::WriteRawTile(const std::string& tile,
+                                            ImageCompression compression,
+                                            unsigned int level,
+                                            unsigned int x,
+                                            unsigned int y)
+  {
+    if (level < upperLevelIndex_)
+    {
+      lowerLevels_.WriteRawTile(tile, compression, level, x, y);
+    }
+    else if (level == upperLevelIndex_)
+    {
+      upperLevel_.WriteRawTile(tile, compression, 0, x, y);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void TruncatedPyramidWriter::EncodeTile(const Orthanc::ImageAccessor& tile,
+                                          unsigned int level,
+                                          unsigned int x, 
+                                          unsigned int y)
+  {
+    if (level < upperLevelIndex_)
+    {
+      lowerLevels_.EncodeTile(tile, level, x, y);
+    }
+    else if (level == upperLevelIndex_)
+    {
+      upperLevel_.EncodeTile(tile, 0, x, y);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
diff --git a/Framework/Outputs/TruncatedPyramidWriter.h b/Framework/Outputs/TruncatedPyramidWriter.h
new file mode 100644
index 0000000..ce50cd0
--- /dev/null
+++ b/Framework/Outputs/TruncatedPyramidWriter.h
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "InMemoryTiledImage.h"
+
+namespace OrthancWSI
+{
+  class TruncatedPyramidWriter : public IPyramidWriter
+  {
+  private:
+    IPyramidWriter&     lowerLevels_;
+    InMemoryTiledImage  upperLevel_;
+    unsigned int        upperLevelIndex_;
+    
+  public:
+    TruncatedPyramidWriter(IPyramidWriter& lower,
+                           unsigned int upperLevelIndex);
+
+    virtual unsigned int GetLevelCount() const
+    {
+      return upperLevelIndex_ + 1;
+    }
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const 
+    {
+      return lowerLevels_.GetPixelFormat();
+    }
+
+    virtual unsigned int GetTileWidth() const
+    {
+      return lowerLevels_.GetTileWidth();
+    }
+
+    virtual unsigned int GetTileHeight() const 
+    {
+      return lowerLevels_.GetTileHeight();
+    }
+
+    virtual unsigned int GetCountTilesX(unsigned int level) const;
+
+    virtual unsigned int GetCountTilesY(unsigned int level) const;
+
+    virtual void WriteRawTile(const std::string& tile,
+                              ImageCompression compression,
+                              unsigned int level,
+                              unsigned int x,
+                              unsigned int y);
+
+    virtual void EncodeTile(const Orthanc::ImageAccessor& tile,
+                            unsigned int level,
+                            unsigned int x, 
+                            unsigned int y);
+
+    InMemoryTiledImage& GetUpperLevel()
+    {
+      return upperLevel_;
+    }
+  };
+}
diff --git a/Framework/PrecompiledHeadersWSI.cpp b/Framework/PrecompiledHeadersWSI.cpp
new file mode 100644
index 0000000..afef2e6
--- /dev/null
+++ b/Framework/PrecompiledHeadersWSI.cpp
@@ -0,0 +1,21 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersWSI.h"
diff --git a/Framework/PrecompiledHeadersWSI.h b/Framework/PrecompiledHeadersWSI.h
new file mode 100644
index 0000000..4232c01
--- /dev/null
+++ b/Framework/PrecompiledHeadersWSI.h
@@ -0,0 +1,34 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
+
+#include "Orthanc/Core/PrecompiledHeaders.h"
+
+#include "DicomToolbox.h"
+#include "ImageToolbox.h"
+#include "Inputs/ITiledPyramid.h"
+#include "Messaging/IFileTarget.h"
+#include "Messaging/IOrthancConnection.h"
+#include "Outputs/IPyramidWriter.h"
+
+#endif
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..0e56822
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,25 @@
+Pending changes in the mainline
+===============================
+
+
+
+Version 0.1 (2016/10/28)
+========================
+
+* Docker images are available
+* Provide "--version" flag in command-line tools
+* Fix freeze if the target Orthanc is not accepting images
+
+Maintenance
+-----------
+
+* Windows resources for command-line tools
+* Build under Apple OS X
+* Build using Microsoft Visual Studio
+* Automatic build in CIS
+
+
+2016-10-22
+==========
+
+* Initial release
diff --git a/README b/README
new file mode 100644
index 0000000..079363f
--- /dev/null
+++ b/README
@@ -0,0 +1,88 @@
+Orthanc for Whole-Slide Imaging
+===============================
+
+
+General Information
+-------------------
+
+This repository contains the source code of the official tools to
+introduce support of whole-slide microscopic imaging (WSI) in
+Orthanc. It is made of three separate components:
+
+(a) A command-line tool called "Dicomizer" that converts whole-slide
+    images to DICOM, according to Supplement 145. 
+
+    The input images of the Dicomizer can be either formatted as
+    standard hierarchical TIFF images, or as proprietary formats for
+    digital pathology. In the latter case, OpenSlide is used to decode
+    the input images.
+
+(b) An Orthanc plugin that extends Orthanc Explorer with a Web viewer
+    of whole-slide images.
+
+(c) A command-line tool called "DicomToTiff" that converts some
+    whole-slide image stored in an Orthanc server, into a standard
+    hierarchical TIFF file. This tool can be used to export DICOM
+    images to any post-processing framework.
+
+
+
+Dependencies
+------------
+
+The whole-slide imaging framework is notably based upon the following
+projects:
+
+* Orthanc, a lightweight Vendor Neutral Archive (DICOM server):
+  http://www.orthanc-server.com/
+
+* OpenSlide, a simple interface to read whole-slide images encoded
+  using proprietary file formats:
+  http://openslide.org/
+
+* OpenLayers, a framework to put a dynamic map in any web page:
+  https://openlayers.org/
+
+* OpenJPEG, an open-source JPEG 2000 codec:
+  http://www.openjpeg.org/
+
+
+
+Installation and usage
+----------------------
+
+Build instructions are similar to that of Orthanc:
+https://orthanc.chu.ulg.ac.be/book/faq/compiling.html
+
+The two command-line tools can be found in folder "Applications".
+They come with an extensive "--help" option.
+
+The Web viewer plugin can be found in folder "ViewerPlugin". It has no
+specific option, so you just have to make your "Plugins" configuration
+option of Orthanc point to the shared library containing the plugin:
+https://orthanc.chu.ulg.ac.be/book/users/configuration.html
+
+
+
+Licensing
+---------
+
+The WSI toolbox for Orthanc is licensed under the AGPL license.
+
+We also kindly ask scientific works and clinical studies that make
+use of Orthanc to cite Orthanc in their associated publications.
+Similarly, we ask open-source and closed-source products that make
+use of Orthanc to warn us about this use. You can cite our work
+using the following BibTeX entry:
+
+ at inproceedings{Jodogne:ISBI2013,
+  author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.},
+  title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research},
+  booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, 
+  year={2013}, 
+  pages={190-193}, 
+  ISSN={1945-7928},
+  month=apr,
+  url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444},
+  address={San Francisco, {CA}, {USA}}
+}
diff --git a/Resources/BrightfieldOpticalPath.json b/Resources/BrightfieldOpticalPath.json
new file mode 100644
index 0000000..83f2cae
--- /dev/null
+++ b/Resources/BrightfieldOpticalPath.json
@@ -0,0 +1,20 @@
+[
+  {
+    "OpticalPathIdentifier" : "1",
+    "OpticalPathDescription" : "Brightfield",
+    "IlluminationTypeCodeSequence" : [
+      {
+        "CodeValue" : "111744",
+        "CodingSchemeDesignator" : "DCM",
+        "CodeMeaning" : "Brightfield illumination"
+      }
+    ],
+    "IlluminationColorCodeSequence" : [
+      {
+        "CodeValue" : "R-102C0",
+        "CodingSchemeDesignator" : "SRT",
+        "CodeMeaning" : "Full Spectrum"
+      }
+    ]
+  }
+]
diff --git a/Resources/CMake/BoostExtendedConfiguration.cmake b/Resources/CMake/BoostExtendedConfiguration.cmake
new file mode 100644
index 0000000..6417761
--- /dev/null
+++ b/Resources/CMake/BoostExtendedConfiguration.cmake
@@ -0,0 +1,21 @@
+SET(ORTHANC_BOOST_COMPONENTS program_options)
+
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+
+if (BOOST_STATIC)
+  list(APPEND BOOST_SOURCES
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/options_description.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/parsers.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/positional_options.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/split.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp
+    #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp
+    )
+  add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB)
+endif()
+
diff --git a/Resources/CMake/LibTiffConfiguration.cmake b/Resources/CMake/LibTiffConfiguration.cmake
new file mode 100644
index 0000000..d35580e
--- /dev/null
+++ b/Resources/CMake/LibTiffConfiguration.cmake
@@ -0,0 +1,125 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBTIFF)
+  SET(LIBTIFF_SOURCES_DIR ${CMAKE_BINARY_DIR}/tiff-4.0.6)
+  SET(LIBTIFF_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/WSI/tiff-4.0.6.tar.gz")
+  SET(LIBTIFF_MD5 "d1d2e940dea0b5ad435f21f03d96dd72")
+
+  DownloadPackage(${LIBTIFF_MD5} ${LIBTIFF_URL} "${LIBTIFF_SOURCES_DIR}")
+
+  if (NOT EXISTS ${LIBTIFF_SOURCES_DIR}/libtiff/tif_config.h)
+    file(WRITE ${LIBTIFF_SOURCES_DIR}/libtiff/tif_config.h "
+#include <stdint.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <string.h>
+")
+    file(WRITE ${LIBTIFF_SOURCES_DIR}/libtiff/tiffconf.h "
+#if defined(_MSC_VER)
+#  if !defined(ssize_t)
+#    define WIN32_LEAN_AND_MEAN
+#    include <windows.h>
+#    define ssize_t SSIZE_T
+#  endif
+#  if !defined(snprintf)
+#    define snprintf _snprintf
+#  endif
+#endif
+
+#include <stdint.h>
+#include <sys/types.h>
+")
+  endif()
+
+  set(TIFF_FILLORDER FILLORDER_MSB2LSB)
+  if (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i.*86.*" OR
+      CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64.*" OR
+      CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64.*")
+    set(TIFF_FILLORDER FILLORDER_LSB2MSB)
+  endif()
+
+  add_definitions(
+    -DTIFF_INT8_T=int8_t
+    -DTIFF_INT16_T=int16_t
+    -DTIFF_INT32_T=int32_t
+    -DTIFF_INT64_T=int64_t
+    -DTIFF_UINT8_T=uint8_t
+    -DTIFF_UINT16_T=uint16_t
+    -DTIFF_UINT32_T=uint32_t
+    -DTIFF_UINT64_T=uint64_t
+    -DTIFF_SSIZE_T=ssize_t
+    -DHAVE_IEEEFP=1
+    -DHOST_FILLORDER=${TIFF_FILLORDER}
+    -DHAVE_SNPRINTF=1
+    -DJPEG_SUPPORT=1
+    -DLZW_SUPPORT=1
+
+    -DTIFF_INT64_FORMAT="%lld"
+    -DTIFF_UINT64_FORMAT="%llu"
+    -DTIFF_SSIZE_FORMAT="%d"
+    )
+
+  set(LIBTIFF_SOURCES
+    #${LIBTIFF_SOURCES_DIR}/libtiff/mkg3states.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_aux.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_close.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_codec.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_color.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_compress.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dir.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dirinfo.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dirread.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dirwrite.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dumpmode.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_error.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_extension.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_fax3.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_fax3sm.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_flush.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_getimage.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_jbig.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_jpeg_12.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_jpeg.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_luv.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_lzma.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_lzw.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_next.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_ojpeg.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_open.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_packbits.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_pixarlog.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_predict.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_print.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_read.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_strip.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_swab.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_thunder.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_tile.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_unix.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_version.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_warning.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_write.c
+    ${LIBTIFF_SOURCES_DIR}/libtiff/tif_zip.c
+    )
+
+  include_directories(${LIBTIFF_SOURCES_DIR}/libtiff)
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "Windows")
+    list(APPEND LIBTIFF_SOURCES
+      ${LIBTIFF_SOURCES_DIR}/libtiff/tif_win32.c
+      )
+  endif()
+
+  source_group(ThirdParty\\libtiff REGULAR_EXPRESSION ${LIBTIFF_SOURCES_DIR}/.*)
+
+else()
+  CHECK_INCLUDE_FILE_CXX(tiff.h HAVE_LIBTIFF_H)
+  if (NOT HAVE_LIBTIFF_H)
+    message(FATAL_ERROR "Please install the libtiff-dev package")
+  endif()
+
+  CHECK_LIBRARY_EXISTS(tiff TIFFGetField "" HAVE_LIBTIFF_LIB)
+  if (NOT HAVE_LIBTIFF_LIB)
+    message(FATAL_ERROR "Please install the libtiff-dev package")
+  endif()
+
+  link_libraries(tiff)
+endif()
diff --git a/Resources/CMake/OpenJpegConfiguration.cmake b/Resources/CMake/OpenJpegConfiguration.cmake
new file mode 100644
index 0000000..e936559
--- /dev/null
+++ b/Resources/CMake/OpenJpegConfiguration.cmake
@@ -0,0 +1,159 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_OPENJPEG)
+  SET(OPENJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/openjpeg-version.2.1)
+  SET(OPENJPEG_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openjpeg-2.1.tar.gz")
+  SET(OPENJPEG_MD5 "3e1c451c087f8462955426da38aa3b3d")
+
+  if (IS_DIRECTORY "${OPENJPEG_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
+  DownloadPackage(${OPENJPEG_MD5} ${OPENJPEG_URL} "${OPENJPEG_SOURCES_DIR}")
+
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${CMAKE_CURRENT_LIST_DIR}/OpenJpegConfiguration.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure AND FirstRun)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+
+  if (USE_OPENJPEG_JP2)
+    set(OPENJPEG_SOURCES
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/bio.c
+      #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/cidx_manager.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/cio.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/dwt.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/event.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/function_list.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/image.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/invert.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/j2k.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/jp2.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/mct.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/mqc.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/openjpeg.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_clock.c
+      #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/phix_manager.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/pi.c
+      #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/ppix_manager.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/raw.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/t1.c
+      #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/t1_generate_luts.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/t2.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/tcd.c
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/tgt.c
+      #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/thix_manager.c
+      #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/tpix_manager.c
+      )
+
+    configure_file(
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config.h.cmake.in
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config.h
+      @ONLY
+      )
+    
+    configure_file(
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config_private.h.cmake.in
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config_private.h
+      @ONLY
+      )
+
+    include_directories(
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2
+      )
+
+    # The following definition disables explicit inlining. This is
+    # necessary to bypass the "undefined reference to
+    # `opj_t1_dec_sigpass_step_mqc'" error.
+    add_definitions(
+      #-DINLINE=
+      )
+
+  else()
+    AUX_SOURCE_DIRECTORY(${OPENJPEG_SOURCES_DIR}/src/lib/openmj2 OPENJPEG_SOURCES)
+
+    configure_file(
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config.h.cmake.in
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openmj2/opj_config.h
+      @ONLY
+      )
+    
+    configure_file(
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config_private.h.cmake.in
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openmj2/opj_config_private.h
+      @ONLY
+      )
+
+    include_directories(
+      ${OPENJPEG_SOURCES_DIR}/src/lib/openmj2
+      )
+  endif()
+
+
+  add_definitions(
+    -DOPJ_STATIC
+    -DORTHANC_OPENJPEG_MAJOR_VERSION=2
+    )
+
+  if (NOT WIN32)
+    add_definitions(
+      -DOPJ_HAVE_STDINT_H=1
+      -DOPJ_HAVE_INTTYPES_H=1
+      )
+  endif()
+
+  source_group(ThirdParty\\OpenJPEG REGULAR_EXPRESSION ${OPENJPEG_SOURCES_DIR}/.*)
+
+else()
+  find_path(OPENJPEG_INCLUDE_DIR 
+    NAMES openjpeg.h
+    PATHS
+    /usr/include/
+    /usr/include/openjpeg-2.1/
+    /usr/local/include/
+    )
+
+  CHECK_INCLUDE_FILE_CXX(${OPENJPEG_INCLUDE_DIR}/openjpeg.h HAVE_OPENJPEG_H)
+  if (NOT HAVE_OPENJPEG_H)
+    message(FATAL_ERROR "Please install the OpenJPEG development package")
+  endif()
+
+  CHECK_LIBRARY_EXISTS(openjpeg opj_image_create "" HAVE_OPENJPEG_LIB)
+  if (HAVE_OPENJPEG_LIB)
+      set(OPENJPEG_LIB openjpeg)
+  else()
+    # Search for alternative name "libopenjp2.so" that is notably used by Debian
+    CHECK_LIBRARY_EXISTS(openjp2 opj_image_create "" HAVE_OPENJP2_LIB)
+    
+    if (HAVE_OPENJP2_LIB)
+      set(OPENJPEG_LIB openjp2)
+    else()
+      message(FATAL_ERROR "Please install the OpenJPEG development package")
+    endif()
+  endif()
+
+  # Detection of the version of OpenJpeg
+  set(CMAKE_REQUIRED_INCLUDES ${OPENJPEG_INCLUDE_DIR})
+  set(CMAKE_REQUIRED_LIBRARIES ${OPENJPEG_LIB})
+
+  CHECK_SYMBOL_EXISTS(opj_destroy_decompress openjpeg.h HAVE_OPENJPEG_1)
+  if (HAVE_OPENJPEG_1)
+    message("Your system has OpenJPEG version 1")
+    add_definitions(-DORTHANC_OPENJPEG_MAJOR_VERSION=1)
+  else()
+    CHECK_SYMBOL_EXISTS(opj_destroy_codec openjpeg.h HAVE_OPENJPEG_2)
+    if (HAVE_OPENJPEG_2)
+      message("Your system has OpenJPEG version 2")
+      add_definitions(-DORTHANC_OPENJPEG_MAJOR_VERSION=2)
+    else()
+      message(FATAL_ERROR "Cannot detect your system version of OpenJPEG")
+    endif()
+  endif()
+    
+  link_libraries(${OPENJPEG_LIB})
+  include_directories(${OPENJPEG_INCLUDE_DIR})
+endif()
diff --git a/Resources/CMake/OpenJpegConfiguration.patch b/Resources/CMake/OpenJpegConfiguration.patch
new file mode 100644
index 0000000..11b568c
--- /dev/null
+++ b/Resources/CMake/OpenJpegConfiguration.patch
@@ -0,0 +1,44 @@
+diff -urEb openjpeg-version.2.1.orig/src/lib/openjp2/t1.c openjpeg-version.2.1/src/lib/openjp2/t1.c
+--- openjpeg-version.2.1.orig/src/lib/openjp2/t1.c	2016-07-15 10:36:18.575913348 +0200
++++ openjpeg-version.2.1/src/lib/openjp2/t1.c	2016-07-15 10:36:45.571914446 +0200
+@@ -85,13 +85,13 @@
+                 OPJ_INT32 orient,
+                 OPJ_INT32 oneplushalf,
+                 OPJ_INT32 vsc);
+-static INLINE void opj_t1_dec_sigpass_step_mqc(
++static void opj_t1_dec_sigpass_step_mqc(
+                 opj_t1_t *t1,
+                 opj_flag_t *flagsp,
+                 OPJ_INT32 *datap,
+                 OPJ_INT32 orient,
+                 OPJ_INT32 oneplushalf);
+-static INLINE void opj_t1_dec_sigpass_step_mqc_vsc(
++static void opj_t1_dec_sigpass_step_mqc_vsc(
+                 opj_t1_t *t1,
+                 opj_flag_t *flagsp,
+                 OPJ_INT32 *datap,
+@@ -179,20 +179,20 @@
+                                     OPJ_UINT32 vsc);
+ #endif
+ 
+-static INLINE void  opj_t1_dec_refpass_step_raw(
++static void  opj_t1_dec_refpass_step_raw(
+                 opj_t1_t *t1,
+                 opj_flag_t *flagsp,
+                 OPJ_INT32 *datap,
+                 OPJ_INT32 poshalf,
+                 OPJ_INT32 neghalf,
+                 OPJ_INT32 vsc);
+-static INLINE void opj_t1_dec_refpass_step_mqc(
++static void opj_t1_dec_refpass_step_mqc(
+                 opj_t1_t *t1,
+                 opj_flag_t *flagsp,
+                 OPJ_INT32 *datap,
+                 OPJ_INT32 poshalf,
+                 OPJ_INT32 neghalf);
+-static INLINE void opj_t1_dec_refpass_step_mqc_vsc(
++static void opj_t1_dec_refpass_step_mqc_vsc(
+                 opj_t1_t *t1,
+                 opj_flag_t *flagsp,
+                 OPJ_INT32 *datap,
+Only in openjpeg-version.2.1/src/lib/openjp2: t1.c~
diff --git a/Resources/CMake/Version.cmake b/Resources/CMake/Version.cmake
new file mode 100644
index 0000000..9291846
--- /dev/null
+++ b/Resources/CMake/Version.cmake
@@ -0,0 +1,5 @@
+set(ORTHANC_WSI_VERSION "0.1")
+
+add_definitions(
+  -DORTHANC_WSI_VERSION="${ORTHANC_WSI_VERSION}"
+  )
diff --git a/Resources/OrthancLogoDocumentation.png b/Resources/OrthancLogoDocumentation.png
new file mode 100644
index 0000000..4cf57af
Binary files /dev/null and b/Resources/OrthancLogoDocumentation.png differ
diff --git a/Resources/OrthancWSI.doxygen b/Resources/OrthancWSI.doxygen
new file mode 100644
index 0000000..b13ff26
--- /dev/null
+++ b/Resources/OrthancWSI.doxygen
@@ -0,0 +1,1794 @@
+# Doxyfile 1.8.1.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
+
+PROJECT_NAME           = OrthancWSI
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Orthanc for Whole-Slide Imaging API"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           = @ORTHANC_WSI_DIR@/Resources/OrthancLogoDocumentation.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = OrthancWSIDocumentation
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE      = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = @ORTHANC_WSI_DIR@/Framework
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = @ORTHANC_WSI_DIR@/Framework/Orthanc/Resources/ \
+                         @ORTHANC_WSI_DIR@/Framework/Orthanc/Sdk-1.0.0/
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = Orthanc::Internals \
+                         OrthancPlugins::Internals
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = doc
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+#  for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# style sheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 1
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax.
+# However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS     =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load style sheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             = ORTHANC_ENABLE_OPENSLIDE=1
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = NO
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG        = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = YES
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/Resources/SampleDataset.json b/Resources/SampleDataset.json
new file mode 100644
index 0000000..a5a0bc8
--- /dev/null
+++ b/Resources/SampleDataset.json
@@ -0,0 +1,43 @@
+{
+  "PatientID" : "C123456789",
+  "PatientName" : "SOME^PATIENT",
+  "PatientBirthDate" : "19700101",
+  "PatientSex" : "M",
+  "StudyID" : "NONE",
+  "SeriesNumber" : "1",
+  "ReferringPhysicianName" : "SOME^PHYSICIAN",
+  "AccessionNumber" : "123456789",
+  "Manufacturer" : "MyManufacturer",
+  "ManufacturerModelName" : "MyModel",
+  "DeviceSerialNumber" : "MySerialNumber",
+  "SoftwareVersions" : "MyVersion",
+
+  "ImageType" : "DERIVED\\PRIMARY\\VOLUME\\NONE",
+  "FocusMethod" : "AUTO",
+  "ExtendedDepthOfField" : "NO",
+
+  "AcquisitionContextSequence" : [
+  ],
+  "AcquisitionDuration" : "100",
+
+  "ContainerIdentifier" : "CI_12345",
+  "IssuerOfTheContainerIdentifierSequence" : [
+  ],
+  "ContainerTypeCodeSequence" : [
+  ],
+  "IssuerOfTheContainerIdentifierSequence" : [
+  ],
+
+  "SpecimenDescriptionSequence" : [
+    {
+      "SpecimenIdentifier" : "Specimen^Identifier",
+      "SpecimenUID" : "1.2.276.0.7230010.3.1.4.3252829876.4112.1426166133.871",
+      "IssuerOfTheSpecimenIdentifierSequence" : [
+      ],
+      "SpecimenPreparationSequence" : [
+      ]
+    }
+  ],
+  "SpecimenLabelInImage" : "NO",
+  "BurnedInAnnotation" : "NO"
+}
diff --git a/Resources/SyncOrthancFolder.py b/Resources/SyncOrthancFolder.py
new file mode 100755
index 0000000..eb1bc9e
--- /dev/null
+++ b/Resources/SyncOrthancFolder.py
@@ -0,0 +1,170 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import multiprocessing
+import os
+import stat
+import urllib2
+
+TARGET = os.path.join(os.path.dirname(__file__), '..', 'Framework', 'Orthanc')
+PLUGIN_SDK_VERSION = '1.0.0'
+REPOSITORY = 'http://bitbucket.org/sjodogne/orthanc/raw'
+
+FILES = [
+    'Core/ChunkedBuffer.cpp',
+    'Core/ChunkedBuffer.h',
+    'Core/DicomFormat/DicomArray.cpp',
+    'Core/DicomFormat/DicomArray.h',
+    'Core/DicomFormat/DicomElement.h',
+    'Core/DicomFormat/DicomMap.cpp',
+    'Core/DicomFormat/DicomMap.h',
+    'Core/DicomFormat/DicomTag.cpp',
+    'Core/DicomFormat/DicomTag.h',
+    'Core/DicomFormat/DicomValue.cpp',
+    'Core/DicomFormat/DicomValue.h',
+    'Core/Endianness.h',
+    'Core/EnumerationDictionary.h',
+    'Core/Enumerations.cpp',
+    'Core/Enumerations.h',
+    'Core/HttpClient.cpp',
+    'Core/HttpClient.h',
+    'Core/ICommand.h',
+    'Core/IDynamicObject.h',
+    'Core/Images/IImageWriter.cpp',
+    'Core/Images/IImageWriter.h',
+    'Core/Images/Image.cpp',
+    'Core/Images/Image.h',
+    'Core/Images/ImageAccessor.cpp',
+    'Core/Images/ImageAccessor.h',
+    'Core/Images/ImageBuffer.cpp',
+    'Core/Images/ImageBuffer.h',
+    'Core/Images/ImageProcessing.cpp',
+    'Core/Images/ImageProcessing.h',
+    'Core/Images/JpegErrorManager.cpp',
+    'Core/Images/JpegErrorManager.h',
+    'Core/Images/JpegReader.cpp',
+    'Core/Images/JpegReader.h',
+    'Core/Images/JpegWriter.cpp',
+    'Core/Images/JpegWriter.h',
+    'Core/Images/PngReader.cpp',
+    'Core/Images/PngReader.h',
+    'Core/Images/PngWriter.cpp',
+    'Core/Images/PngWriter.h',
+    'Core/Logging.cpp',
+    'Core/Logging.h',
+    'Core/MultiThreading/BagOfTasks.h',
+    'Core/MultiThreading/BagOfTasksProcessor.cpp',
+    'Core/MultiThreading/BagOfTasksProcessor.h',
+    'Core/MultiThreading/Semaphore.cpp',
+    'Core/MultiThreading/Semaphore.h',
+    'Core/MultiThreading/SharedMessageQueue.cpp',
+    'Core/MultiThreading/SharedMessageQueue.h',
+    'Core/OrthancException.h',
+    'Core/PrecompiledHeaders.cpp',
+    'Core/PrecompiledHeaders.h',
+    'Core/Toolbox.cpp',
+    'Core/Toolbox.h',
+    'Core/Uuid.cpp',
+    'Core/Uuid.h',
+    'Core/WebServiceParameters.cpp',
+    'Core/WebServiceParameters.h',
+    'OrthancServer/FromDcmtkBridge.cpp',
+    'OrthancServer/FromDcmtkBridge.h',
+    'OrthancServer/PrecompiledHeadersServer.h',
+    'OrthancServer/ServerEnumerations.cpp',
+    'OrthancServer/ServerEnumerations.h',
+    'OrthancServer/ToDcmtkBridge.cpp',
+    'OrthancServer/ToDcmtkBridge.h',
+    'Plugins/Engine/SharedLibrary.cpp',
+    'Plugins/Engine/SharedLibrary.h',
+    'Plugins/Samples/Common/ExportedSymbols.list',
+    'Plugins/Samples/Common/OrthancPluginCppWrapper.cpp',
+    'Plugins/Samples/Common/OrthancPluginCppWrapper.h',
+    'Plugins/Samples/Common/VersionScript.map',
+    'Resources/CMake/AutoGeneratedCode.cmake',
+    'Resources/CMake/BoostConfiguration.cmake',
+    'Resources/CMake/Compiler.cmake',
+    'Resources/CMake/DcmtkConfiguration.cmake',
+    'Resources/CMake/DownloadPackage.cmake',
+    'Resources/CMake/JsonCppConfiguration.cmake',
+    'Resources/CMake/LibCurlConfiguration.cmake',
+    'Resources/CMake/LibJpegConfiguration.cmake',
+    'Resources/CMake/LibPngConfiguration.cmake',
+    'Resources/CMake/OpenSslConfiguration.cmake',
+    'Resources/CMake/VisualStudioPrecompiledHeaders.cmake',
+    'Resources/CMake/ZlibConfiguration.cmake',
+    'Resources/EmbedResources.py',
+    'Resources/MinGW-W64-Toolchain32.cmake',
+    'Resources/MinGW-W64-Toolchain64.cmake',
+    'Resources/MinGWToolchain.cmake',
+    'Resources/Patches/dcmtk-3.6.0-mingw64.patch',
+    'Resources/Patches/dcmtk-3.6.0-speed.patch',
+    'Resources/Patches/dcmtk-3.6.1-speed.patch',
+    'Resources/ThirdParty/VisualStudio/stdint.h',
+    'Resources/ThirdParty/base64/base64.cpp',
+    'Resources/ThirdParty/base64/base64.h',
+    'Resources/ThirdParty/patch/NOTES.txt',
+    'Resources/ThirdParty/patch/msys-1.0.dll',
+    'Resources/ThirdParty/patch/patch.exe',
+    'Resources/ThirdParty/patch/patch.exe.manifest',
+    'Resources/WindowsResources.py',
+    'Resources/WindowsResources.rc',
+]
+
+SDK = [
+    'orthanc/OrthancCPlugin.h',
+]   
+
+EXE = [
+    'Resources/EmbedResources.py',
+    'Resources/WindowsResources.py',
+]
+
+
+def Download(x):
+    branch = x[0]
+    source = x[1]
+    target = os.path.join(TARGET, x[2])
+    print target
+
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
+
+    try:
+        with open(target, 'w') as f:
+            f.write(urllib2.urlopen(url).read())
+    except:
+        print('Cannot download file %s' % url)
+        raise
+
+
+commands = []
+
+for f in FILES:
+    commands.append([ 'default', f, f ])
+
+for f in SDK:
+    commands.append([ 
+        'Orthanc-%s' % PLUGIN_SDK_VERSION, 
+        'Plugins/Include/%s' % f,
+        'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) 
+    ])
+
+
+pool = multiprocessing.Pool(10)  # simultaneous downloads
+pool.map(Download, commands)
+
+
+for exe in EXE:
+    path = os.path.join(TARGET, exe)
+    st = os.stat(path)
+    os.chmod(path, st.st_mode | stat.S_IEXEC)
+
diff --git a/Resources/sRGB.icc b/Resources/sRGB.icc
new file mode 100644
index 0000000..1d8f741
Binary files /dev/null and b/Resources/sRGB.icc differ
diff --git a/Resources/sRGB.txt b/Resources/sRGB.txt
new file mode 100644
index 0000000..9bd30a2
--- /dev/null
+++ b/Resources/sRGB.txt
@@ -0,0 +1,69 @@
+Notes about the sRGB.icc file
+=============================
+
+The file "sRGB.icc" contains a default sRGB ICC color profile. It is
+taken from a Debian distribution, from the following path:
+/usr/share/color/icc/sRGB.icc
+
+Its MD5 hash is: 7fb30d688bf82d32a0e748daf3dba95d
+
+This profile comes from the "icc-profiles-free" package from Debian:
+https://packages.debian.org/sid/icc-profiles-free
+
+The license terms of the profile are reproduced below, and are taken
+from the source of the "icc-profiles-free" package:
+http://anonscm.debian.org/cgit/collab-maint/icc-profiles.git/tree/debian/copyright
+
+
+License
+-------
+
+Files: LCMS* compat* Gray* Cine* ITUL* sRGB.icc
+Copyright:  Kai-Uwe Behrmann <www.behrmann.name>
+            Marti Maria <www.littlecms.com>
+            Photogamut <www.photogamut.org>
+            Graeme Gill <www.argyllcms.com>
+            ColorSolutions <www.basICColor.com>
+License: Zlib
+ The zlib/libpng License
+ .
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+ .
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+ .
+    1. The origin of this software must not be misrepresented; you must not
+    claim that you wrote the original software. If you use this software
+    in a product, an acknowledgment in the product documentation would be
+    appreciated but is not required.
+ .
+    2. Altered source versions must be plainly marked as such, and must not be
+    misrepresented as being the original software.
+ .
+    3. This notice may not be removed or altered from any source
+    distribution.
+ .
+ NO WARRANTY
+ .
+   BECAUSE THE DATA IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+ FOR THE DATA, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+ PROVIDE THE DATA "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+ TO THE QUALITY AND PERFORMANCE OF THE DATA IS WITH YOU.  SHOULD THE
+ DATA PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+ REPAIR OR CORRECTION.
+ .
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+ REDISTRIBUTE THE DATA AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+ OUT OF THE USE OR INABILITY TO USE THE DATA (INCLUDING BUT NOT LIMITED
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+ YOU OR THIRD PARTIES OR A FAILURE OF THE DATA TO OPERATE WITH ANY OTHER
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGES.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..2c738bb
--- /dev/null
+++ b/TODO
@@ -0,0 +1,33 @@
+Orthanc for Whole-Slide Imaging
+===============================
+
+
+-------
+General
+-------
+
+* Support sparse tiling (both in encoder and decoder)
+* Display physical scale in Web viewer
+
+
+-----------
+Performance
+-----------
+
+* Larger cache with LRU recycling to improve viewer performance
+
+
+-------------
+Documentation
+-------------
+
+* Doxygen Documentation
+* Document the REST API in the Orthanc Book (/wsi/pyramids and /wsi/tiles)
+
+
+---------
+Packaging
+---------
+
+* DebianMed (ITP #842168: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=842168)
+* Provide precompiled Windows binaries
diff --git a/ViewerPlugin/CMakeLists.txt b/ViewerPlugin/CMakeLists.txt
new file mode 100644
index 0000000..f6b4d75
--- /dev/null
+++ b/ViewerPlugin/CMakeLists.txt
@@ -0,0 +1,238 @@
+cmake_minimum_required(VERSION 2.8)
+project(OrthancWSIPlugin)
+
+
+#####################################################################
+## Parameters of the build
+#####################################################################
+
+# Generic parameters
+SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
+# Advanced parameters to fine-tune linking against system libraries
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg")
+SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng")
+SET(USE_SYSTEM_OPENJPEG ON CACHE BOOL "Use the system version of OpenJpeg")
+SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib")
+SET(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
+
+# Parameters related to OpenLayers
+SET(USE_SYSTEM_OPENLAYERS OFF CACHE BOOL "Use the system version of OpenLayers")
+SET(OPENLAYERS_CSS "" CACHE FILEPATH "Path to the system version of OpenLayers CSS")
+SET(OPENLAYERS_JS "" CACHE FILEPATH "Path to the system version of OpenLayers JavaScript")
+
+
+#####################################################################
+## Configure mandatory third-party components
+#####################################################################
+
+SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
+SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Framework/Orthanc)
+
+SET(USE_OPENJPEG_JP2 ON)
+
+include(CheckIncludeFiles)
+include(CheckIncludeFileCXX)
+include(CheckLibraryExists)
+include(FindPythonInterp)
+include(FindPkgConfig)
+include(CheckSymbolExists)
+
+include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake)
+
+# Third-party components shipped with Orthanc
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibJpegConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake)
+
+# Include components specific to WSI
+include(${ORTHANC_WSI_DIR}/Resources/CMake/Version.cmake)
+include(${ORTHANC_WSI_DIR}/Resources/CMake/OpenJpegConfiguration.cmake)
+
+add_definitions(
+  -DORTHANC_ENABLE_BASE64=0
+  -DORTHANC_ENABLE_CURL=0
+  -DORTHANC_ENABLE_DCMTK=0
+  -DORTHANC_ENABLE_LOGGING=0
+  -DORTHANC_ENABLE_MD5=0
+  -DHAS_ORTHANC_EXCEPTION=1
+  )
+
+
+#####################################################################
+## Find the Orthanc SDK
+#####################################################################
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
+  include_directories(${ORTHANC_ROOT}/Sdk-1.0.0)
+else ()
+  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
+  if (NOT HAVE_ORTHANC_H)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+endif()
+
+
+#####################################################################
+## Platform-specific configuration
+#####################################################################
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+  link_libraries(rt)
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
+    ${ORTHANC_WSI_VERSION} "OrthancWSI" OrthancWSI.dll "Whole-slide imaging plugin for Orthanc"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/Version.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  list(APPEND AUTOGENERATED_SOURCES  ${AUTOGENERATED_DIR}/Version.rc)
+endif()
+
+if (APPLE)
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework CoreFoundation")
+endif()
+
+
+#####################################################################
+## Prepare OpenLayers
+#####################################################################
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_OPENLAYERS)
+  DownloadPackage(
+    "77e57aad873c2d4deea8bb1446e9b87a"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/WSI/openlayers-3.19.0-dist.zip"
+    "v3.19.0-dist")
+  
+  set(OPENLAYERS_RESOURCES
+    OPENLAYERS_CSS  ${CMAKE_CURRENT_BINARY_DIR}/v3.19.0-dist/ol.css
+    OPENLAYERS_JS   ${CMAKE_CURRENT_BINARY_DIR}/v3.19.0-dist/ol.js
+    )
+
+else()
+  if (OPENLAYERS_CSS STREQUAL "")
+    message(FATAL_ERROR "The option OPENLAYERS_CSS is not set")
+  endif()
+
+  if (OPENLAYERS_JS STREQUAL "")
+    message(FATAL_ERROR "The option OPENLAYERS_JS is not set")
+  endif()
+
+  set(OPENLAYERS_RESOURCES
+    OPENLAYERS_CSS  ${OPENLAYERS_CSS}
+    OPENLAYERS_JS   ${OPENLAYERS_JS}
+    )  
+endif()
+
+EmbedResources(
+  ${OPENLAYERS_RESOURCES}
+  ORTHANC_EXPLORER  ${CMAKE_SOURCE_DIR}/OrthancExplorer.js
+  VIEWER_HTML       ${CMAKE_SOURCE_DIR}/viewer.html
+  VIEWER_JS         ${CMAKE_SOURCE_DIR}/viewer.js
+  )
+
+
+#####################################################################
+## Create the list of the source files that depend upon the
+## precompiled headers
+#####################################################################
+
+set(ORTHANC_WSI_SOURCES
+  Plugin.cpp
+  ${ORTHANC_WSI_DIR}/Framework/DicomToolbox.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Enumerations.cpp
+  ${ORTHANC_WSI_DIR}/Framework/ImageToolbox.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramid.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidInstance.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidLevel.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/IOrthancConnection.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancConnectionBase.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Messaging/PluginOrthancConnection.cpp
+  )
+
+set(ORTHANC_CORE_SOURCES
+  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/Images/IImageWriter.cpp
+  ${ORTHANC_ROOT}/Core/Images/Image.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp
+  ${ORTHANC_ROOT}/Core/Images/JpegWriter.cpp
+  ${ORTHANC_ROOT}/Core/Images/PngReader.cpp
+  ${ORTHANC_ROOT}/Core/Images/PngWriter.cpp
+  ${ORTHANC_ROOT}/Core/Logging.cpp
+  ${ORTHANC_ROOT}/Core/MultiThreading/Semaphore.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+  )
+
+
+#####################################################################
+## Setup precompiled headers for Microsoft Visual Studio
+#####################################################################
+
+if (MSVC)
+  add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1)
+
+  ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
+    "PrecompiledHeaders.h" "${ORTHANC_WSI_DIR}/Framework/Orthanc/Core/PrecompiledHeaders.cpp" ORTHANC_CORE_SOURCES)
+
+  ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
+    "PrecompiledHeadersWSI.h" "${ORTHANC_WSI_DIR}/Framework/PrecompiledHeadersWSI.cpp" ORTHANC_WSI_SOURCES)
+
+  source_group(ThirdParty\\OrthancCore FILES ${ORTHANC_CORE_SOURCES})
+endif()
+
+
+#####################################################################
+## Create the plugin
+#####################################################################
+
+add_library(OrthancWSI SHARED
+  ${ORTHANC_CORE_SOURCES}
+  ${ORTHANC_WSI_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+
+  # Mandatory components
+  ${BOOST_SOURCES}
+  ${JSONCPP_SOURCES}
+  ${ZLIB_SOURCES}
+  ${LIBPNG_SOURCES}
+  ${LIBJPEG_SOURCES}
+  ${OPENJPEG_SOURCES}
+  )
+
+message("Setting the version of the library to ${ORTHANC_WSI_VERSION}")
+set_target_properties(OrthancWSI PROPERTIES 
+  VERSION ${ORTHANC_WSI_VERSION} 
+  SOVERSION ${ORTHANC_WSI_VERSION})
+
+install(
+  TARGETS OrthancWSI
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
diff --git a/ViewerPlugin/OrthancExplorer.js b/ViewerPlugin/OrthancExplorer.js
new file mode 100644
index 0000000..7b47e99
--- /dev/null
+++ b/ViewerPlugin/OrthancExplorer.js
@@ -0,0 +1,34 @@
+$('#series').live('pagebeforeshow', function() {
+  var seriesId = $.mobile.pageData.uuid;
+
+  $('#wsi-button').remove();
+
+  // Test whether this is a whole-slide image by check the SOP Class
+  // UID of one instance of the series
+  GetResource('/series/' + seriesId, function(series) {
+    GetResource('/instances/' + series['Instances'][0] + '/tags?simplify', function(instance) {
+      console.log(instance['SOPClassUID']);
+
+      if (instance['SOPClassUID'] == '1.2.840.10008.5.1.4.1.1.77.1.6') {
+
+        // This is a whole-slide image, register the button
+        var b = $('<a>')
+          .attr('id', 'wsi-button')
+          .attr('data-role', 'button')
+          .attr('href', '#')
+          .attr('data-icon', 'search')
+          .attr('data-theme', 'e')
+          .text('Whole-Slide Imaging Viewer')
+          .button();
+
+        b.insertAfter($('#series-info'));
+        b.click(function() {
+          if ($.mobile.pageData) {
+            window.open('../wsi/app/viewer.html?series=' + seriesId);
+          }
+        });
+
+      }
+    });
+  });
+});
diff --git a/ViewerPlugin/Plugin.cpp b/ViewerPlugin/Plugin.cpp
new file mode 100644
index 0000000..d68ed00
--- /dev/null
+++ b/ViewerPlugin/Plugin.cpp
@@ -0,0 +1,397 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Framework/PrecompiledHeadersWSI.h"
+#include "../Framework/Inputs/DicomPyramid.h"
+#include "../Framework/Jpeg2000Reader.h"
+#include "../Framework/Messaging/PluginOrthancConnection.h"
+#include "../Framework/Orthanc/Core/Images/PngWriter.h"
+#include "../Framework/Orthanc/Core/MultiThreading/Semaphore.h"
+#include "../Framework/Orthanc/Core/OrthancException.h"
+#include "../Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+
+#include <EmbeddedResources.h>
+
+#include <cassert>
+
+
+
+namespace OrthancWSI
+{
+  // TODO Add LRU recycling policy
+  class DicomPyramidCache : public boost::noncopyable
+  {
+  private:
+    boost::mutex                  mutex_;
+    IOrthancConnection&           orthanc_;
+    std::auto_ptr<DicomPyramid>   pyramid_;
+
+    DicomPyramid& GetPyramid(const std::string& seriesId)
+    {
+      // Mutex is assumed to be locked
+
+      if (pyramid_.get() == NULL ||
+          pyramid_->GetSeriesId() != seriesId)
+      {
+        pyramid_.reset(new DicomPyramid(orthanc_, seriesId));
+      }
+
+      if (pyramid_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      return *pyramid_;
+    }
+
+  public:
+    DicomPyramidCache(IOrthancConnection& orthanc) :
+      orthanc_(orthanc)
+    {
+    }
+
+    void Invalidate(const std::string& seriesId)
+    {
+      boost::mutex::scoped_lock  lock(mutex_);
+
+      if (pyramid_.get() != NULL &&
+          pyramid_->GetSeriesId() == seriesId)
+      {
+        pyramid_.reset(NULL);
+      }
+    }
+
+    class Locker : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      DicomPyramid&              pyramid_;
+
+    public:
+      Locker(DicomPyramidCache& cache,
+             const std::string& seriesId) :
+        lock_(cache.mutex_),
+        pyramid_(cache.GetPyramid(seriesId))
+      {
+      }
+
+      DicomPyramid& GetPyramid() const
+      {
+        return pyramid_;
+      }
+    };
+  };
+}
+
+
+OrthancPluginContext* context_ = NULL;
+
+std::auto_ptr<OrthancWSI::PluginOrthancConnection>  orthanc_;
+std::auto_ptr<OrthancWSI::DicomPyramidCache>        cache_;
+std::auto_ptr<Orthanc::Semaphore>                   transcoderSemaphore_;
+
+
+static bool DisplayPerformanceWarning()
+{
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  OrthancPluginLogWarning(context_, "Performance warning in whole-slide imaging: "
+                          "Non-release build, runtime debug assertions are turned on");
+  return true;
+}
+
+
+void ServePyramid(OrthancPluginRestOutput* output,
+                  const char* url,
+                  const OrthancPluginHttpRequest* request)
+{
+  std::string seriesId(request->groups[0]);
+
+  char tmp[1024];
+  sprintf(tmp, "Accessing whole-slide pyramid of series %s", seriesId.c_str());
+  OrthancPluginLogInfo(context_, tmp);
+  
+
+  OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
+
+  unsigned int totalWidth = locker.GetPyramid().GetLevelWidth(0);
+  unsigned int totalHeight = locker.GetPyramid().GetLevelHeight(0);
+
+  Json::Value resolutions = Json::arrayValue;
+  for (unsigned int i = 0; i < locker.GetPyramid().GetLevelCount(); i++)
+  {
+    resolutions.append(static_cast<float>(totalWidth) /
+                       static_cast<float>(locker.GetPyramid().GetLevelWidth(i)));
+  }
+
+  Json::Value result;
+  result["ID"] = seriesId;
+  result["TotalWidth"] = totalWidth;
+  result["TotalHeight"] = totalHeight;
+  result["TileWidth"] = locker.GetPyramid().GetTileWidth();
+  result["TileHeight"] = locker.GetPyramid().GetTileHeight();
+  result["Resolutions"] = resolutions;
+
+  std::string s = result.toStyledString();
+  OrthancPluginAnswerBuffer(context_, output, s.c_str(), s.size(), "application/json");
+}
+
+
+void ServeTile(OrthancPluginRestOutput* output,
+               const char* url,
+               const OrthancPluginHttpRequest* request)
+{
+  std::string seriesId(request->groups[0]);
+  int level = boost::lexical_cast<int>(request->groups[1]);
+  int tileY = boost::lexical_cast<int>(request->groups[3]);
+  int tileX = boost::lexical_cast<int>(request->groups[2]);
+
+  char tmp[1024];
+  sprintf(tmp, "Accessing tile in series %s: (%d,%d) at level %d", seriesId.c_str(), tileX, tileY, level);
+  OrthancPluginLogInfo(context_, tmp);
+  
+  if (level < 0 ||
+      tileX < 0 ||
+      tileY < 0)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+
+  // Retrieve the raw tile from the WSI pyramid
+  OrthancWSI::ImageCompression compression;
+  Orthanc::PixelFormat format;
+  std::string tile;
+  unsigned int tileWidth, tileHeight;
+
+  {
+    OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
+
+    compression = locker.GetPyramid().GetImageCompression();
+    format = locker.GetPyramid().GetPixelFormat();
+    tileWidth = locker.GetPyramid().GetTileWidth();
+    tileHeight = locker.GetPyramid().GetTileHeight();
+
+    if (!locker.GetPyramid().ReadRawTile(tile, 
+                                         static_cast<unsigned int>(level),
+                                         static_cast<unsigned int>(tileX),
+                                         static_cast<unsigned int>(tileY)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+  
+  // Test whether the tile is a JPEG image. In such a case, we can
+  // serve it as such, because any Web browser can handle JPEG
+
+  if (compression == OrthancWSI::ImageCompression_Jpeg)
+  {
+    OrthancPluginAnswerBuffer(context_, output, tile.c_str(), tile.size(), "image/jpeg");
+    return;   // We're done
+  }
+
+
+  // The tile does not come from a DICOM-JPEG instance, we need to
+  // decompress the raw tile
+  std::auto_ptr<Orthanc::ImageAccessor> decoded;
+
+  Orthanc::Semaphore::Locker locker(*transcoderSemaphore_);
+
+  switch (compression)
+  {
+    case OrthancWSI::ImageCompression_Jpeg2000:
+      decoded.reset(new OrthancWSI::Jpeg2000Reader);
+      dynamic_cast<OrthancWSI::Jpeg2000Reader&>(*decoded).ReadFromMemory(tile);
+      break;
+
+    case OrthancWSI::ImageCompression_None:
+    {
+      unsigned int bpp = Orthanc::GetBytesPerPixel(format);
+      if (bpp * tileWidth * tileHeight != tile.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      decoded.reset(new Orthanc::ImageAccessor);
+      decoded->AssignReadOnly(format, tileWidth, tileHeight, bpp * tileWidth, tile.c_str());
+      break;
+    }
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+
+  // This is a lossless frame (coming from a JPEG2000 or uncompressed
+  // DICOM instance), serve it as a PNG image so as to prevent lossy
+  // compression
+
+  std::string png;
+  Orthanc::PngWriter writer;
+  writer.WriteToMemory(png, *decoded);
+
+  OrthancPluginAnswerBuffer(context_, output, png.c_str(), png.size(), "image/png");
+}
+
+
+OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, 
+                                        OrthancPluginResourceType resourceType, 
+                                        const char *resourceId)
+{
+  if (resourceType == OrthancPluginResourceType_Series &&
+      changeType == OrthancPluginChangeType_NewChildInstance)
+  { 
+    char tmp[1024];
+    sprintf(tmp, "New instance has been added to series %s, invalidating it", resourceId);
+    OrthancPluginLogInfo(context_, tmp);
+
+    cache_->Invalidate(resourceId);
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
+
+void ServeFile(OrthancPluginRestOutput* output,
+               const char* url,
+               const OrthancPluginHttpRequest* request)
+{
+  Orthanc::EmbeddedResources::FileResourceId resource;
+
+  std::string f(request->groups[0]);
+  std::string mime;
+
+  if (f == "viewer.html")
+  {
+    resource = Orthanc::EmbeddedResources::VIEWER_HTML;
+    mime = "text/html";
+  }
+  else if (f == "viewer.js")
+  {
+    resource = Orthanc::EmbeddedResources::VIEWER_JS;
+    mime = "application/javascript";
+  }
+  else if (f == "ol.js")
+  {
+    resource = Orthanc::EmbeddedResources::OPENLAYERS_JS;
+    mime = "application/javascript";
+  }
+  else if (f == "ol.css")
+  {
+    resource = Orthanc::EmbeddedResources::OPENLAYERS_CSS;
+    mime = "text/css";
+  }
+  else
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+  }
+
+  std::string content;
+  Orthanc::EmbeddedResources::GetFileResource(content, resource);
+
+  OrthancPluginAnswerBuffer(context_, output, content.c_str(), content.size(), mime.c_str());
+}
+
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    context_ = context;
+    assert(DisplayPerformanceWarning());
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context_->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    if (!OrthancPlugins::CheckMinimalOrthancVersion(context_, 1, 1, 0))
+    {
+      // We need the "/instances/.../frames/.../raw" URI that was introduced in Orthanc 1.1.0
+      return -1;
+    }
+
+    // Limit the number of PNG transcoders to the number of available
+    // hardware threads (e.g. number of CPUs or cores or
+    // hyperthreading units)
+    unsigned int threads = boost::thread::hardware_concurrency();
+    
+    if (threads <= 0)
+    {
+      threads = 1;
+    }
+    
+    transcoderSemaphore_.reset(new Orthanc::Semaphore(threads));
+
+    char info[1024];
+    sprintf(info, "The whole-slide imaging plugin will use at most %d threads to transcode the tiles", threads);
+    OrthancPluginLogWarning(context_, info);
+
+    OrthancPluginSetDescription(context, "Provides a Web viewer of whole-slide microscopic images within Orthanc.");
+
+    orthanc_.reset(new OrthancWSI::PluginOrthancConnection(context));
+    cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_));
+
+    OrthancPluginRegisterOnChangeCallback(context_, OnChangeCallback);
+
+    OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(ol.css)", true);
+    OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(ol.js)", true);
+    OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(viewer.html)", true);
+    OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(viewer.js)", true);
+    OrthancPlugins::RegisterRestCallback<ServePyramid>(context, "/wsi/pyramids/([0-9a-f-]+)", true);
+    OrthancPlugins::RegisterRestCallback<ServeTile>(context, "/wsi/tiles/([0-9a-f-]+)/([0-9-]+)/([0-9-]+)/([0-9-]+)", true);
+
+    // Extend the default Orthanc Explorer with custom JavaScript for WSI
+    std::string explorer;
+    Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
+    OrthancPluginExtendOrthancExplorer(context_, explorer.c_str());
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    cache_.reset(NULL);
+    orthanc_.reset(NULL);
+    transcoderSemaphore_.reset(NULL);
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "wsi";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_WSI_VERSION;
+  }
+}
diff --git a/ViewerPlugin/viewer.html b/ViewerPlugin/viewer.html
new file mode 100644
index 0000000..777eab5
--- /dev/null
+++ b/ViewerPlugin/viewer.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <title>Orthanc for Whole-Slide Imaging</title>
+
+    <link rel="stylesheet" href="ol.css" type="text/css">
+
+    <!-- This is the version of jQuery that is used by Orthanc Explorer -->
+    <script src="../../app/libs/jquery.min.js"></script>
+
+    <script src="ol.js"></script>
+
+    <style>
+      #map {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: 1px solid #ccc;
+      margin-bottom: 10px;
+      }
+    </style>
+
+  </head>
+  <body>
+    <div id="map" class="map"></div>
+
+    <script src="viewer.js"></script>
+  </body>
+</html>
diff --git a/ViewerPlugin/viewer.js b/ViewerPlugin/viewer.js
new file mode 100644
index 0000000..99787b8
--- /dev/null
+++ b/ViewerPlugin/viewer.js
@@ -0,0 +1,125 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+// For IE compatibility
+if (!window.console) window.console = {};
+if (!window.console.log) window.console.log = function () { };
+
+
+// http://stackoverflow.com/a/21903119/881731
+function GetUrlParameter(sParam) 
+{
+  var sPageURL = decodeURIComponent(window.location.search.substring(1));
+  var sURLVariables = sPageURL.split('&');
+  var sParameterName;
+  var i;
+
+  for (i = 0; i < sURLVariables.length; i++) 
+  {
+    sParameterName = sURLVariables[i].split('=');
+
+    if (sParameterName[0] === sParam) 
+    {
+      return sParameterName[1] === undefined ? '' : sParameterName[1];
+    }
+  }
+
+  return '';
+};
+
+
+
+$(document).ready(function() {
+  var seriesId = GetUrlParameter('series');
+  if (seriesId.length == 0)
+  {
+    alert('Error - No series ID specified!');
+  }
+  else
+  {
+    $.ajax({
+      url : '../pyramids/' + seriesId,
+      error: function() {
+        alert('Error - Cannot get the pyramid structure of series: ' + seriesId);
+      },
+      success : function(series) {
+        var width = series['TotalWidth'];
+        var height = series['TotalHeight'];
+        var tileWidth = series['TileWidth'];
+        var tileHeight = series['TileHeight'];
+        var countLevels = series['Resolutions'].length;
+
+        // Maps always need a projection, but Zoomify layers are not geo-referenced, and
+        // are only measured in pixels.  So, we create a fake projection that the map
+        // can use to properly display the layer.
+        var proj = new ol.proj.Projection({
+          code: 'pixel',
+          units: 'pixels',
+          extent: [0, 0, width, height]
+        });
+
+        var extent = [0, -height, width, 0];
+        
+        // Disable the rotation of the map, and inertia while panning
+        // http://stackoverflow.com/a/25682186
+        var interactions = ol.interaction.defaults({
+          altShiftDragRotate : false, 
+          pinchRotate : false,
+          dragPan: false
+        }).extend([
+          new ol.interaction.DragPan({kinetic: false})
+        ]);
+
+        var layer = new ol.layer.Tile({
+          extent: extent,
+          source: new ol.source.TileImage({
+            projection: proj,
+            tileUrlFunction: function(tileCoord, pixelRatio, projection) {
+              return ('../tiles/' + seriesId + '/' + 
+                      (countLevels - 1 - tileCoord[0]) + '/' + tileCoord[1] + '/' + (-tileCoord[2] - 1));
+            },
+            tileGrid: new ol.tilegrid.TileGrid({
+              extent: extent,
+              resolutions: series['Resolutions'].reverse(),
+              tileSize: [tileWidth, tileHeight]
+            })
+          }),
+          wrapX: false,
+          projection: proj
+        });
+
+
+        var map = new ol.Map({
+          target: 'map',
+          layers: [ layer ],
+          view: new ol.View({
+            projection: proj,
+            center: [width / 2, -height / 2],
+            zoom: 0,
+            minResolution: 1   // Do not interpelate over pixels
+          }),
+          interactions: interactions
+        });
+
+        map.getView().fit(extent, map.getSize());
+      }
+    });
+  }
+});
diff --git a/debian/JS/openlayers-3.19.0/README.txt b/debian/JS/openlayers-3.19.0/README.txt
deleted file mode 100644
index 3876a5a..0000000
--- a/debian/JS/openlayers-3.19.0/README.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-The file "ol.css" comes from the official GitHub repository of
-OpenLayers, under tag "v3.19.0". To re-download it:
-
-# curl https://raw.githubusercontent.com/openlayers/ol3/v3.19.0/css/ol.css > ol.css
-
-The file "ol-debug.js" comes from the official distribution of
-OpenLayers, that is available for download at:
-https://github.com/openlayers/ol3/releases/
diff --git a/debian/JS/openlayers-3.19.0/ol-debug.js b/debian/JS/openlayers-3.19.0/ol-debug.js
deleted file mode 100644
index 77a08a6..0000000
--- a/debian/JS/openlayers-3.19.0/ol-debug.js
+++ /dev/null
@@ -1,88520 +0,0 @@
-// OpenLayers 3. See https://openlayers.org/
-// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
-// Version: v3.19.0
-;(function (root, factory) {
-  if (typeof exports === "object") {
-    module.exports = factory();
-  } else if (typeof define === "function" && define.amd) {
-    define([], factory);
-  } else {
-    root.ol = factory();
-  }
-}(this, function () {
-  var OPENLAYERS = {};
-  var goog = this.goog = {};
-this.CLOSURE_NO_DEPS = true;
-// Copyright 2006 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview Bootstrap for the Google JS Library (Closure).
- *
- * In uncompiled mode base.js will write out Closure's deps file, unless the
- * global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects to
- * include their own deps file(s) from different locations.
- *
- * @author arv at google.com (Erik Arvidsson)
- *
- * @provideGoog
- */
-
-
-/**
- * @define {boolean} Overridden to true by the compiler when
- *     --process_closure_primitives is specified.
- */
-var COMPILED = false;
-
-
-/**
- * Base namespace for the Closure library.  Checks to see goog is already
- * defined in the current scope before assigning to prevent clobbering if
- * base.js is loaded more than once.
- *
- * @const
- */
-var goog = goog || {};
-
-
-/**
- * Reference to the global context.  In most cases this will be 'window'.
- */
-goog.global = this;
-
-
-/**
- * A hook for overriding the define values in uncompiled mode.
- *
- * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before
- * loading base.js.  If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES},
- * {@code goog.define} will use the value instead of the default value.  This
- * allows flags to be overwritten without compilation (this is normally
- * accomplished with the compiler's "define" flag).
- *
- * Example:
- * <pre>
- *   var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
- * </pre>
- *
- * @type {Object<string, (string|number|boolean)>|undefined}
- */
-goog.global.CLOSURE_UNCOMPILED_DEFINES;
-
-
-/**
- * A hook for overriding the define values in uncompiled or compiled mode,
- * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code.  In
- * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence.
- *
- * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or
- * string literals or the compiler will emit an error.
- *
- * While any @define value may be set, only those set with goog.define will be
- * effective for uncompiled code.
- *
- * Example:
- * <pre>
- *   var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
- * </pre>
- *
- * @type {Object<string, (string|number|boolean)>|undefined}
- */
-goog.global.CLOSURE_DEFINES;
-
-
-/**
- * Returns true if the specified value is not undefined.
- * WARNING: Do not use this to test if an object has a property. Use the in
- * operator instead.
- *
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is defined.
- */
-goog.isDef = function(val) {
-  // void 0 always evaluates to undefined and hence we do not need to depend on
-  // the definition of the global variable named 'undefined'.
-  return val !== void 0;
-};
-
-
-/**
- * Builds an object structure for the provided namespace path, ensuring that
- * names that already exist are not overwritten. For example:
- * "a.b.c" -> a = {};a.b={};a.b.c={};
- * Used by goog.provide and goog.exportSymbol.
- * @param {string} name name of the object that this file defines.
- * @param {*=} opt_object the object to expose at the end of the path.
- * @param {Object=} opt_objectToExportTo The object to add the path to; default
- *     is |goog.global|.
- * @private
- */
-goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
-  var parts = name.split('.');
-  var cur = opt_objectToExportTo || goog.global;
-
-  // Internet Explorer exhibits strange behavior when throwing errors from
-  // methods externed in this manner.  See the testExportSymbolExceptions in
-  // base_test.html for an example.
-  if (!(parts[0] in cur) && cur.execScript) {
-    cur.execScript('var ' + parts[0]);
-  }
-
-  // Certain browsers cannot parse code in the form for((a in b); c;);
-  // This pattern is produced by the JSCompiler when it collapses the
-  // statement above into the conditional loop below. To prevent this from
-  // happening, use a for-loop and reserve the init logic as below.
-
-  // Parentheses added to eliminate strict JS warning in Firefox.
-  for (var part; parts.length && (part = parts.shift());) {
-    if (!parts.length && goog.isDef(opt_object)) {
-      // last part and we have an object; use it
-      cur[part] = opt_object;
-    } else if (cur[part]) {
-      cur = cur[part];
-    } else {
-      cur = cur[part] = {};
-    }
-  }
-};
-
-
-/**
- * Defines a named value. In uncompiled mode, the value is retrieved from
- * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and
- * has the property specified, and otherwise used the defined defaultValue.
- * When compiled the default can be overridden using the compiler
- * options or the value set in the CLOSURE_DEFINES object.
- *
- * @param {string} name The distinguished name to provide.
- * @param {string|number|boolean} defaultValue
- */
-goog.define = function(name, defaultValue) {
-  var value = defaultValue;
-  if (!COMPILED) {
-    if (goog.global.CLOSURE_UNCOMPILED_DEFINES &&
-        Object.prototype.hasOwnProperty.call(
-            goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
-      value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
-    } else if (
-        goog.global.CLOSURE_DEFINES &&
-        Object.prototype.hasOwnProperty.call(
-            goog.global.CLOSURE_DEFINES, name)) {
-      value = goog.global.CLOSURE_DEFINES[name];
-    }
-  }
-  goog.exportPath_(name, value);
-};
-
-
-/**
- * @define {boolean} DEBUG is provided as a convenience so that debugging code
- * that should not be included in a production js_binary can be easily stripped
- * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
- * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
- * because they are generally used for debugging purposes and it is difficult
- * for the JSCompiler to statically determine whether they are used.
- */
-goog.define('goog.DEBUG', true);
-
-
-/**
- * @define {string} LOCALE defines the locale being used for compilation. It is
- * used to select locale specific data to be compiled in js binary. BUILD rule
- * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
- * option.
- *
- * Take into account that the locale code format is important. You should use
- * the canonical Unicode format with hyphen as a delimiter. Language must be
- * lowercase, Language Script - Capitalized, Region - UPPERCASE.
- * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
- *
- * See more info about locale codes here:
- * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
- *
- * For language codes you should use values defined by ISO 693-1. See it here
- * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
- * this rule: the Hebrew language. For legacy reasons the old code (iw) should
- * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
- */
-goog.define('goog.LOCALE', 'en');  // default to en
-
-
-/**
- * @define {boolean} Whether this code is running on trusted sites.
- *
- * On untrusted sites, several native functions can be defined or overridden by
- * external libraries like Prototype, Datejs, and JQuery and setting this flag
- * to false forces closure to use its own implementations when possible.
- *
- * If your JavaScript can be loaded by a third party site and you are wary about
- * relying on non-standard implementations, specify
- * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
- */
-goog.define('goog.TRUSTED_SITE', true);
-
-
-/**
- * @define {boolean} Whether a project is expected to be running in strict mode.
- *
- * This define can be used to trigger alternate implementations compatible with
- * running in EcmaScript Strict mode or warn about unavailable functionality.
- * @see https://goo.gl/PudQ4y
- *
- */
-goog.define('goog.STRICT_MODE_COMPATIBLE', false);
-
-
-/**
- * @define {boolean} Whether code that calls {@link goog.setTestOnly} should
- *     be disallowed in the compilation unit.
- */
-goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG);
-
-
-/**
- * @define {boolean} Whether to use a Chrome app CSP-compliant method for
- *     loading scripts via goog.require. @see appendScriptSrcNode_.
- */
-goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);
-
-
-/**
- * Defines a namespace in Closure.
- *
- * A namespace may only be defined once in a codebase. It may be defined using
- * goog.provide() or goog.module().
- *
- * The presence of one or more goog.provide() calls in a file indicates
- * that the file defines the given objects/namespaces.
- * Provided symbols must not be null or undefined.
- *
- * In addition, goog.provide() creates the object stubs for a namespace
- * (for example, goog.provide("goog.foo.bar") will create the object
- * goog.foo.bar if it does not already exist).
- *
- * Build tools also scan for provide/require/module statements
- * to discern dependencies, build dependency files (see deps.js), etc.
- *
- * @see goog.require
- * @see goog.module
- * @param {string} name Namespace provided by this file in the form
- *     "goog.package.part".
- */
-goog.provide = function(name) {
-  if (goog.isInModuleLoader_()) {
-    throw Error('goog.provide can not be used within a goog.module.');
-  }
-  if (!COMPILED) {
-    // Ensure that the same namespace isn't provided twice.
-    // A goog.module/goog.provide maps a goog.require to a specific file
-    if (goog.isProvided_(name)) {
-      throw Error('Namespace "' + name + '" already declared.');
-    }
-  }
-
-  goog.constructNamespace_(name);
-};
-
-
-/**
- * @param {string} name Namespace provided by this file in the form
- *     "goog.package.part".
- * @param {Object=} opt_obj The object to embed in the namespace.
- * @private
- */
-goog.constructNamespace_ = function(name, opt_obj) {
-  if (!COMPILED) {
-    delete goog.implicitNamespaces_[name];
-
-    var namespace = name;
-    while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
-      if (goog.getObjectByName(namespace)) {
-        break;
-      }
-      goog.implicitNamespaces_[namespace] = true;
-    }
-  }
-
-  goog.exportPath_(name, opt_obj);
-};
-
-
-/**
- * Module identifier validation regexp.
- * Note: This is a conservative check, it is very possible to be more lenient,
- *   the primary exclusion here is "/" and "\" and a leading ".", these
- *   restrictions are intended to leave the door open for using goog.require
- *   with relative file paths rather than module identifiers.
- * @private
- */
-goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
-
-
-/**
- * Defines a module in Closure.
- *
- * Marks that this file must be loaded as a module and claims the namespace.
- *
- * A namespace may only be defined once in a codebase. It may be defined using
- * goog.provide() or goog.module().
- *
- * goog.module() has three requirements:
- * - goog.module may not be used in the same file as goog.provide.
- * - goog.module must be the first statement in the file.
- * - only one goog.module is allowed per file.
- *
- * When a goog.module annotated file is loaded, it is enclosed in
- * a strict function closure. This means that:
- * - any variables declared in a goog.module file are private to the file
- * (not global), though the compiler is expected to inline the module.
- * - The code must obey all the rules of "strict" JavaScript.
- * - the file will be marked as "use strict"
- *
- * NOTE: unlike goog.provide, goog.module does not declare any symbols by
- * itself. If declared symbols are desired, use
- * goog.module.declareLegacyNamespace().
- *
- *
- * See the public goog.module proposal: http://goo.gl/Va1hin
- *
- * @param {string} name Namespace provided by this file in the form
- *     "goog.package.part", is expected but not required.
- */
-goog.module = function(name) {
-  if (!goog.isString(name) || !name ||
-      name.search(goog.VALID_MODULE_RE_) == -1) {
-    throw Error('Invalid module identifier');
-  }
-  if (!goog.isInModuleLoader_()) {
-    throw Error('Module ' + name + ' has been loaded incorrectly.');
-  }
-  if (goog.moduleLoaderState_.moduleName) {
-    throw Error('goog.module may only be called once per module.');
-  }
-
-  // Store the module name for the loader.
-  goog.moduleLoaderState_.moduleName = name;
-  if (!COMPILED) {
-    // Ensure that the same namespace isn't provided twice.
-    // A goog.module/goog.provide maps a goog.require to a specific file
-    if (goog.isProvided_(name)) {
-      throw Error('Namespace "' + name + '" already declared.');
-    }
-    delete goog.implicitNamespaces_[name];
-  }
-};
-
-
-/**
- * @param {string} name The module identifier.
- * @return {?} The module exports for an already loaded module or null.
- *
- * Note: This is not an alternative to goog.require, it does not
- * indicate a hard dependency, instead it is used to indicate
- * an optional dependency or to access the exports of a module
- * that has already been loaded.
- * @suppress {missingProvide}
- */
-goog.module.get = function(name) {
-  return goog.module.getInternal_(name);
-};
-
-
-/**
- * @param {string} name The module identifier.
- * @return {?} The module exports for an already loaded module or null.
- * @private
- */
-goog.module.getInternal_ = function(name) {
-  if (!COMPILED) {
-    if (goog.isProvided_(name)) {
-      // goog.require only return a value with-in goog.module files.
-      return name in goog.loadedModules_ ? goog.loadedModules_[name] :
-                                           goog.getObjectByName(name);
-    } else {
-      return null;
-    }
-  }
-};
-
-
-/**
- * @private {?{moduleName: (string|undefined), declareLegacyNamespace:boolean}}
- */
-goog.moduleLoaderState_ = null;
-
-
-/**
- * @private
- * @return {boolean} Whether a goog.module is currently being initialized.
- */
-goog.isInModuleLoader_ = function() {
-  return goog.moduleLoaderState_ != null;
-};
-
-
-/**
- * Provide the module's exports as a globally accessible object under the
- * module's declared name.  This is intended to ease migration to goog.module
- * for files that have existing usages.
- * @suppress {missingProvide}
- */
-goog.module.declareLegacyNamespace = function() {
-  if (!COMPILED && !goog.isInModuleLoader_()) {
-    throw new Error(
-        'goog.module.declareLegacyNamespace must be called from ' +
-        'within a goog.module');
-  }
-  if (!COMPILED && !goog.moduleLoaderState_.moduleName) {
-    throw Error(
-        'goog.module must be called prior to ' +
-        'goog.module.declareLegacyNamespace.');
-  }
-  goog.moduleLoaderState_.declareLegacyNamespace = true;
-};
-
-
-/**
- * Marks that the current file should only be used for testing, and never for
- * live code in production.
- *
- * In the case of unit tests, the message may optionally be an exact namespace
- * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra
- * provide (if not explicitly defined in the code).
- *
- * @param {string=} opt_message Optional message to add to the error that's
- *     raised when used in production code.
- */
-goog.setTestOnly = function(opt_message) {
-  if (goog.DISALLOW_TEST_ONLY_CODE) {
-    opt_message = opt_message || '';
-    throw Error(
-        'Importing test-only code into non-debug environment' +
-        (opt_message ? ': ' + opt_message : '.'));
-  }
-};
-
-
-/**
- * Forward declares a symbol. This is an indication to the compiler that the
- * symbol may be used in the source yet is not required and may not be provided
- * in compilation.
- *
- * The most common usage of forward declaration is code that takes a type as a
- * function parameter but does not need to require it. By forward declaring
- * instead of requiring, no hard dependency is made, and (if not required
- * elsewhere) the namespace may never be required and thus, not be pulled
- * into the JavaScript binary. If it is required elsewhere, it will be type
- * checked as normal.
- *
- *
- * @param {string} name The namespace to forward declare in the form of
- *     "goog.package.part".
- */
-goog.forwardDeclare = function(name) {};
-
-
-/**
- * Forward declare type information. Used to assign types to goog.global
- * referenced object that would otherwise result in unknown type references
- * and thus block property disambiguation.
- */
-goog.forwardDeclare('Document');
-goog.forwardDeclare('HTMLScriptElement');
-goog.forwardDeclare('XMLHttpRequest');
-
-
-if (!COMPILED) {
-  /**
-   * Check if the given name has been goog.provided. This will return false for
-   * names that are available only as implicit namespaces.
-   * @param {string} name name of the object to look for.
-   * @return {boolean} Whether the name has been provided.
-   * @private
-   */
-  goog.isProvided_ = function(name) {
-    return (name in goog.loadedModules_) ||
-        (!goog.implicitNamespaces_[name] &&
-         goog.isDefAndNotNull(goog.getObjectByName(name)));
-  };
-
-  /**
-   * Namespaces implicitly defined by goog.provide. For example,
-   * goog.provide('goog.events.Event') implicitly declares that 'goog' and
-   * 'goog.events' must be namespaces.
-   *
-   * @type {!Object<string, (boolean|undefined)>}
-   * @private
-   */
-  goog.implicitNamespaces_ = {'goog.module': true};
-
-  // NOTE: We add goog.module as an implicit namespace as goog.module is defined
-  // here and because the existing module package has not been moved yet out of
-  // the goog.module namespace. This satisifies both the debug loader and
-  // ahead-of-time dependency management.
-}
-
-
-/**
- * Returns an object based on its fully qualified external name.  The object
- * is not found if null or undefined.  If you are using a compilation pass that
- * renames property names beware that using this function will not find renamed
- * properties.
- *
- * @param {string} name The fully qualified name.
- * @param {Object=} opt_obj The object within which to look; default is
- *     |goog.global|.
- * @return {?} The value (object or primitive) or, if not found, null.
- */
-goog.getObjectByName = function(name, opt_obj) {
-  var parts = name.split('.');
-  var cur = opt_obj || goog.global;
-  for (var part; part = parts.shift();) {
-    if (goog.isDefAndNotNull(cur[part])) {
-      cur = cur[part];
-    } else {
-      return null;
-    }
-  }
-  return cur;
-};
-
-
-/**
- * Globalizes a whole namespace, such as goog or goog.lang.
- *
- * @param {!Object} obj The namespace to globalize.
- * @param {Object=} opt_global The object to add the properties to.
- * @deprecated Properties may be explicitly exported to the global scope, but
- *     this should no longer be done in bulk.
- */
-goog.globalize = function(obj, opt_global) {
-  var global = opt_global || goog.global;
-  for (var x in obj) {
-    global[x] = obj[x];
-  }
-};
-
-
-/**
- * Adds a dependency from a file to the files it requires.
- * @param {string} relPath The path to the js file.
- * @param {!Array<string>} provides An array of strings with
- *     the names of the objects this file provides.
- * @param {!Array<string>} requires An array of strings with
- *     the names of the objects this file requires.
- * @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating
- *     how the file must be loaded.  The boolean 'true' is equivalent
- *     to {'module': 'goog'} for backwards-compatibility.  Valid properties
- *     and values include {'module': 'goog'} and {'lang': 'es6'}.
- */
-goog.addDependency = function(relPath, provides, requires, opt_loadFlags) {
-  if (goog.DEPENDENCIES_ENABLED) {
-    var provide, require;
-    var path = relPath.replace(/\\/g, '/');
-    var deps = goog.dependencies_;
-    if (!opt_loadFlags || typeof opt_loadFlags === 'boolean') {
-      opt_loadFlags = opt_loadFlags ? {'module': 'goog'} : {};
-    }
-    for (var i = 0; provide = provides[i]; i++) {
-      deps.nameToPath[provide] = path;
-      deps.loadFlags[path] = opt_loadFlags;
-    }
-    for (var j = 0; require = requires[j]; j++) {
-      if (!(path in deps.requires)) {
-        deps.requires[path] = {};
-      }
-      deps.requires[path][require] = true;
-    }
-  }
-};
-
-
-
-
-// NOTE(nnaze): The debug DOM loader was included in base.js as an original way
-// to do "debug-mode" development.  The dependency system can sometimes be
-// confusing, as can the debug DOM loader's asynchronous nature.
-//
-// With the DOM loader, a call to goog.require() is not blocking -- the script
-// will not load until some point after the current script.  If a namespace is
-// needed at runtime, it needs to be defined in a previous script, or loaded via
-// require() with its registered dependencies.
-//
-// User-defined namespaces may need their own deps file. For a reference on
-// creating a deps file, see:
-// Externally: https://developers.google.com/closure/library/docs/depswriter
-//
-// Because of legacy clients, the DOM loader can't be easily removed from
-// base.js.  Work is being done to make it disableable or replaceable for
-// different environments (DOM-less JavaScript interpreters like Rhino or V8,
-// for example). See bootstrap/ for more information.
-
-
-/**
- * @define {boolean} Whether to enable the debug loader.
- *
- * If enabled, a call to goog.require() will attempt to load the namespace by
- * appending a script tag to the DOM (if the namespace has been registered).
- *
- * If disabled, goog.require() will simply assert that the namespace has been
- * provided (and depend on the fact that some outside tool correctly ordered
- * the script).
- */
-goog.define('goog.ENABLE_DEBUG_LOADER', true);
-
-
-/**
- * @param {string} msg
- * @private
- */
-goog.logToConsole_ = function(msg) {
-  if (goog.global.console) {
-    goog.global.console['error'](msg);
-  }
-};
-
-
-/**
- * Implements a system for the dynamic resolution of dependencies that works in
- * parallel with the BUILD system. Note that all calls to goog.require will be
- * stripped by the JSCompiler when the --process_closure_primitives option is
- * used.
- * @see goog.provide
- * @param {string} name Namespace to include (as was given in goog.provide()) in
- *     the form "goog.package.part".
- * @return {?} If called within a goog.module file, the associated namespace or
- *     module otherwise null.
- */
-goog.require = function(name) {
-  // If the object already exists we do not need do do anything.
-  if (!COMPILED) {
-    if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) {
-      goog.maybeProcessDeferredDep_(name);
-    }
-
-    if (goog.isProvided_(name)) {
-      if (goog.isInModuleLoader_()) {
-        return goog.module.getInternal_(name);
-      } else {
-        return null;
-      }
-    }
-
-    if (goog.ENABLE_DEBUG_LOADER) {
-      var path = goog.getPathFromDeps_(name);
-      if (path) {
-        goog.writeScripts_(path);
-        return null;
-      }
-    }
-
-    var errorMessage = 'goog.require could not find: ' + name;
-    goog.logToConsole_(errorMessage);
-
-    throw Error(errorMessage);
-  }
-};
-
-
-/**
- * Path for included scripts.
- * @type {string}
- */
-goog.basePath = '';
-
-
-/**
- * A hook for overriding the base path.
- * @type {string|undefined}
- */
-goog.global.CLOSURE_BASE_PATH;
-
-
-/**
- * Whether to write out Closure's deps file. By default, the deps are written.
- * @type {boolean|undefined}
- */
-goog.global.CLOSURE_NO_DEPS;
-
-
-/**
- * A function to import a single script. This is meant to be overridden when
- * Closure is being run in non-HTML contexts, such as web workers. It's defined
- * in the global scope so that it can be set before base.js is loaded, which
- * allows deps.js to be imported properly.
- *
- * The function is passed the script source, which is a relative URI. It should
- * return true if the script was imported, false otherwise.
- * @type {(function(string): boolean)|undefined}
- */
-goog.global.CLOSURE_IMPORT_SCRIPT;
-
-
-/**
- * Null function used for default values of callbacks, etc.
- * @return {void} Nothing.
- */
-goog.nullFunction = function() {};
-
-
-/**
- * When defining a class Foo with an abstract method bar(), you can do:
- * Foo.prototype.bar = goog.abstractMethod
- *
- * Now if a subclass of Foo fails to override bar(), an error will be thrown
- * when bar() is invoked.
- *
- * Note: This does not take the name of the function to override as an argument
- * because that would make it more difficult to obfuscate our JavaScript code.
- *
- * @type {!Function}
- * @throws {Error} when invoked to indicate the method should be overridden.
- */
-goog.abstractMethod = function() {
-  throw Error('unimplemented abstract method');
-};
-
-
-/**
- * Adds a {@code getInstance} static method that always returns the same
- * instance object.
- * @param {!Function} ctor The constructor for the class to add the static
- *     method to.
- */
-goog.addSingletonGetter = function(ctor) {
-  ctor.getInstance = function() {
-    if (ctor.instance_) {
-      return ctor.instance_;
-    }
-    if (goog.DEBUG) {
-      // NOTE: JSCompiler can't optimize away Array#push.
-      goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
-    }
-    return ctor.instance_ = new ctor;
-  };
-};
-
-
-/**
- * All singleton classes that have been instantiated, for testing. Don't read
- * it directly, use the {@code goog.testing.singleton} module. The compiler
- * removes this variable if unused.
- * @type {!Array<!Function>}
- * @private
- */
-goog.instantiatedSingletons_ = [];
-
-
-/**
- * @define {boolean} Whether to load goog.modules using {@code eval} when using
- * the debug loader.  This provides a better debugging experience as the
- * source is unmodified and can be edited using Chrome Workspaces or similar.
- * However in some environments the use of {@code eval} is banned
- * so we provide an alternative.
- */
-goog.define('goog.LOAD_MODULE_USING_EVAL', true);
-
-
-/**
- * @define {boolean} Whether the exports of goog.modules should be sealed when
- * possible.
- */
-goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG);
-
-
-/**
- * The registry of initialized modules:
- * the module identifier to module exports map.
- * @private @const {!Object<string, ?>}
- */
-goog.loadedModules_ = {};
-
-
-/**
- * True if goog.dependencies_ is available.
- * @const {boolean}
- */
-goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
-
-
-/**
- * @define {string} How to decide whether to transpile.  Valid values
- * are 'always', 'never', and 'detect'.  The default ('detect') is to
- * use feature detection to determine which language levels need
- * transpilation.
- */
-// NOTE(user): we could expand this to accept a language level to bypass
-// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but
-// would leave ES3 and ES5 files alone.
-goog.define('goog.TRANSPILE', 'detect');
-
-
-/**
- * @define {string} Path to the transpiler.  Executing the script at this
- * path (relative to base.js) should define a function $jscomp.transpile.
- */
-goog.define('goog.TRANSPILER', 'transpile.js');
-
-
-if (goog.DEPENDENCIES_ENABLED) {
-  /**
-   * This object is used to keep track of dependencies and other data that is
-   * used for loading scripts.
-   * @private
-   * @type {{
-   *   loadFlags: !Object<string, !Object<string, string>>,
-   *   nameToPath: !Object<string, string>,
-   *   requires: !Object<string, !Object<string, boolean>>,
-   *   visited: !Object<string, boolean>,
-   *   written: !Object<string, boolean>,
-   *   deferred: !Object<string, string>
-   * }}
-   */
-  goog.dependencies_ = {
-    loadFlags: {},  // 1 to 1
-
-    nameToPath: {},  // 1 to 1
-
-    requires: {},  // 1 to many
-
-    // Used when resolving dependencies to prevent us from visiting file twice.
-    visited: {},
-
-    written: {},  // Used to keep track of script files we have written.
-
-    deferred: {}  // Used to track deferred module evaluations in old IEs
-  };
-
-
-  /**
-   * Tries to detect whether is in the context of an HTML document.
-   * @return {boolean} True if it looks like HTML document.
-   * @private
-   */
-  goog.inHtmlDocument_ = function() {
-    /** @type {Document} */
-    var doc = goog.global.document;
-    return doc != null && 'write' in doc;  // XULDocument misses write.
-  };
-
-
-  /**
-   * Tries to detect the base path of base.js script that bootstraps Closure.
-   * @private
-   */
-  goog.findBasePath_ = function() {
-    if (goog.isDef(goog.global.CLOSURE_BASE_PATH)) {
-      goog.basePath = goog.global.CLOSURE_BASE_PATH;
-      return;
-    } else if (!goog.inHtmlDocument_()) {
-      return;
-    }
-    /** @type {Document} */
-    var doc = goog.global.document;
-    var scripts = doc.getElementsByTagName('SCRIPT');
-    // Search backwards since the current script is in almost all cases the one
-    // that has base.js.
-    for (var i = scripts.length - 1; i >= 0; --i) {
-      var script = /** @type {!HTMLScriptElement} */ (scripts[i]);
-      var src = script.src;
-      var qmark = src.lastIndexOf('?');
-      var l = qmark == -1 ? src.length : qmark;
-      if (src.substr(l - 7, 7) == 'base.js') {
-        goog.basePath = src.substr(0, l - 7);
-        return;
-      }
-    }
-  };
-
-
-  /**
-   * Imports a script if, and only if, that script hasn't already been imported.
-   * (Must be called at execution time)
-   * @param {string} src Script source.
-   * @param {string=} opt_sourceText The optionally source text to evaluate
-   * @private
-   */
-  goog.importScript_ = function(src, opt_sourceText) {
-    var importScript =
-        goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_;
-    if (importScript(src, opt_sourceText)) {
-      goog.dependencies_.written[src] = true;
-    }
-  };
-
-
-  /**
-   * Whether the browser is IE9 or earlier, which needs special handling
-   * for deferred modules.
-   * @const @private {boolean}
-   */
-  goog.IS_OLD_IE_ =
-      !!(!goog.global.atob && goog.global.document && goog.global.document.all);
-
-
-  /**
-   * Given a URL initiate retrieval and execution of a script that needs
-   * pre-processing.
-   * @param {string} src Script source URL.
-   * @param {boolean} isModule Whether this is a goog.module.
-   * @param {boolean} needsTranspile Whether this source needs transpilation.
-   * @private
-   */
-  goog.importProcessedScript_ = function(src, isModule, needsTranspile) {
-    // In an attempt to keep browsers from timing out loading scripts using
-    // synchronous XHRs, put each load in its own script block.
-    var bootstrap = 'goog.retrieveAndExec_("' + src + '", ' + isModule + ', ' +
-        needsTranspile + ');';
-
-    goog.importScript_('', bootstrap);
-  };
-
-
-  /** @private {!Array<string>} */
-  goog.queuedModules_ = [];
-
-
-  /**
-   * Return an appropriate module text. Suitable to insert into
-   * a script tag (that is unescaped).
-   * @param {string} srcUrl
-   * @param {string} scriptText
-   * @return {string}
-   * @private
-   */
-  goog.wrapModule_ = function(srcUrl, scriptText) {
-    if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) {
-      return '' +
-          'goog.loadModule(function(exports) {' +
-          '"use strict";' + scriptText +
-          '\n' +  // terminate any trailing single line comment.
-          ';return exports' +
-          '});' +
-          '\n//# sourceURL=' + srcUrl + '\n';
-    } else {
-      return '' +
-          'goog.loadModule(' +
-          goog.global.JSON.stringify(
-              scriptText + '\n//# sourceURL=' + srcUrl + '\n') +
-          ');';
-    }
-  };
-
-  // On IE9 and earlier, it is necessary to handle
-  // deferred module loads. In later browsers, the
-  // code to be evaluated is simply inserted as a script
-  // block in the correct order. To eval deferred
-  // code at the right time, we piggy back on goog.require to call
-  // goog.maybeProcessDeferredDep_.
-  //
-  // The goog.requires are used both to bootstrap
-  // the loading process (when no deps are available) and
-  // declare that they should be available.
-  //
-  // Here we eval the sources, if all the deps are available
-  // either already eval'd or goog.require'd.  This will
-  // be the case when all the dependencies have already
-  // been loaded, and the dependent module is loaded.
-  //
-  // But this alone isn't sufficient because it is also
-  // necessary to handle the case where there is no root
-  // that is not deferred.  For that there we register for an event
-  // and trigger goog.loadQueuedModules_ handle any remaining deferred
-  // evaluations.
-
-  /**
-   * Handle any remaining deferred goog.module evals.
-   * @private
-   */
-  goog.loadQueuedModules_ = function() {
-    var count = goog.queuedModules_.length;
-    if (count > 0) {
-      var queue = goog.queuedModules_;
-      goog.queuedModules_ = [];
-      for (var i = 0; i < count; i++) {
-        var path = queue[i];
-        goog.maybeProcessDeferredPath_(path);
-      }
-    }
-  };
-
-
-  /**
-   * Eval the named module if its dependencies are
-   * available.
-   * @param {string} name The module to load.
-   * @private
-   */
-  goog.maybeProcessDeferredDep_ = function(name) {
-    if (goog.isDeferredModule_(name) && goog.allDepsAreAvailable_(name)) {
-      var path = goog.getPathFromDeps_(name);
-      goog.maybeProcessDeferredPath_(goog.basePath + path);
-    }
-  };
-
-  /**
-   * @param {string} name The module to check.
-   * @return {boolean} Whether the name represents a
-   *     module whose evaluation has been deferred.
-   * @private
-   */
-  goog.isDeferredModule_ = function(name) {
-    var path = goog.getPathFromDeps_(name);
-    var loadFlags = path && goog.dependencies_.loadFlags[path] || {};
-    if (path && (loadFlags['module'] == 'goog' ||
-                 goog.needsTranspile_(loadFlags['lang']))) {
-      var abspath = goog.basePath + path;
-      return (abspath) in goog.dependencies_.deferred;
-    }
-    return false;
-  };
-
-  /**
-   * @param {string} name The module to check.
-   * @return {boolean} Whether the name represents a
-   *     module whose declared dependencies have all been loaded
-   *     (eval'd or a deferred module load)
-   * @private
-   */
-  goog.allDepsAreAvailable_ = function(name) {
-    var path = goog.getPathFromDeps_(name);
-    if (path && (path in goog.dependencies_.requires)) {
-      for (var requireName in goog.dependencies_.requires[path]) {
-        if (!goog.isProvided_(requireName) &&
-            !goog.isDeferredModule_(requireName)) {
-          return false;
-        }
-      }
-    }
-    return true;
-  };
-
-
-  /**
-   * @param {string} abspath
-   * @private
-   */
-  goog.maybeProcessDeferredPath_ = function(abspath) {
-    if (abspath in goog.dependencies_.deferred) {
-      var src = goog.dependencies_.deferred[abspath];
-      delete goog.dependencies_.deferred[abspath];
-      goog.globalEval(src);
-    }
-  };
-
-
-  /**
-   * Load a goog.module from the provided URL.  This is not a general purpose
-   * code loader and does not support late loading code, that is it should only
-   * be used during page load. This method exists to support unit tests and
-   * "debug" loaders that would otherwise have inserted script tags. Under the
-   * hood this needs to use a synchronous XHR and is not recommeneded for
-   * production code.
-   *
-   * The module's goog.requires must have already been satisified; an exception
-   * will be thrown if this is not the case. This assumption is that no
-   * "deps.js" file exists, so there is no way to discover and locate the
-   * module-to-be-loaded's dependencies and no attempt is made to do so.
-   *
-   * There should only be one attempt to load a module.  If
-   * "goog.loadModuleFromUrl" is called for an already loaded module, an
-   * exception will be throw.
-   *
-   * @param {string} url The URL from which to attempt to load the goog.module.
-   */
-  goog.loadModuleFromUrl = function(url) {
-    // Because this executes synchronously, we don't need to do any additional
-    // bookkeeping. When "goog.loadModule" the namespace will be marked as
-    // having been provided which is sufficient.
-    goog.retrieveAndExec_(url, true, false);
-  };
-
-
-  /**
-   * Writes a new script pointing to {@code src} directly into the DOM.
-   *
-   * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for
-   * the fallback mechanism.
-   *
-   * @param {string} src The script URL.
-   * @private
-   */
-  goog.writeScriptSrcNode_ = function(src) {
-    goog.global.document.write(
-        '<script type="text/javascript" src="' + src + '"></' +
-        'script>');
-  };
-
-
-  /**
-   * Appends a new script node to the DOM using a CSP-compliant mechanism. This
-   * method exists as a fallback for document.write (which is not allowed in a
-   * strict CSP context, e.g., Chrome apps).
-   *
-   * NOTE: This method is not analogous to using document.write to insert a
-   * <script> tag; specifically, the user agent will execute a script added by
-   * document.write immediately after the current script block finishes
-   * executing, whereas the DOM-appended script node will not be executed until
-   * the entire document is parsed and executed. That is to say, this script is
-   * added to the end of the script execution queue.
-   *
-   * The page must not attempt to call goog.required entities until after the
-   * document has loaded, e.g., in or after the window.onload callback.
-   *
-   * @param {string} src The script URL.
-   * @private
-   */
-  goog.appendScriptSrcNode_ = function(src) {
-    /** @type {Document} */
-    var doc = goog.global.document;
-    var scriptEl =
-        /** @type {HTMLScriptElement} */ (doc.createElement('script'));
-    scriptEl.type = 'text/javascript';
-    scriptEl.src = src;
-    scriptEl.defer = false;
-    scriptEl.async = false;
-    doc.head.appendChild(scriptEl);
-  };
-
-
-  /**
-   * The default implementation of the import function. Writes a script tag to
-   * import the script.
-   *
-   * @param {string} src The script url.
-   * @param {string=} opt_sourceText The optionally source text to evaluate
-   * @return {boolean} True if the script was imported, false otherwise.
-   * @private
-   */
-  goog.writeScriptTag_ = function(src, opt_sourceText) {
-    if (goog.inHtmlDocument_()) {
-      /** @type {!HTMLDocument} */
-      var doc = goog.global.document;
-
-      // If the user tries to require a new symbol after document load,
-      // something has gone terribly wrong. Doing a document.write would
-      // wipe out the page. This does not apply to the CSP-compliant method
-      // of writing script tags.
-      if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING &&
-          doc.readyState == 'complete') {
-        // Certain test frameworks load base.js multiple times, which tries
-        // to write deps.js each time. If that happens, just fail silently.
-        // These frameworks wipe the page between each load of base.js, so this
-        // is OK.
-        var isDeps = /\bdeps.js$/.test(src);
-        if (isDeps) {
-          return false;
-        } else {
-          throw Error('Cannot write "' + src + '" after document load');
-        }
-      }
-
-      if (opt_sourceText === undefined) {
-        if (!goog.IS_OLD_IE_) {
-          if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) {
-            goog.appendScriptSrcNode_(src);
-          } else {
-            goog.writeScriptSrcNode_(src);
-          }
-        } else {
-          var state = " onreadystatechange='goog.onScriptLoad_(this, " +
-              ++goog.lastNonModuleScriptIndex_ + ")' ";
-          doc.write(
-              '<script type="text/javascript" src="' + src + '"' + state +
-              '></' +
-              'script>');
-        }
-      } else {
-        doc.write(
-            '<script type="text/javascript">' + opt_sourceText + '</' +
-            'script>');
-      }
-      return true;
-    } else {
-      return false;
-    }
-  };
-
-
-  /**
-   * Determines whether the given language needs to be transpiled.
-   * @param {string} lang
-   * @return {boolean}
-   * @private
-   */
-  goog.needsTranspile_ = function(lang) {
-    if (goog.TRANSPILE == 'always') {
-      return true;
-    } else if (goog.TRANSPILE == 'never') {
-      return false;
-    } else if (!goog.transpiledLanguages_) {
-      goog.transpiledLanguages_ = {'es5': true, 'es6': true, 'es6-impl': true};
-      /** @preserveTry */
-      try {
-        // Perform some quick conformance checks, to distinguish
-        // between browsers that support es5, es6-impl, or es6.
-
-        // Identify ES3-only browsers by their incorrect treatment of commas.
-        goog.transpiledLanguages_['es5'] = eval('[1,].length!=1');
-
-        // As browsers mature, features will be moved from the full test
-        // into the impl test.  This must happen before the corresponding
-        // features are changed in the Closure Compiler's FeatureSet object.
-
-        // Test 1: es6-impl [FF49, Edge 13, Chrome 49]
-        //   (a) let/const keyword, (b) class expressions, (c) Map object,
-        //   (d) iterable arguments, (e) spread operator
-        var es6implTest =
-            'let a={};const X=class{constructor(){}x(z){return new Map([' +
-            '...arguments]).get(z[0])==3}};return new X().x([a,3])';
-
-        // Test 2: es6 [FF50 (?), Edge 14 (?), Chrome 50]
-        //   (a) default params (specifically shadowing locals),
-        //   (b) destructuring, (c) block-scoped functions,
-        //   (d) for-of (const), (e) new.target/Reflect.construct
-        var es6fullTest =
-            'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' +
-            'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' +
-            'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' +
-            'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' +
-            '==3}';
-
-        if (eval('(()=>{"use strict";' + es6implTest + '})()')) {
-          goog.transpiledLanguages_['es6-impl'] = false;
-        }
-        if (eval('(()=>{"use strict";' + es6fullTest + '})()')) {
-          goog.transpiledLanguages_['es6'] = false;
-        }
-      } catch (err) {
-      }
-    }
-    return !!goog.transpiledLanguages_[lang];
-  };
-
-
-  /** @private {?Object<string, boolean>} */
-  goog.transpiledLanguages_ = null;
-
-
-  /** @private {number} */
-  goog.lastNonModuleScriptIndex_ = 0;
-
-
-  /**
-   * A readystatechange handler for legacy IE
-   * @param {!HTMLScriptElement} script
-   * @param {number} scriptIndex
-   * @return {boolean}
-   * @private
-   */
-  goog.onScriptLoad_ = function(script, scriptIndex) {
-    // for now load the modules when we reach the last script,
-    // later allow more inter-mingling.
-    if (script.readyState == 'complete' &&
-        goog.lastNonModuleScriptIndex_ == scriptIndex) {
-      goog.loadQueuedModules_();
-    }
-    return true;
-  };
-
-  /**
-   * Resolves dependencies based on the dependencies added using addDependency
-   * and calls importScript_ in the correct order.
-   * @param {string} pathToLoad The path from which to start discovering
-   *     dependencies.
-   * @private
-   */
-  goog.writeScripts_ = function(pathToLoad) {
-    /** @type {!Array<string>} The scripts we need to write this time. */
-    var scripts = [];
-    var seenScript = {};
-    var deps = goog.dependencies_;
-
-    /** @param {string} path */
-    function visitNode(path) {
-      if (path in deps.written) {
-        return;
-      }
-
-      // We have already visited this one. We can get here if we have cyclic
-      // dependencies.
-      if (path in deps.visited) {
-        return;
-      }
-
-      deps.visited[path] = true;
-
-      if (path in deps.requires) {
-        for (var requireName in deps.requires[path]) {
-          // If the required name is defined, we assume that it was already
-          // bootstrapped by other means.
-          if (!goog.isProvided_(requireName)) {
-            if (requireName in deps.nameToPath) {
-              visitNode(deps.nameToPath[requireName]);
-            } else {
-              throw Error('Undefined nameToPath for ' + requireName);
-            }
-          }
-        }
-      }
-
-      if (!(path in seenScript)) {
-        seenScript[path] = true;
-        scripts.push(path);
-      }
-    }
-
-    visitNode(pathToLoad);
-
-    // record that we are going to load all these scripts.
-    for (var i = 0; i < scripts.length; i++) {
-      var path = scripts[i];
-      goog.dependencies_.written[path] = true;
-    }
-
-    // If a module is loaded synchronously then we need to
-    // clear the current inModuleLoader value, and restore it when we are
-    // done loading the current "requires".
-    var moduleState = goog.moduleLoaderState_;
-    goog.moduleLoaderState_ = null;
-
-    for (var i = 0; i < scripts.length; i++) {
-      var path = scripts[i];
-      if (path) {
-        var loadFlags = deps.loadFlags[path] || {};
-        var needsTranspile = goog.needsTranspile_(loadFlags['lang']);
-        if (loadFlags['module'] == 'goog' || needsTranspile) {
-          goog.importProcessedScript_(
-              goog.basePath + path, loadFlags['module'] == 'goog',
-              needsTranspile);
-        } else {
-          goog.importScript_(goog.basePath + path);
-        }
-      } else {
-        goog.moduleLoaderState_ = moduleState;
-        throw Error('Undefined script input');
-      }
-    }
-
-    // restore the current "module loading state"
-    goog.moduleLoaderState_ = moduleState;
-  };
-
-
-  /**
-   * Looks at the dependency rules and tries to determine the script file that
-   * fulfills a particular rule.
-   * @param {string} rule In the form goog.namespace.Class or project.script.
-   * @return {?string} Url corresponding to the rule, or null.
-   * @private
-   */
-  goog.getPathFromDeps_ = function(rule) {
-    if (rule in goog.dependencies_.nameToPath) {
-      return goog.dependencies_.nameToPath[rule];
-    } else {
-      return null;
-    }
-  };
-
-  goog.findBasePath_();
-
-  // Allow projects to manage the deps files themselves.
-  if (!goog.global.CLOSURE_NO_DEPS) {
-    goog.importScript_(goog.basePath + 'deps.js');
-  }
-}
-
-
-/**
- * @param {function(?):?|string} moduleDef The module definition.
- */
-goog.loadModule = function(moduleDef) {
-  // NOTE: we allow function definitions to be either in the from
-  // of a string to eval (which keeps the original source intact) or
-  // in a eval forbidden environment (CSP) we allow a function definition
-  // which in its body must call {@code goog.module}, and return the exports
-  // of the module.
-  var previousState = goog.moduleLoaderState_;
-  try {
-    goog.moduleLoaderState_ = {
-      moduleName: undefined,
-      declareLegacyNamespace: false
-    };
-    var exports;
-    if (goog.isFunction(moduleDef)) {
-      exports = moduleDef.call(undefined, {});
-    } else if (goog.isString(moduleDef)) {
-      exports = goog.loadModuleFromSource_.call(undefined, moduleDef);
-    } else {
-      throw Error('Invalid module definition');
-    }
-
-    var moduleName = goog.moduleLoaderState_.moduleName;
-    if (!goog.isString(moduleName) || !moduleName) {
-      throw Error('Invalid module name \"' + moduleName + '\"');
-    }
-
-    // Don't seal legacy namespaces as they may be uses as a parent of
-    // another namespace
-    if (goog.moduleLoaderState_.declareLegacyNamespace) {
-      goog.constructNamespace_(moduleName, exports);
-    } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) {
-      Object.seal(exports);
-    }
-
-    goog.loadedModules_[moduleName] = exports;
-  } finally {
-    goog.moduleLoaderState_ = previousState;
-  }
-};
-
-
-/**
- * @private @const {function(string):?}
- *
- * The new type inference warns because this function has no formal
- * parameters, but its jsdoc says that it takes one argument.
- * (The argument is used via arguments[0], but NTI does not detect this.)
- * @suppress {newCheckTypes}
- */
-goog.loadModuleFromSource_ = function() {
-  // NOTE: we avoid declaring parameters or local variables here to avoid
-  // masking globals or leaking values into the module definition.
-  'use strict';
-  var exports = {};
-  eval(arguments[0]);
-  return exports;
-};
-
-
-/**
- * Normalize a file path by removing redundant ".." and extraneous "." file
- * path components.
- * @param {string} path
- * @return {string}
- * @private
- */
-goog.normalizePath_ = function(path) {
-  var components = path.split('/');
-  var i = 0;
-  while (i < components.length) {
-    if (components[i] == '.') {
-      components.splice(i, 1);
-    } else if (
-        i && components[i] == '..' && components[i - 1] &&
-        components[i - 1] != '..') {
-      components.splice(--i, 2);
-    } else {
-      i++;
-    }
-  }
-  return components.join('/');
-};
-
-
-/**
- * Loads file by synchronous XHR. Should not be used in production environments.
- * @param {string} src Source URL.
- * @return {?string} File contents, or null if load failed.
- * @private
- */
-goog.loadFileSync_ = function(src) {
-  if (goog.global.CLOSURE_LOAD_FILE_SYNC) {
-    return goog.global.CLOSURE_LOAD_FILE_SYNC(src);
-  } else {
-    try {
-      /** @type {XMLHttpRequest} */
-      var xhr = new goog.global['XMLHttpRequest']();
-      xhr.open('get', src, false);
-      xhr.send();
-      // NOTE: Successful http: requests have a status of 200, but successful
-      // file: requests may have a status of zero.  Any other status, or a
-      // thrown exception (particularly in case of file: requests) indicates
-      // some sort of error, which we treat as a missing or unavailable file.
-      return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null;
-    } catch (err) {
-      // No need to rethrow or log, since errors should show up on their own.
-      return null;
-    }
-  }
-};
-
-
-/**
- * Retrieve and execute a script that needs some sort of wrapping.
- * @param {string} src Script source URL.
- * @param {boolean} isModule Whether to load as a module.
- * @param {boolean} needsTranspile Whether to transpile down to ES3.
- * @private
- */
-goog.retrieveAndExec_ = function(src, isModule, needsTranspile) {
-  if (!COMPILED) {
-    // The full but non-canonicalized URL for later use.
-    var originalPath = src;
-    // Canonicalize the path, removing any /./ or /../ since Chrome's debugging
-    // console doesn't auto-canonicalize XHR loads as it does <script> srcs.
-    src = goog.normalizePath_(src);
-
-    var importScript =
-        goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_;
-
-    var scriptText = goog.loadFileSync_(src);
-    if (scriptText == null) {
-      throw new Error('Load of "' + src + '" failed');
-    }
-
-    if (needsTranspile) {
-      scriptText = goog.transpile_.call(goog.global, scriptText, src);
-    }
-
-    if (isModule) {
-      scriptText = goog.wrapModule_(src, scriptText);
-    } else {
-      scriptText += '\n//# sourceURL=' + src;
-    }
-    var isOldIE = goog.IS_OLD_IE_;
-    if (isOldIE) {
-      goog.dependencies_.deferred[originalPath] = scriptText;
-      goog.queuedModules_.push(originalPath);
-    } else {
-      importScript(src, scriptText);
-    }
-  }
-};
-
-
-/**
- * Lazily retrieves the transpiler and applies it to the source.
- * @param {string} code JS code.
- * @param {string} path Path to the code.
- * @return {string} The transpiled code.
- * @private
- */
-goog.transpile_ = function(code, path) {
-  var jscomp = goog.global['$jscomp'];
-  if (!jscomp) {
-    goog.global['$jscomp'] = jscomp = {};
-  }
-  var transpile = jscomp.transpile;
-  if (!transpile) {
-    var transpilerPath = goog.basePath + goog.TRANSPILER;
-    var transpilerCode = goog.loadFileSync_(transpilerPath);
-    if (transpilerCode) {
-      // This must be executed synchronously, since by the time we know we
-      // need it, we're about to load and write the ES6 code synchronously,
-      // so a normal script-tag load will be too slow.
-      eval(transpilerCode + '\n//# sourceURL=' + transpilerPath);
-      // Note: transpile.js reassigns goog.global['$jscomp'] so pull it again.
-      jscomp = goog.global['$jscomp'];
-      transpile = jscomp.transpile;
-    }
-  }
-  if (!transpile) {
-    // The transpiler is an optional component.  If it's not available then
-    // replace it with a pass-through function that simply logs.
-    var suffix = ' requires transpilation but no transpiler was found.';
-    transpile = jscomp.transpile = function(code, path) {
-      // TODO(user): figure out some way to get this error to show up
-      // in test results, noting that the failure may occur in many
-      // different ways, including in loadModule() before the test
-      // runner even comes up.
-      goog.logToConsole_(path + suffix);
-      return code;
-    };
-  }
-  // Note: any transpilation errors/warnings will be logged to the console.
-  return transpile(code, path);
-};
-
-
-//==============================================================================
-// Language Enhancements
-//==============================================================================
-
-
-/**
- * This is a "fixed" version of the typeof operator.  It differs from the typeof
- * operator in such a way that null returns 'null' and arrays return 'array'.
- * @param {?} value The value to get the type of.
- * @return {string} The name of the type.
- */
-goog.typeOf = function(value) {
-  var s = typeof value;
-  if (s == 'object') {
-    if (value) {
-      // Check these first, so we can avoid calling Object.prototype.toString if
-      // possible.
-      //
-      // IE improperly marshals typeof across execution contexts, but a
-      // cross-context object will still return false for "instanceof Object".
-      if (value instanceof Array) {
-        return 'array';
-      } else if (value instanceof Object) {
-        return s;
-      }
-
-      // HACK: In order to use an Object prototype method on the arbitrary
-      //   value, the compiler requires the value be cast to type Object,
-      //   even though the ECMA spec explicitly allows it.
-      var className = Object.prototype.toString.call(
-          /** @type {!Object} */ (value));
-      // In Firefox 3.6, attempting to access iframe window objects' length
-      // property throws an NS_ERROR_FAILURE, so we need to special-case it
-      // here.
-      if (className == '[object Window]') {
-        return 'object';
-      }
-
-      // We cannot always use constructor == Array or instanceof Array because
-      // different frames have different Array objects. In IE6, if the iframe
-      // where the array was created is destroyed, the array loses its
-      // prototype. Then dereferencing val.splice here throws an exception, so
-      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
-      // so that will work. In this case, this function will return false and
-      // most array functions will still work because the array is still
-      // array-like (supports length and []) even though it has lost its
-      // prototype.
-      // Mark Miller noticed that Object.prototype.toString
-      // allows access to the unforgeable [[Class]] property.
-      //  15.2.4.2 Object.prototype.toString ( )
-      //  When the toString method is called, the following steps are taken:
-      //      1. Get the [[Class]] property of this object.
-      //      2. Compute a string value by concatenating the three strings
-      //         "[object ", Result(1), and "]".
-      //      3. Return Result(2).
-      // and this behavior survives the destruction of the execution context.
-      if ((className == '[object Array]' ||
-           // In IE all non value types are wrapped as objects across window
-           // boundaries (not iframe though) so we have to do object detection
-           // for this edge case.
-           typeof value.length == 'number' &&
-               typeof value.splice != 'undefined' &&
-               typeof value.propertyIsEnumerable != 'undefined' &&
-               !value.propertyIsEnumerable('splice')
-
-               )) {
-        return 'array';
-      }
-      // HACK: There is still an array case that fails.
-      //     function ArrayImpostor() {}
-      //     ArrayImpostor.prototype = [];
-      //     var impostor = new ArrayImpostor;
-      // this can be fixed by getting rid of the fast path
-      // (value instanceof Array) and solely relying on
-      // (value && Object.prototype.toString.vall(value) === '[object Array]')
-      // but that would require many more function calls and is not warranted
-      // unless closure code is receiving objects from untrusted sources.
-
-      // IE in cross-window calls does not correctly marshal the function type
-      // (it appears just as an object) so we cannot use just typeof val ==
-      // 'function'. However, if the object has a call property, it is a
-      // function.
-      if ((className == '[object Function]' ||
-           typeof value.call != 'undefined' &&
-               typeof value.propertyIsEnumerable != 'undefined' &&
-               !value.propertyIsEnumerable('call'))) {
-        return 'function';
-      }
-
-    } else {
-      return 'null';
-    }
-
-  } else if (s == 'function' && typeof value.call == 'undefined') {
-    // In Safari typeof nodeList returns 'function', and on Firefox typeof
-    // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We
-    // would like to return object for those and we can detect an invalid
-    // function by making sure that the function object has a call method.
-    return 'object';
-  }
-  return s;
-};
-
-
-/**
- * Returns true if the specified value is null.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is null.
- */
-goog.isNull = function(val) {
-  return val === null;
-};
-
-
-/**
- * Returns true if the specified value is defined and not null.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is defined and not null.
- */
-goog.isDefAndNotNull = function(val) {
-  // Note that undefined == null.
-  return val != null;
-};
-
-
-/**
- * Returns true if the specified value is an array.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is an array.
- */
-goog.isArray = function(val) {
-  return goog.typeOf(val) == 'array';
-};
-
-
-/**
- * Returns true if the object looks like an array. To qualify as array like
- * the value needs to be either a NodeList or an object with a Number length
- * property. As a special case, a function value is not array like, because its
- * length property is fixed to correspond to the number of expected arguments.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is an array.
- */
-goog.isArrayLike = function(val) {
-  var type = goog.typeOf(val);
-  // We do not use goog.isObject here in order to exclude function values.
-  return type == 'array' || type == 'object' && typeof val.length == 'number';
-};
-
-
-/**
- * Returns true if the object looks like a Date. To qualify as Date-like the
- * value needs to be an object and have a getFullYear() function.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is a like a Date.
- */
-goog.isDateLike = function(val) {
-  return goog.isObject(val) && typeof val.getFullYear == 'function';
-};
-
-
-/**
- * Returns true if the specified value is a string.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is a string.
- */
-goog.isString = function(val) {
-  return typeof val == 'string';
-};
-
-
-/**
- * Returns true if the specified value is a boolean.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is boolean.
- */
-goog.isBoolean = function(val) {
-  return typeof val == 'boolean';
-};
-
-
-/**
- * Returns true if the specified value is a number.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is a number.
- */
-goog.isNumber = function(val) {
-  return typeof val == 'number';
-};
-
-
-/**
- * Returns true if the specified value is a function.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is a function.
- */
-goog.isFunction = function(val) {
-  return goog.typeOf(val) == 'function';
-};
-
-
-/**
- * Returns true if the specified value is an object.  This includes arrays and
- * functions.
- * @param {?} val Variable to test.
- * @return {boolean} Whether variable is an object.
- */
-goog.isObject = function(val) {
-  var type = typeof val;
-  return type == 'object' && val != null || type == 'function';
-  // return Object(val) === val also works, but is slower, especially if val is
-  // not an object.
-};
-
-
-/**
- * Gets a unique ID for an object. This mutates the object so that further calls
- * with the same object as a parameter returns the same value. The unique ID is
- * guaranteed to be unique across the current session amongst objects that are
- * passed into {@code getUid}. There is no guarantee that the ID is unique or
- * consistent across sessions. It is unsafe to generate unique ID for function
- * prototypes.
- *
- * @param {Object} obj The object to get the unique ID for.
- * @return {number} The unique ID for the object.
- */
-goog.getUid = function(obj) {
-  // TODO(arv): Make the type stricter, do not accept null.
-
-  // In Opera window.hasOwnProperty exists but always returns false so we avoid
-  // using it. As a consequence the unique ID generated for BaseClass.prototype
-  // and SubClass.prototype will be the same.
-  return obj[goog.UID_PROPERTY_] ||
-      (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
-};
-
-
-/**
- * Whether the given object is already assigned a unique ID.
- *
- * This does not modify the object.
- *
- * @param {!Object} obj The object to check.
- * @return {boolean} Whether there is an assigned unique id for the object.
- */
-goog.hasUid = function(obj) {
-  return !!obj[goog.UID_PROPERTY_];
-};
-
-
-/**
- * Removes the unique ID from an object. This is useful if the object was
- * previously mutated using {@code goog.getUid} in which case the mutation is
- * undone.
- * @param {Object} obj The object to remove the unique ID field from.
- */
-goog.removeUid = function(obj) {
-  // TODO(arv): Make the type stricter, do not accept null.
-
-  // In IE, DOM nodes are not instances of Object and throw an exception if we
-  // try to delete.  Instead we try to use removeAttribute.
-  if (obj !== null && 'removeAttribute' in obj) {
-    obj.removeAttribute(goog.UID_PROPERTY_);
-  }
-  /** @preserveTry */
-  try {
-    delete obj[goog.UID_PROPERTY_];
-  } catch (ex) {
-  }
-};
-
-
-/**
- * Name for unique ID property. Initialized in a way to help avoid collisions
- * with other closure JavaScript on the same page.
- * @type {string}
- * @private
- */
-goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);
-
-
-/**
- * Counter for UID.
- * @type {number}
- * @private
- */
-goog.uidCounter_ = 0;
-
-
-/**
- * Adds a hash code field to an object. The hash code is unique for the
- * given object.
- * @param {Object} obj The object to get the hash code for.
- * @return {number} The hash code for the object.
- * @deprecated Use goog.getUid instead.
- */
-goog.getHashCode = goog.getUid;
-
-
-/**
- * Removes the hash code field from an object.
- * @param {Object} obj The object to remove the field from.
- * @deprecated Use goog.removeUid instead.
- */
-goog.removeHashCode = goog.removeUid;
-
-
-/**
- * Clones a value. The input may be an Object, Array, or basic type. Objects and
- * arrays will be cloned recursively.
- *
- * WARNINGS:
- * <code>goog.cloneObject</code> does not detect reference loops. Objects that
- * refer to themselves will cause infinite recursion.
- *
- * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
- * UIDs created by <code>getUid</code> into cloned results.
- *
- * @param {*} obj The value to clone.
- * @return {*} A clone of the input value.
- * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
- */
-goog.cloneObject = function(obj) {
-  var type = goog.typeOf(obj);
-  if (type == 'object' || type == 'array') {
-    if (obj.clone) {
-      return obj.clone();
-    }
-    var clone = type == 'array' ? [] : {};
-    for (var key in obj) {
-      clone[key] = goog.cloneObject(obj[key]);
-    }
-    return clone;
-  }
-
-  return obj;
-};
-
-
-/**
- * A native implementation of goog.bind.
- * @param {Function} fn A function to partially apply.
- * @param {Object|undefined} selfObj Specifies the object which this should
- *     point to when the function is run.
- * @param {...*} var_args Additional arguments that are partially applied to the
- *     function.
- * @return {!Function} A partially-applied form of the function bind() was
- *     invoked as a method of.
- * @private
- * @suppress {deprecated} The compiler thinks that Function.prototype.bind is
- *     deprecated because some people have declared a pure-JS version.
- *     Only the pure-JS version is truly deprecated.
- */
-goog.bindNative_ = function(fn, selfObj, var_args) {
-  return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
-};
-
-
-/**
- * A pure-JS implementation of goog.bind.
- * @param {Function} fn A function to partially apply.
- * @param {Object|undefined} selfObj Specifies the object which this should
- *     point to when the function is run.
- * @param {...*} var_args Additional arguments that are partially applied to the
- *     function.
- * @return {!Function} A partially-applied form of the function bind() was
- *     invoked as a method of.
- * @private
- */
-goog.bindJs_ = function(fn, selfObj, var_args) {
-  if (!fn) {
-    throw new Error();
-  }
-
-  if (arguments.length > 2) {
-    var boundArgs = Array.prototype.slice.call(arguments, 2);
-    return function() {
-      // Prepend the bound arguments to the current arguments.
-      var newArgs = Array.prototype.slice.call(arguments);
-      Array.prototype.unshift.apply(newArgs, boundArgs);
-      return fn.apply(selfObj, newArgs);
-    };
-
-  } else {
-    return function() { return fn.apply(selfObj, arguments); };
-  }
-};
-
-
-/**
- * Partially applies this function to a particular 'this object' and zero or
- * more arguments. The result is a new function with some arguments of the first
- * function pre-filled and the value of this 'pre-specified'.
- *
- * Remaining arguments specified at call-time are appended to the pre-specified
- * ones.
- *
- * Also see: {@link #partial}.
- *
- * Usage:
- * <pre>var barMethBound = goog.bind(myFunction, myObj, 'arg1', 'arg2');
- * barMethBound('arg3', 'arg4');</pre>
- *
- * @param {?function(this:T, ...)} fn A function to partially apply.
- * @param {T} selfObj Specifies the object which this should point to when the
- *     function is run.
- * @param {...*} var_args Additional arguments that are partially applied to the
- *     function.
- * @return {!Function} A partially-applied form of the function goog.bind() was
- *     invoked as a method of.
- * @template T
- * @suppress {deprecated} See above.
- */
-goog.bind = function(fn, selfObj, var_args) {
-  // TODO(nicksantos): narrow the type signature.
-  if (Function.prototype.bind &&
-      // NOTE(nicksantos): Somebody pulled base.js into the default Chrome
-      // extension environment. This means that for Chrome extensions, they get
-      // the implementation of Function.prototype.bind that calls goog.bind
-      // instead of the native one. Even worse, we don't want to introduce a
-      // circular dependency between goog.bind and Function.prototype.bind, so
-      // we have to hack this to make sure it works correctly.
-      Function.prototype.bind.toString().indexOf('native code') != -1) {
-    goog.bind = goog.bindNative_;
-  } else {
-    goog.bind = goog.bindJs_;
-  }
-  return goog.bind.apply(null, arguments);
-};
-
-
-/**
- * Like goog.bind(), except that a 'this object' is not required. Useful when
- * the target function is already bound.
- *
- * Usage:
- * var g = goog.partial(f, arg1, arg2);
- * g(arg3, arg4);
- *
- * @param {Function} fn A function to partially apply.
- * @param {...*} var_args Additional arguments that are partially applied to fn.
- * @return {!Function} A partially-applied form of the function goog.partial()
- *     was invoked as a method of.
- */
-goog.partial = function(fn, var_args) {
-  var args = Array.prototype.slice.call(arguments, 1);
-  return function() {
-    // Clone the array (with slice()) and append additional arguments
-    // to the existing arguments.
-    var newArgs = args.slice();
-    newArgs.push.apply(newArgs, arguments);
-    return fn.apply(this, newArgs);
-  };
-};
-
-
-/**
- * Copies all the members of a source object to a target object. This method
- * does not work on all browsers for all objects that contain keys such as
- * toString or hasOwnProperty. Use goog.object.extend for this purpose.
- * @param {Object} target Target.
- * @param {Object} source Source.
- */
-goog.mixin = function(target, source) {
-  for (var x in source) {
-    target[x] = source[x];
-  }
-
-  // For IE7 or lower, the for-in-loop does not contain any properties that are
-  // not enumerable on the prototype object (for example, isPrototypeOf from
-  // Object.prototype) but also it will not include 'replace' on objects that
-  // extend String and change 'replace' (not that it is common for anyone to
-  // extend anything except Object).
-};
-
-
-/**
- * @return {number} An integer value representing the number of milliseconds
- *     between midnight, January 1, 1970 and the current time.
- */
-goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
-             // Unary plus operator converts its operand to a number which in
-             // the case of
-             // a date is done by calling getTime().
-             return +new Date();
-           });
-
-
-/**
- * Evals JavaScript in the global scope.  In IE this uses execScript, other
- * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
- * global scope (for example, in Safari), appends a script tag instead.
- * Throws an exception if neither execScript or eval is defined.
- * @param {string} script JavaScript string.
- */
-goog.globalEval = function(script) {
-  if (goog.global.execScript) {
-    goog.global.execScript(script, 'JavaScript');
-  } else if (goog.global.eval) {
-    // Test to see if eval works
-    if (goog.evalWorksForGlobals_ == null) {
-      goog.global.eval('var _evalTest_ = 1;');
-      if (typeof goog.global['_evalTest_'] != 'undefined') {
-        try {
-          delete goog.global['_evalTest_'];
-        } catch (ignore) {
-          // Microsoft edge fails the deletion above in strict mode.
-        }
-        goog.evalWorksForGlobals_ = true;
-      } else {
-        goog.evalWorksForGlobals_ = false;
-      }
-    }
-
-    if (goog.evalWorksForGlobals_) {
-      goog.global.eval(script);
-    } else {
-      /** @type {Document} */
-      var doc = goog.global.document;
-      var scriptElt =
-          /** @type {!HTMLScriptElement} */ (doc.createElement('SCRIPT'));
-      scriptElt.type = 'text/javascript';
-      scriptElt.defer = false;
-      // Note(user): can't use .innerHTML since "t('<test>')" will fail and
-      // .text doesn't work in Safari 2.  Therefore we append a text node.
-      scriptElt.appendChild(doc.createTextNode(script));
-      doc.body.appendChild(scriptElt);
-      doc.body.removeChild(scriptElt);
-    }
-  } else {
-    throw Error('goog.globalEval not available');
-  }
-};
-
-
-/**
- * Indicates whether or not we can call 'eval' directly to eval code in the
- * global scope. Set to a Boolean by the first call to goog.globalEval (which
- * empirically tests whether eval works for globals). @see goog.globalEval
- * @type {?boolean}
- * @private
- */
-goog.evalWorksForGlobals_ = null;
-
-
-/**
- * Optional map of CSS class names to obfuscated names used with
- * goog.getCssName().
- * @private {!Object<string, string>|undefined}
- * @see goog.setCssNameMapping
- */
-goog.cssNameMapping_;
-
-
-/**
- * Optional obfuscation style for CSS class names. Should be set to either
- * 'BY_WHOLE' or 'BY_PART' if defined.
- * @type {string|undefined}
- * @private
- * @see goog.setCssNameMapping
- */
-goog.cssNameMappingStyle_;
-
-
-/**
- * Handles strings that are intended to be used as CSS class names.
- *
- * This function works in tandem with @see goog.setCssNameMapping.
- *
- * Without any mapping set, the arguments are simple joined with a hyphen and
- * passed through unaltered.
- *
- * When there is a mapping, there are two possible styles in which these
- * mappings are used. In the BY_PART style, each part (i.e. in between hyphens)
- * of the passed in css name is rewritten according to the map. In the BY_WHOLE
- * style, the full css name is looked up in the map directly. If a rewrite is
- * not specified by the map, the compiler will output a warning.
- *
- * When the mapping is passed to the compiler, it will replace calls to
- * goog.getCssName with the strings from the mapping, e.g.
- *     var x = goog.getCssName('foo');
- *     var y = goog.getCssName(this.baseClass, 'active');
- *  becomes:
- *     var x = 'foo';
- *     var y = this.baseClass + '-active';
- *
- * If one argument is passed it will be processed, if two are passed only the
- * modifier will be processed, as it is assumed the first argument was generated
- * as a result of calling goog.getCssName.
- *
- * @param {string} className The class name.
- * @param {string=} opt_modifier A modifier to be appended to the class name.
- * @return {string} The class name or the concatenation of the class name and
- *     the modifier.
- */
-goog.getCssName = function(className, opt_modifier) {
-  var getMapping = function(cssName) {
-    return goog.cssNameMapping_[cssName] || cssName;
-  };
-
-  var renameByParts = function(cssName) {
-    // Remap all the parts individually.
-    var parts = cssName.split('-');
-    var mapped = [];
-    for (var i = 0; i < parts.length; i++) {
-      mapped.push(getMapping(parts[i]));
-    }
-    return mapped.join('-');
-  };
-
-  var rename;
-  if (goog.cssNameMapping_) {
-    rename =
-        goog.cssNameMappingStyle_ == 'BY_WHOLE' ? getMapping : renameByParts;
-  } else {
-    rename = function(a) { return a; };
-  }
-
-  if (opt_modifier) {
-    return className + '-' + rename(opt_modifier);
-  } else {
-    return rename(className);
-  }
-};
-
-
-/**
- * Sets the map to check when returning a value from goog.getCssName(). Example:
- * <pre>
- * goog.setCssNameMapping({
- *   "goog": "a",
- *   "disabled": "b",
- * });
- *
- * var x = goog.getCssName('goog');
- * // The following evaluates to: "a a-b".
- * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
- * </pre>
- * When declared as a map of string literals to string literals, the JSCompiler
- * will replace all calls to goog.getCssName() using the supplied map if the
- * --process_closure_primitives flag is set.
- *
- * @param {!Object} mapping A map of strings to strings where keys are possible
- *     arguments to goog.getCssName() and values are the corresponding values
- *     that should be returned.
- * @param {string=} opt_style The style of css name mapping. There are two valid
- *     options: 'BY_PART', and 'BY_WHOLE'.
- * @see goog.getCssName for a description.
- */
-goog.setCssNameMapping = function(mapping, opt_style) {
-  goog.cssNameMapping_ = mapping;
-  goog.cssNameMappingStyle_ = opt_style;
-};
-
-
-/**
- * To use CSS renaming in compiled mode, one of the input files should have a
- * call to goog.setCssNameMapping() with an object literal that the JSCompiler
- * can extract and use to replace all calls to goog.getCssName(). In uncompiled
- * mode, JavaScript code should be loaded before this base.js file that declares
- * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
- * to ensure that the mapping is loaded before any calls to goog.getCssName()
- * are made in uncompiled mode.
- *
- * A hook for overriding the CSS name mapping.
- * @type {!Object<string, string>|undefined}
- */
-goog.global.CLOSURE_CSS_NAME_MAPPING;
-
-
-if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
-  // This does not call goog.setCssNameMapping() because the JSCompiler
-  // requires that goog.setCssNameMapping() be called with an object literal.
-  goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
-}
-
-
-/**
- * Gets a localized message.
- *
- * This function is a compiler primitive. If you give the compiler a localized
- * message bundle, it will replace the string at compile-time with a localized
- * version, and expand goog.getMsg call to a concatenated string.
- *
- * Messages must be initialized in the form:
- * <code>
- * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
- * </code>
- *
- * This function produces a string which should be treated as plain text. Use
- * {@link goog.html.SafeHtmlFormatter} in conjunction with goog.getMsg to
- * produce SafeHtml.
- *
- * @param {string} str Translatable string, places holders in the form {$foo}.
- * @param {Object<string, string>=} opt_values Maps place holder name to value.
- * @return {string} message with placeholders filled.
- */
-goog.getMsg = function(str, opt_values) {
-  if (opt_values) {
-    str = str.replace(/\{\$([^}]+)}/g, function(match, key) {
-      return (opt_values != null && key in opt_values) ? opt_values[key] :
-                                                         match;
-    });
-  }
-  return str;
-};
-
-
-/**
- * Gets a localized message. If the message does not have a translation, gives a
- * fallback message.
- *
- * This is useful when introducing a new message that has not yet been
- * translated into all languages.
- *
- * This function is a compiler primitive. Must be used in the form:
- * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
- * where MSG_A and MSG_B were initialized with goog.getMsg.
- *
- * @param {string} a The preferred message.
- * @param {string} b The fallback message.
- * @return {string} The best translated message.
- */
-goog.getMsgWithFallback = function(a, b) {
-  return a;
-};
-
-
-/**
- * Exposes an unobfuscated global namespace path for the given object.
- * Note that fields of the exported object *will* be obfuscated, unless they are
- * exported in turn via this function or goog.exportProperty.
- *
- * Also handy for making public items that are defined in anonymous closures.
- *
- * ex. goog.exportSymbol('public.path.Foo', Foo);
- *
- * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction);
- *     public.path.Foo.staticFunction();
- *
- * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
- *                       Foo.prototype.myMethod);
- *     new public.path.Foo().myMethod();
- *
- * @param {string} publicPath Unobfuscated name to export.
- * @param {*} object Object the name should point to.
- * @param {Object=} opt_objectToExportTo The object to add the path to; default
- *     is goog.global.
- */
-goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
-  goog.exportPath_(publicPath, object, opt_objectToExportTo);
-};
-
-
-/**
- * Exports a property unobfuscated into the object's namespace.
- * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
- * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
- * @param {Object} object Object whose static property is being exported.
- * @param {string} publicName Unobfuscated name to export.
- * @param {*} symbol Object the name should point to.
- */
-goog.exportProperty = function(object, publicName, symbol) {
-  object[publicName] = symbol;
-};
-
-
-/**
- * Inherit the prototype methods from one constructor into another.
- *
- * Usage:
- * <pre>
- * function ParentClass(a, b) { }
- * ParentClass.prototype.foo = function(a) { };
- *
- * function ChildClass(a, b, c) {
- *   ChildClass.base(this, 'constructor', a, b);
- * }
- * goog.inherits(ChildClass, ParentClass);
- *
- * var child = new ChildClass('a', 'b', 'see');
- * child.foo(); // This works.
- * </pre>
- *
- * @param {!Function} childCtor Child class.
- * @param {!Function} parentCtor Parent class.
- */
-goog.inherits = function(childCtor, parentCtor) {
-  /** @constructor */
-  function tempCtor() {}
-  tempCtor.prototype = parentCtor.prototype;
-  childCtor.superClass_ = parentCtor.prototype;
-  childCtor.prototype = new tempCtor();
-  /** @override */
-  childCtor.prototype.constructor = childCtor;
-
-  /**
-   * Calls superclass constructor/method.
-   *
-   * This function is only available if you use goog.inherits to
-   * express inheritance relationships between classes.
-   *
-   * NOTE: This is a replacement for goog.base and for superClass_
-   * property defined in childCtor.
-   *
-   * @param {!Object} me Should always be "this".
-   * @param {string} methodName The method name to call. Calling
-   *     superclass constructor can be done with the special string
-   *     'constructor'.
-   * @param {...*} var_args The arguments to pass to superclass
-   *     method/constructor.
-   * @return {*} The return value of the superclass method/constructor.
-   */
-  childCtor.base = function(me, methodName, var_args) {
-    // Copying using loop to avoid deop due to passing arguments object to
-    // function. This is faster in many JS engines as of late 2014.
-    var args = new Array(arguments.length - 2);
-    for (var i = 2; i < arguments.length; i++) {
-      args[i - 2] = arguments[i];
-    }
-    return parentCtor.prototype[methodName].apply(me, args);
-  };
-};
-
-
-/**
- * Call up to the superclass.
- *
- * If this is called from a constructor, then this calls the superclass
- * constructor with arguments 1-N.
- *
- * If this is called from a prototype method, then you must pass the name of the
- * method as the second argument to this function. If you do not, you will get a
- * runtime error. This calls the superclass' method with arguments 2-N.
- *
- * This function only works if you use goog.inherits to express inheritance
- * relationships between your classes.
- *
- * This function is a compiler primitive. At compile-time, the compiler will do
- * macro expansion to remove a lot of the extra overhead that this function
- * introduces. The compiler will also enforce a lot of the assumptions that this
- * function makes, and treat it as a compiler error if you break them.
- *
- * @param {!Object} me Should always be "this".
- * @param {*=} opt_methodName The method name if calling a super method.
- * @param {...*} var_args The rest of the arguments.
- * @return {*} The return value of the superclass method.
- * @suppress {es5Strict} This method can not be used in strict mode, but
- *     all Closure Library consumers must depend on this file.
- */
-goog.base = function(me, opt_methodName, var_args) {
-  var caller = arguments.callee.caller;
-
-  if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) {
-    throw Error(
-        'arguments.caller not defined.  goog.base() cannot be used ' +
-        'with strict mode code. See ' +
-        'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
-  }
-
-  if (caller.superClass_) {
-    // Copying using loop to avoid deop due to passing arguments object to
-    // function. This is faster in many JS engines as of late 2014.
-    var ctorArgs = new Array(arguments.length - 1);
-    for (var i = 1; i < arguments.length; i++) {
-      ctorArgs[i - 1] = arguments[i];
-    }
-    // This is a constructor. Call the superclass constructor.
-    return caller.superClass_.constructor.apply(me, ctorArgs);
-  }
-
-  // Copying using loop to avoid deop due to passing arguments object to
-  // function. This is faster in many JS engines as of late 2014.
-  var args = new Array(arguments.length - 2);
-  for (var i = 2; i < arguments.length; i++) {
-    args[i - 2] = arguments[i];
-  }
-  var foundCaller = false;
-  for (var ctor = me.constructor; ctor;
-       ctor = ctor.superClass_ && ctor.superClass_.constructor) {
-    if (ctor.prototype[opt_methodName] === caller) {
-      foundCaller = true;
-    } else if (foundCaller) {
-      return ctor.prototype[opt_methodName].apply(me, args);
-    }
-  }
-
-  // If we did not find the caller in the prototype chain, then one of two
-  // things happened:
-  // 1) The caller is an instance method.
-  // 2) This method was not called by the right caller.
-  if (me[opt_methodName] === caller) {
-    return me.constructor.prototype[opt_methodName].apply(me, args);
-  } else {
-    throw Error(
-        'goog.base called from a method of one name ' +
-        'to a method of a different name');
-  }
-};
-
-
-/**
- * Allow for aliasing within scope functions.  This function exists for
- * uncompiled code - in compiled code the calls will be inlined and the aliases
- * applied.  In uncompiled code the function is simply run since the aliases as
- * written are valid JavaScript.
- *
- *
- * @param {function()} fn Function to call.  This function can contain aliases
- *     to namespaces (e.g. "var dom = goog.dom") or classes
- *     (e.g. "var Timer = goog.Timer").
- */
-goog.scope = function(fn) {
-  if (goog.isInModuleLoader_()) {
-    throw Error('goog.scope is not supported within a goog.module.');
-  }
-  fn.call(goog.global);
-};
-
-
-/*
- * To support uncompiled, strict mode bundles that use eval to divide source
- * like so:
- *    eval('someSource;//# sourceUrl sourcefile.js');
- * We need to export the globally defined symbols "goog" and "COMPILED".
- * Exporting "goog" breaks the compiler optimizations, so we required that
- * be defined externally.
- * NOTE: We don't use goog.exportSymbol here because we don't want to trigger
- * extern generation when that compiler option is enabled.
- */
-if (!COMPILED) {
-  goog.global['COMPILED'] = COMPILED;
-}
-
-
-//==============================================================================
-// goog.defineClass implementation
-//==============================================================================
-
-
-/**
- * Creates a restricted form of a Closure "class":
- *   - from the compiler's perspective, the instance returned from the
- *     constructor is sealed (no new properties may be added).  This enables
- *     better checks.
- *   - the compiler will rewrite this definition to a form that is optimal
- *     for type checking and optimization (initially this will be a more
- *     traditional form).
- *
- * @param {Function} superClass The superclass, Object or null.
- * @param {goog.defineClass.ClassDescriptor} def
- *     An object literal describing
- *     the class.  It may have the following properties:
- *     "constructor": the constructor function
- *     "statics": an object literal containing methods to add to the constructor
- *        as "static" methods or a function that will receive the constructor
- *        function as its only parameter to which static properties can
- *        be added.
- *     all other properties are added to the prototype.
- * @return {!Function} The class constructor.
- */
-goog.defineClass = function(superClass, def) {
-  // TODO(johnlenz): consider making the superClass an optional parameter.
-  var constructor = def.constructor;
-  var statics = def.statics;
-  // Wrap the constructor prior to setting up the prototype and static methods.
-  if (!constructor || constructor == Object.prototype.constructor) {
-    constructor = function() {
-      throw Error('cannot instantiate an interface (no constructor defined).');
-    };
-  }
-
-  var cls = goog.defineClass.createSealingConstructor_(constructor, superClass);
-  if (superClass) {
-    goog.inherits(cls, superClass);
-  }
-
-  // Remove all the properties that should not be copied to the prototype.
-  delete def.constructor;
-  delete def.statics;
-
-  goog.defineClass.applyProperties_(cls.prototype, def);
-  if (statics != null) {
-    if (statics instanceof Function) {
-      statics(cls);
-    } else {
-      goog.defineClass.applyProperties_(cls, statics);
-    }
-  }
-
-  return cls;
-};
-
-
-/**
- * @typedef {{
- *   constructor: (!Function|undefined),
- *   statics: (Object|undefined|function(Function):void)
- * }}
- * @suppress {missingProvide}
- */
-goog.defineClass.ClassDescriptor;
-
-
-/**
- * @define {boolean} Whether the instances returned by goog.defineClass should
- *     be sealed when possible.
- *
- * When sealing is disabled the constructor function will not be wrapped by
- * goog.defineClass, making it incompatible with ES6 class methods.
- */
-goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);
-
-
-/**
- * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is
- * defined, this function will wrap the constructor in a function that seals the
- * results of the provided constructor function.
- *
- * @param {!Function} ctr The constructor whose results maybe be sealed.
- * @param {Function} superClass The superclass constructor.
- * @return {!Function} The replacement constructor.
- * @private
- */
-goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
-  if (!goog.defineClass.SEAL_CLASS_INSTANCES) {
-    // Do now wrap the constructor when sealing is disabled. Angular code
-    // depends on this for injection to work properly.
-    return ctr;
-  }
-
-  // Compute whether the constructor is sealable at definition time, rather
-  // than when the instance is being constructed.
-  var superclassSealable = !goog.defineClass.isUnsealable_(superClass);
-
-  /**
-   * @this {Object}
-   * @return {?}
-   */
-  var wrappedCtr = function() {
-    // Don't seal an instance of a subclass when it calls the constructor of
-    // its super class as there is most likely still setup to do.
-    var instance = ctr.apply(this, arguments) || this;
-    instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_];
-
-    if (this.constructor === wrappedCtr && superclassSealable &&
-        Object.seal instanceof Function) {
-      Object.seal(instance);
-    }
-    return instance;
-  };
-
-  return wrappedCtr;
-};
-
-
-/**
- * @param {Function} ctr The constructor to test.
- * @returns {boolean} Whether the constructor has been tagged as unsealable
- *     using goog.tagUnsealableClass.
- * @private
- */
-goog.defineClass.isUnsealable_ = function(ctr) {
-  return ctr && ctr.prototype &&
-      ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_];
-};
-
-
-// TODO(johnlenz): share these values with the goog.object
-/**
- * The names of the fields that are defined on Object.prototype.
- * @type {!Array<string>}
- * @private
- * @const
- */
-goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [
-  'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
-  'toLocaleString', 'toString', 'valueOf'
-];
-
-
-// TODO(johnlenz): share this function with the goog.object
-/**
- * @param {!Object} target The object to add properties to.
- * @param {!Object} source The object to copy properties from.
- * @private
- */
-goog.defineClass.applyProperties_ = function(target, source) {
-  // TODO(johnlenz): update this to support ES5 getters/setters
-
-  var key;
-  for (key in source) {
-    if (Object.prototype.hasOwnProperty.call(source, key)) {
-      target[key] = source[key];
-    }
-  }
-
-  // For IE the for-in-loop does not contain any properties that are not
-  // enumerable on the prototype object (for example isPrototypeOf from
-  // Object.prototype) and it will also not include 'replace' on objects that
-  // extend String and change 'replace' (not that it is common for anyone to
-  // extend anything except Object).
-  for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
-    key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i];
-    if (Object.prototype.hasOwnProperty.call(source, key)) {
-      target[key] = source[key];
-    }
-  }
-};
-
-
-/**
- * Sealing classes breaks the older idiom of assigning properties on the
- * prototype rather than in the constructor. As such, goog.defineClass
- * must not seal subclasses of these old-style classes until they are fixed.
- * Until then, this marks a class as "broken", instructing defineClass
- * not to seal subclasses.
- * @param {!Function} ctr The legacy constructor to tag as unsealable.
- */
-goog.tagUnsealableClass = function(ctr) {
-  if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) {
-    ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true;
-  }
-};
-
-
-/**
- * Name for unsealable tag property.
- * @const @private {string}
- */
-goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable';
-
-goog.provide('ol');
-
-
-/**
- * Constants defined with the define tag cannot be changed in application
- * code, but can be set at compile time.
- * Some reduce the size of the build in advanced compile mode.
- */
-
-
-/**
- * @define {boolean} Enable debug mode. Default is `true`.
- */
-ol.DEBUG = true;
-
-
-/**
- * @define {boolean} Assume touch.  Default is `false`.
- */
-ol.ASSUME_TOUCH = false;
-
-
-/**
- * TODO: rename this to something having to do with tile grids
- * see https://github.com/openlayers/ol3/issues/2076
- * @define {number} Default maximum zoom for default tile grids.
- */
-ol.DEFAULT_MAX_ZOOM = 42;
-
-
-/**
- * @define {number} Default min zoom level for the map view.  Default is `0`.
- */
-ol.DEFAULT_MIN_ZOOM = 0;
-
-
-/**
- * @define {number} Default maximum allowed threshold  (in pixels) for
- *     reprojection triangulation. Default is `0.5`.
- */
-ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD = 0.5;
-
-
-/**
- * @define {number} Default tile size.
- */
-ol.DEFAULT_TILE_SIZE = 256;
-
-
-/**
- * @define {string} Default WMS version.
- */
-ol.DEFAULT_WMS_VERSION = '1.3.0';
-
-
-/**
- * @define {number} Hysteresis pixels.
- */
-ol.DRAG_BOX_HYSTERESIS_PIXELS = 8;
-
-
-/**
- * @define {boolean} Enable the Canvas renderer.  Default is `true`. Setting
- *     this to false at compile time in advanced mode removes all code
- *     supporting the Canvas renderer from the build.
- */
-ol.ENABLE_CANVAS = true;
-
-
-/**
- * @define {boolean} Enable rendering of ol.layer.Image based layers.  Default
- *     is `true`. Setting this to false at compile time in advanced mode removes
- *     all code supporting Image layers from the build.
- */
-ol.ENABLE_IMAGE = true;
-
-
-/**
- * @define {boolean} Enable integration with the Proj4js library.  Default is
- *     `true`.
- */
-ol.ENABLE_PROJ4JS = true;
-
-
-/**
- * @define {boolean} Enable automatic reprojection of raster sources. Default is
- *     `true`.
- */
-ol.ENABLE_RASTER_REPROJECTION = true;
-
-
-/**
- * @define {boolean} Enable rendering of ol.layer.Tile based layers.  Default is
- *     `true`. Setting this to false at compile time in advanced mode removes
- *     all code supporting Tile layers from the build.
- */
-ol.ENABLE_TILE = true;
-
-
-/**
- * @define {boolean} Enable rendering of ol.layer.Vector based layers.  Default
- *     is `true`. Setting this to false at compile time in advanced mode removes
- *     all code supporting Vector layers from the build.
- */
-ol.ENABLE_VECTOR = true;
-
-
-/**
- * @define {boolean} Enable rendering of ol.layer.VectorTile based layers.
- *     Default is `true`. Setting this to false at compile time in advanced mode
- *     removes all code supporting VectorTile layers from the build.
- */
-ol.ENABLE_VECTOR_TILE = true;
-
-
-/**
- * @define {boolean} Enable the WebGL renderer.  Default is `true`. Setting
- *     this to false at compile time in advanced mode removes all code
- *     supporting the WebGL renderer from the build.
- */
-ol.ENABLE_WEBGL = true;
-
-
-/**
- * @define {number} The size in pixels of the first atlas image. Default is
- * `256`.
- */
-ol.INITIAL_ATLAS_SIZE = 256;
-
-
-/**
- * @define {number} The maximum size in pixels of atlas images. Default is
- * `-1`, meaning it is not used (and `ol.WEBGL_MAX_TEXTURE_SIZE` is
- * used instead).
- */
-ol.MAX_ATLAS_SIZE = -1;
-
-
-/**
- * @define {number} Maximum mouse wheel delta.
- */
-ol.MOUSEWHEELZOOM_MAXDELTA = 1;
-
-
-/**
- * @define {number} Mouse wheel timeout duration.
- */
-ol.MOUSEWHEELZOOM_TIMEOUT_DURATION = 80;
-
-
-/**
- * @define {number} Maximum width and/or height extent ratio that determines
- * when the overview map should be zoomed out.
- */
-ol.OVERVIEWMAP_MAX_RATIO = 0.75;
-
-
-/**
- * @define {number} Minimum width and/or height extent ratio that determines
- * when the overview map should be zoomed in.
- */
-ol.OVERVIEWMAP_MIN_RATIO = 0.1;
-
-
-/**
- * @define {number} Maximum number of source tiles for raster reprojection of
- *     a single tile.
- *     If too many source tiles are determined to be loaded to create a single
- *     reprojected tile the browser can become unresponsive or even crash.
- *     This can happen if the developer defines projections improperly and/or
- *     with unlimited extents.
- *     If too many tiles are required, no tiles are loaded and
- *     `ol.Tile.State.ERROR` state is set. Default is `100`.
- */
-ol.RASTER_REPROJECTION_MAX_SOURCE_TILES = 100;
-
-
-/**
- * @define {number} Maximum number of subdivision steps during raster
- *     reprojection triangulation. Prevents high memory usage and large
- *     number of proj4 calls (for certain transformations and areas).
- *     At most `2*(2^this)` triangles are created for each triangulated
- *     extent (tile/image). Default is `10`.
- */
-ol.RASTER_REPROJECTION_MAX_SUBDIVISION = 10;
-
-
-/**
- * @define {number} Maximum allowed size of triangle relative to world width.
- *     When transforming corners of world extent between certain projections,
- *     the resulting triangulation seems to have zero error and no subdivision
- *     is performed.
- *     If the triangle width is more than this (relative to world width; 0-1),
- *     subdivison is forced (up to `ol.RASTER_REPROJECTION_MAX_SUBDIVISION`).
- *     Default is `0.25`.
- */
-ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH = 0.25;
-
-
-/**
- * @define {number} Tolerance for geometry simplification in device pixels.
- */
-ol.SIMPLIFY_TOLERANCE = 0.5;
-
-
-/**
- * @define {number} Texture cache high water mark.
- */
-ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
-
-
-/**
- * @define {string} OpenLayers version.
- */
-ol.VERSION = '';
-
-
-/**
- * The maximum supported WebGL texture size in pixels. If WebGL is not
- * supported, the value is set to `undefined`.
- * @const
- * @type {number|undefined}
- */
-ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has`
-
-
-/**
- * List of supported WebGL extensions.
- * @const
- * @type {Array.<string>}
- */
-ol.WEBGL_EXTENSIONS; // value is set in `ol.has`
-
-
-/**
- * Inherit the prototype methods from one constructor into another.
- *
- * Usage:
- *
- *     function ParentClass(a, b) { }
- *     ParentClass.prototype.foo = function(a) { }
- *
- *     function ChildClass(a, b, c) {
- *       // Call parent constructor
- *       ParentClass.call(this, a, b);
- *     }
- *     ol.inherits(ChildClass, ParentClass);
- *
- *     var child = new ChildClass('a', 'b', 'see');
- *     child.foo(); // This works.
- *
- * @param {!Function} childCtor Child constructor.
- * @param {!Function} parentCtor Parent constructor.
- * @function
- * @api
- */
-ol.inherits = function(childCtor, parentCtor) {
-  childCtor.prototype = Object.create(parentCtor.prototype);
-  childCtor.prototype.constructor = childCtor;
-};
-
-
-/**
- * A reusable function, used e.g. as a default for callbacks.
- *
- * @return {undefined} Nothing.
- */
-ol.nullFunction = function() {};
-
-
-/**
- * Gets a unique ID for an object. This mutates the object so that further calls
- * with the same object as a parameter returns the same value. Unique IDs are generated
- * as a strictly increasing sequence. Adapted from goog.getUid.
- *
- * @param {Object} obj The object to get the unique ID for.
- * @return {number} The unique ID for the object.
- */
-ol.getUid = function(obj) {
-  return obj.ol_uid ||
-      (obj.ol_uid = ++ol.uidCounter_);
-};
-
-
-/**
- * Counter for getUid.
- * @type {number}
- * @private
- */
-ol.uidCounter_ = 0;
-
-goog.provide('ol.AssertionError');
-
-goog.require('ol');
-
-/**
- * Error object thrown when an assertion failed. This is an ECMA-262 Error,
- * extended with a `code` property.
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error}
- * @constructor
- * @extends {Error}
- * @implements {oli.AssertionError}
- * @param {number} code Error code.
- */
-ol.AssertionError = function(code) {
-
-  /**
-   * @type {string}
-   */
-  this.message = 'Assertion failed. See ' +
-      (ol.VERSION ? 'https://openlayers.org/en/' + ol.VERSION.split('-')[0] : '') +
-      '/doc/errors/#' + code + ' for details.';
-
-  /**
-   * Error code. The meaning of the code can be found on
-   * {@link https://openlayers.org/en/latest/errors.html} (replace `latest` with
-   * the version found in the OpenLayers script's header comment if a version
-   * other than the latest is used).
-   * @type {number}
-   * @api
-   */
-  this.code = code;
-
-  this.name = 'AssertionError';
-
-};
-ol.inherits(ol.AssertionError, Error);
-
-goog.provide('ol.asserts');
-
-goog.require('ol.AssertionError');
-
-
-/**
- * @param {*} assertion Assertion we expected to be truthy.
- * @param {number} errorCode Error code.
- */
-ol.asserts.assert = function(assertion, errorCode) {
-  if (!assertion) {
-    throw new ol.AssertionError(errorCode);
-  }
-};
-
-goog.provide('ol.math');
-
-goog.require('ol');
-goog.require('ol.asserts');
-
-
-/**
- * Takes a number and clamps it to within the provided bounds.
- * @param {number} value The input number.
- * @param {number} min The minimum value to return.
- * @param {number} max The maximum value to return.
- * @return {number} The input number if it is within bounds, or the nearest
- *     number within the bounds.
- */
-ol.math.clamp = function(value, min, max) {
-  return Math.min(Math.max(value, min), max);
-};
-
-
-/**
- * Return the hyperbolic cosine of a given number. The method will use the
- * native `Math.cosh` function if it is available, otherwise the hyperbolic
- * cosine will be calculated via the reference implementation of the Mozilla
- * developer network.
- *
- * @param {number} x X.
- * @return {number} Hyperbolic cosine of x.
- */
-ol.math.cosh = (function() {
-  // Wrapped in a iife, to save the overhead of checking for the native
-  // implementation on every invocation.
-  var cosh;
-  if ('cosh' in Math) {
-    // The environment supports the native Math.cosh function, use it…
-    cosh = Math.cosh;
-  } else {
-    // … else, use the reference implementation of MDN:
-    cosh = function(x) {
-      var y = Math.exp(x);
-      return (y + 1 / y) / 2;
-    };
-  }
-  return cosh;
-}());
-
-
-/**
- * @param {number} x X.
- * @return {number} The smallest power of two greater than or equal to x.
- */
-ol.math.roundUpToPowerOfTwo = function(x) {
-  ol.asserts.assert(0 < x, 29); // `x` must be greater than `0`
-  return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
-};
-
-
-/**
- * Returns the square of the closest distance between the point (x, y) and the
- * line segment (x1, y1) to (x2, y2).
- * @param {number} x X.
- * @param {number} y Y.
- * @param {number} x1 X1.
- * @param {number} y1 Y1.
- * @param {number} x2 X2.
- * @param {number} y2 Y2.
- * @return {number} Squared distance.
- */
-ol.math.squaredSegmentDistance = function(x, y, x1, y1, x2, y2) {
-  var dx = x2 - x1;
-  var dy = y2 - y1;
-  if (dx !== 0 || dy !== 0) {
-    var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
-    if (t > 1) {
-      x1 = x2;
-      y1 = y2;
-    } else if (t > 0) {
-      x1 += dx * t;
-      y1 += dy * t;
-    }
-  }
-  return ol.math.squaredDistance(x, y, x1, y1);
-};
-
-
-/**
- * Returns the square of the distance between the points (x1, y1) and (x2, y2).
- * @param {number} x1 X1.
- * @param {number} y1 Y1.
- * @param {number} x2 X2.
- * @param {number} y2 Y2.
- * @return {number} Squared distance.
- */
-ol.math.squaredDistance = function(x1, y1, x2, y2) {
-  var dx = x2 - x1;
-  var dy = y2 - y1;
-  return dx * dx + dy * dy;
-};
-
-
-/**
- * Solves system of linear equations using Gaussian elimination method.
- *
- * @param {Array.<Array.<number>>} mat Augmented matrix (n x n + 1 column)
- *                                     in row-major order.
- * @return {Array.<number>} The resulting vector.
- */
-ol.math.solveLinearSystem = function(mat) {
-  var n = mat.length;
-
-  if (ol.DEBUG) {
-    for (var row = 0; row < n; row++) {
-      console.assert(mat[row].length == n + 1,
-                          'every row should have correct number of columns');
-    }
-  }
-
-  for (var i = 0; i < n; i++) {
-    // Find max in the i-th column (ignoring i - 1 first rows)
-    var maxRow = i;
-    var maxEl = Math.abs(mat[i][i]);
-    for (var r = i + 1; r < n; r++) {
-      var absValue = Math.abs(mat[r][i]);
-      if (absValue > maxEl) {
-        maxEl = absValue;
-        maxRow = r;
-      }
-    }
-
-    if (maxEl === 0) {
-      return null; // matrix is singular
-    }
-
-    // Swap max row with i-th (current) row
-    var tmp = mat[maxRow];
-    mat[maxRow] = mat[i];
-    mat[i] = tmp;
-
-    // Subtract the i-th row to make all the remaining rows 0 in the i-th column
-    for (var j = i + 1; j < n; j++) {
-      var coef = -mat[j][i] / mat[i][i];
-      for (var k = i; k < n + 1; k++) {
-        if (i == k) {
-          mat[j][k] = 0;
-        } else {
-          mat[j][k] += coef * mat[i][k];
-        }
-      }
-    }
-  }
-
-  // Solve Ax=b for upper triangular matrix A (mat)
-  var x = new Array(n);
-  for (var l = n - 1; l >= 0; l--) {
-    x[l] = mat[l][n] / mat[l][l];
-    for (var m = l - 1; m >= 0; m--) {
-      mat[m][n] -= mat[m][l] * x[l];
-    }
-  }
-  return x;
-};
-
-
-/**
- * Converts radians to to degrees.
- *
- * @param {number} angleInRadians Angle in radians.
- * @return {number} Angle in degrees.
- */
-ol.math.toDegrees = function(angleInRadians) {
-  return angleInRadians * 180 / Math.PI;
-};
-
-
-/**
- * Converts degrees to radians.
- *
- * @param {number} angleInDegrees Angle in degrees.
- * @return {number} Angle in radians.
- */
-ol.math.toRadians = function(angleInDegrees) {
-  return angleInDegrees * Math.PI / 180;
-};
-
-/**
- * Returns the modulo of a / b, depending on the sign of b.
- *
- * @param {number} a Dividend.
- * @param {number} b Divisor.
- * @return {number} Modulo.
- */
-ol.math.modulo = function(a, b) {
-  var r = a % b;
-  return r * b < 0 ? r + b : r;
-};
-
-/**
- * Calculates the linearly interpolated value of x between a and b.
- *
- * @param {number} a Number
- * @param {number} b Number
- * @param {number} x Value to be interpolated.
- * @return {number} Interpolated value.
- */
-ol.math.lerp = function(a, b, x) {
-  return a + x * (b - a);
-};
-
-goog.provide('ol.CenterConstraint');
-
-goog.require('ol.math');
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @return {ol.CenterConstraintType} The constraint.
- */
-ol.CenterConstraint.createExtent = function(extent) {
-  return (
-      /**
-       * @param {ol.Coordinate|undefined} center Center.
-       * @return {ol.Coordinate|undefined} Center.
-       */
-      function(center) {
-        if (center) {
-          return [
-            ol.math.clamp(center[0], extent[0], extent[2]),
-            ol.math.clamp(center[1], extent[1], extent[3])
-          ];
-        } else {
-          return undefined;
-        }
-      });
-};
-
-
-/**
- * @param {ol.Coordinate|undefined} center Center.
- * @return {ol.Coordinate|undefined} Center.
- */
-ol.CenterConstraint.none = function(center) {
-  return center;
-};
-
-goog.provide('ol.Constraints');
-
-
-/**
- * @constructor
- * @param {ol.CenterConstraintType} centerConstraint Center constraint.
- * @param {ol.ResolutionConstraintType} resolutionConstraint
- *     Resolution constraint.
- * @param {ol.RotationConstraintType} rotationConstraint
- *     Rotation constraint.
- */
-ol.Constraints = function(centerConstraint, resolutionConstraint, rotationConstraint) {
-
-  /**
-   * @type {ol.CenterConstraintType}
-   */
-  this.center = centerConstraint;
-
-  /**
-   * @type {ol.ResolutionConstraintType}
-   */
-  this.resolution = resolutionConstraint;
-
-  /**
-   * @type {ol.RotationConstraintType}
-   */
-  this.rotation = rotationConstraint;
-
-};
-
-goog.provide('ol.obj');
-
-
-/**
- * Polyfill for Object.assign().  Assigns enumerable and own properties from
- * one or more source objects to a target object.
- *
- * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
- * @param {!Object} target The target object.
- * @param {...Object} var_sources The source object(s).
- * @return {!Object} The modified target object.
- */
-ol.obj.assign = (typeof Object.assign === 'function') ? Object.assign : function(target, var_sources) {
-  if (target === undefined || target === null) {
-    throw new TypeError('Cannot convert undefined or null to object');
-  }
-
-  var output = Object(target);
-  for (var i = 1, ii = arguments.length; i < ii; ++i) {
-    var source = arguments[i];
-    if (source !== undefined && source !== null) {
-      for (var key in source) {
-        if (source.hasOwnProperty(key)) {
-          output[key] = source[key];
-        }
-      }
-    }
-  }
-  return output;
-};
-
-
-/**
- * Removes all properties from an object.
- * @param {Object} object The object to clear.
- */
-ol.obj.clear = function(object) {
-  for (var property in object) {
-    delete object[property];
-  }
-};
-
-
-/**
- * Get an array of property values from an object.
- * @param {Object<K,V>} object The object from which to get the values.
- * @return {!Array<V>} The property values.
- * @template K,V
- */
-ol.obj.getValues = function(object) {
-  var values = [];
-  for (var property in object) {
-    values.push(object[property]);
-  }
-  return values;
-};
-
-
-/**
- * Determine if an object has any properties.
- * @param {Object} object The object to check.
- * @return {boolean} The object is empty.
- */
-ol.obj.isEmpty = function(object) {
-  var property;
-  for (property in object) {
-    return false;
-  }
-  return !property;
-};
-
-goog.provide('ol.events');
-
-goog.require('ol.obj');
-
-
-/**
- * @param {ol.EventsKey} listenerObj Listener object.
- * @return {ol.EventsListenerFunctionType} Bound listener.
- */
-ol.events.bindListener_ = function(listenerObj) {
-  var boundListener = function(evt) {
-    var listener = listenerObj.listener;
-    var bindTo = listenerObj.bindTo || listenerObj.target;
-    if (listenerObj.callOnce) {
-      ol.events.unlistenByKey(listenerObj);
-    }
-    return listener.call(bindTo, evt);
-  };
-  listenerObj.boundListener = boundListener;
-  return boundListener;
-};
-
-
-/**
- * Finds the matching {@link ol.EventsKey} in the given listener
- * array.
- *
- * @param {!Array<!ol.EventsKey>} listeners Array of listeners.
- * @param {!Function} listener The listener function.
- * @param {Object=} opt_this The `this` value inside the listener.
- * @param {boolean=} opt_setDeleteIndex Set the deleteIndex on the matching
- *     listener, for {@link ol.events.unlistenByKey}.
- * @return {ol.EventsKey|undefined} The matching listener object.
- * @private
- */
-ol.events.findListener_ = function(listeners, listener, opt_this,
-    opt_setDeleteIndex) {
-  var listenerObj;
-  for (var i = 0, ii = listeners.length; i < ii; ++i) {
-    listenerObj = listeners[i];
-    if (listenerObj.listener === listener &&
-        listenerObj.bindTo === opt_this) {
-      if (opt_setDeleteIndex) {
-        listenerObj.deleteIndex = i;
-      }
-      return listenerObj;
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @param {ol.EventTargetLike} target Target.
- * @param {string} type Type.
- * @return {Array.<ol.EventsKey>|undefined} Listeners.
- */
-ol.events.getListeners = function(target, type) {
-  var listenerMap = target.ol_lm;
-  return listenerMap ? listenerMap[type] : undefined;
-};
-
-
-/**
- * Get the lookup of listeners.  If one does not exist on the target, it is
- * created.
- * @param {ol.EventTargetLike} target Target.
- * @return {!Object.<string, Array.<ol.EventsKey>>} Map of
- *     listeners by event type.
- * @private
- */
-ol.events.getListenerMap_ = function(target) {
-  var listenerMap = target.ol_lm;
-  if (!listenerMap) {
-    listenerMap = target.ol_lm = {};
-  }
-  return listenerMap;
-};
-
-
-/**
- * Clean up all listener objects of the given type.  All properties on the
- * listener objects will be removed, and if no listeners remain in the listener
- * map, it will be removed from the target.
- * @param {ol.EventTargetLike} target Target.
- * @param {string} type Type.
- * @private
- */
-ol.events.removeListeners_ = function(target, type) {
-  var listeners = ol.events.getListeners(target, type);
-  if (listeners) {
-    for (var i = 0, ii = listeners.length; i < ii; ++i) {
-      target.removeEventListener(type, listeners[i].boundListener);
-      ol.obj.clear(listeners[i]);
-    }
-    listeners.length = 0;
-    var listenerMap = target.ol_lm;
-    if (listenerMap) {
-      delete listenerMap[type];
-      if (Object.keys(listenerMap).length === 0) {
-        delete target.ol_lm;
-      }
-    }
-  }
-};
-
-
-/**
- * Registers an event listener on an event target. Inspired by
- * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
- *
- * This function efficiently binds a `listener` to a `this` object, and returns
- * a key for use with {@link ol.events.unlistenByKey}.
- *
- * @param {ol.EventTargetLike} target Event target.
- * @param {string} type Event type.
- * @param {ol.EventsListenerFunctionType} listener Listener.
- * @param {Object=} opt_this Object referenced by the `this` keyword in the
- *     listener. Default is the `target`.
- * @param {boolean=} opt_once If true, add the listener as one-off listener.
- * @return {ol.EventsKey} Unique key for the listener.
- */
-ol.events.listen = function(target, type, listener, opt_this, opt_once) {
-  var listenerMap = ol.events.getListenerMap_(target);
-  var listeners = listenerMap[type];
-  if (!listeners) {
-    listeners = listenerMap[type] = [];
-  }
-  var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
-      false);
-  if (listenerObj) {
-    if (!opt_once) {
-      // Turn one-off listener into a permanent one.
-      listenerObj.callOnce = false;
-    }
-  } else {
-    listenerObj = /** @type {ol.EventsKey} */ ({
-      bindTo: opt_this,
-      callOnce: !!opt_once,
-      listener: listener,
-      target: target,
-      type: type
-    });
-    target.addEventListener(type, ol.events.bindListener_(listenerObj));
-    listeners.push(listenerObj);
-  }
-
-  return listenerObj;
-};
-
-
-/**
- * Registers a one-off event listener on an event target. Inspired by
- * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
- *
- * This function efficiently binds a `listener` as self-unregistering listener
- * to a `this` object, and returns a key for use with
- * {@link ol.events.unlistenByKey} in case the listener needs to be unregistered
- * before it is called.
- *
- * When {@link ol.events.listen} is called with the same arguments after this
- * function, the self-unregistering listener will be turned into a permanent
- * listener.
- *
- * @param {ol.EventTargetLike} target Event target.
- * @param {string} type Event type.
- * @param {ol.EventsListenerFunctionType} listener Listener.
- * @param {Object=} opt_this Object referenced by the `this` keyword in the
- *     listener. Default is the `target`.
- * @return {ol.EventsKey} Key for unlistenByKey.
- */
-ol.events.listenOnce = function(target, type, listener, opt_this) {
-  return ol.events.listen(target, type, listener, opt_this, true);
-};
-
-
-/**
- * Unregisters an event listener on an event target. Inspired by
- * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
- *
- * To return a listener, this function needs to be called with the exact same
- * arguments that were used for a previous {@link ol.events.listen} call.
- *
- * @param {ol.EventTargetLike} target Event target.
- * @param {string} type Event type.
- * @param {ol.EventsListenerFunctionType} listener Listener.
- * @param {Object=} opt_this Object referenced by the `this` keyword in the
- *     listener. Default is the `target`.
- */
-ol.events.unlisten = function(target, type, listener, opt_this) {
-  var listeners = ol.events.getListeners(target, type);
-  if (listeners) {
-    var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
-        true);
-    if (listenerObj) {
-      ol.events.unlistenByKey(listenerObj);
-    }
-  }
-};
-
-
-/**
- * Unregisters event listeners on an event target. Inspired by
- * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
- *
- * The argument passed to this function is the key returned from
- * {@link ol.events.listen} or {@link ol.events.listenOnce}.
- *
- * @param {ol.EventsKey} key The key.
- */
-ol.events.unlistenByKey = function(key) {
-  if (key && key.target) {
-    key.target.removeEventListener(key.type, key.boundListener);
-    var listeners = ol.events.getListeners(key.target, key.type);
-    if (listeners) {
-      var i = 'deleteIndex' in key ? key.deleteIndex : listeners.indexOf(key);
-      if (i !== -1) {
-        listeners.splice(i, 1);
-      }
-      if (listeners.length === 0) {
-        ol.events.removeListeners_(key.target, key.type);
-      }
-    }
-    ol.obj.clear(key);
-  }
-};
-
-
-/**
- * Unregisters all event listeners on an event target. Inspired by
- * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
- *
- * @param {ol.EventTargetLike} target Target.
- */
-ol.events.unlistenAll = function(target) {
-  var listenerMap = ol.events.getListenerMap_(target);
-  for (var type in listenerMap) {
-    ol.events.removeListeners_(target, type);
-  }
-};
-
-goog.provide('ol.Disposable');
-
-goog.require('ol');
-
-/**
- * Objects that need to clean up after themselves.
- * @constructor
- */
-ol.Disposable = function() {};
-
-/**
- * The object has already been disposed.
- * @type {boolean}
- * @private
- */
-ol.Disposable.prototype.disposed_ = false;
-
-/**
- * Clean up.
- */
-ol.Disposable.prototype.dispose = function() {
-  if (!this.disposed_) {
-    this.disposed_ = true;
-    this.disposeInternal();
-  }
-};
-
-/**
- * Extension point for disposable objects.
- * @protected
- */
-ol.Disposable.prototype.disposeInternal = ol.nullFunction;
-
-goog.provide('ol.events.Event');
-
-
-/**
- * @classdesc
- * Stripped down implementation of the W3C DOM Level 2 Event interface.
- * @see {@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface}
- *
- * This implementation only provides `type` and `target` properties, and
- * `stopPropagation` and `preventDefault` methods. It is meant as base class
- * for higher level events defined in the library, and works with
- * {@link ol.events.EventTarget}.
- *
- * @constructor
- * @implements {oli.events.Event}
- * @param {string} type Type.
- */
-ol.events.Event = function(type) {
-
-  /**
-   * @type {boolean}
-   */
-  this.propagationStopped;
-
-  /**
-   * The event type.
-   * @type {string}
-   * @api stable
-   */
-  this.type = type;
-
-  /**
-   * The event target.
-   * @type {Object}
-   * @api stable
-   */
-  this.target = null;
-
-};
-
-
-/**
- * Stop event propagation.
- * @function
- * @api stable
- */
-ol.events.Event.prototype.preventDefault =
-
-/**
- * Stop event propagation.
- * @function
- * @api stable
- */
-ol.events.Event.prototype.stopPropagation = function() {
-  this.propagationStopped = true;
-};
-
-
-/**
- * @param {Event|ol.events.Event} evt Event
- */
-ol.events.Event.stopPropagation = function(evt) {
-  evt.stopPropagation();
-};
-
-
-/**
- * @param {Event|ol.events.Event} evt Event
- */
-ol.events.Event.preventDefault = function(evt) {
-  evt.preventDefault();
-};
-
-goog.provide('ol.events.EventTarget');
-
-goog.require('ol');
-goog.require('ol.Disposable');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-
-
-/**
- * @classdesc
- * A simplified implementation of the W3C DOM Level 2 EventTarget interface.
- * @see {@link https://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget}
- *
- * There are two important simplifications compared to the specification:
- *
- * 1. The handling of `useCapture` in `addEventListener` and
- *    `removeEventListener`. There is no real capture model.
- * 2. The handling of `stopPropagation` and `preventDefault` on `dispatchEvent`.
- *    There is no event target hierarchy. When a listener calls
- *    `stopPropagation` or `preventDefault` on an event object, it means that no
- *    more listeners after this one will be called. Same as when the listener
- *    returns false.
- *
- * @constructor
- * @extends {ol.Disposable}
- */
-ol.events.EventTarget = function() {
-
-  ol.Disposable.call(this);
-
-  /**
-   * @private
-   * @type {!Object.<string, number>}
-   */
-  this.pendingRemovals_ = {};
-
-  /**
-   * @private
-   * @type {!Object.<string, number>}
-   */
-  this.dispatching_ = {};
-
-  /**
-   * @private
-   * @type {!Object.<string, Array.<ol.EventsListenerFunctionType>>}
-   */
-  this.listeners_ = {};
-
-};
-ol.inherits(ol.events.EventTarget, ol.Disposable);
-
-
-/**
- * @param {string} type Type.
- * @param {ol.EventsListenerFunctionType} listener Listener.
- */
-ol.events.EventTarget.prototype.addEventListener = function(type, listener) {
-  var listeners = this.listeners_[type];
-  if (!listeners) {
-    listeners = this.listeners_[type] = [];
-  }
-  if (listeners.indexOf(listener) === -1) {
-    listeners.push(listener);
-  }
-};
-
-
-/**
- * @param {{type: string,
- *     target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event|
- *     string} event Event or event type.
- * @return {boolean|undefined} `false` if anyone called preventDefault on the
- *     event object or if any of the listeners returned false.
- */
-ol.events.EventTarget.prototype.dispatchEvent = function(event) {
-  var evt = typeof event === 'string' ? new ol.events.Event(event) : event;
-  var type = evt.type;
-  evt.target = this;
-  var listeners = this.listeners_[type];
-  var propagate;
-  if (listeners) {
-    if (!(type in this.dispatching_)) {
-      this.dispatching_[type] = 0;
-      this.pendingRemovals_[type] = 0;
-    }
-    ++this.dispatching_[type];
-    for (var i = 0, ii = listeners.length; i < ii; ++i) {
-      if (listeners[i].call(this, evt) === false || evt.propagationStopped) {
-        propagate = false;
-        break;
-      }
-    }
-    --this.dispatching_[type];
-    if (this.dispatching_[type] === 0) {
-      var pendingRemovals = this.pendingRemovals_[type];
-      delete this.pendingRemovals_[type];
-      while (pendingRemovals--) {
-        this.removeEventListener(type, ol.nullFunction);
-      }
-      delete this.dispatching_[type];
-    }
-    return propagate;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.events.EventTarget.prototype.disposeInternal = function() {
-  ol.events.unlistenAll(this);
-};
-
-
-/**
- * Get the listeners for a specified event type. Listeners are returned in the
- * order that they will be called in.
- *
- * @param {string} type Type.
- * @return {Array.<ol.EventsListenerFunctionType>} Listeners.
- */
-ol.events.EventTarget.prototype.getListeners = function(type) {
-  return this.listeners_[type];
-};
-
-
-/**
- * @param {string=} opt_type Type. If not provided,
- *     `true` will be returned if this EventTarget has any listeners.
- * @return {boolean} Has listeners.
- */
-ol.events.EventTarget.prototype.hasListener = function(opt_type) {
-  return opt_type ?
-      opt_type in this.listeners_ :
-      Object.keys(this.listeners_).length > 0;
-};
-
-
-/**
- * @param {string} type Type.
- * @param {ol.EventsListenerFunctionType} listener Listener.
- */
-ol.events.EventTarget.prototype.removeEventListener = function(type, listener) {
-  var listeners = this.listeners_[type];
-  if (listeners) {
-    var index = listeners.indexOf(listener);
-    ol.DEBUG && console.assert(index != -1, 'listener not found');
-    if (type in this.pendingRemovals_) {
-      // make listener a no-op, and remove later in #dispatchEvent()
-      listeners[index] = ol.nullFunction;
-      ++this.pendingRemovals_[type];
-    } else {
-      listeners.splice(index, 1);
-      if (listeners.length === 0) {
-        delete this.listeners_[type];
-      }
-    }
-  }
-};
-
-goog.provide('ol.events.EventType');
-
-/**
- * @enum {string}
- * @const
- */
-ol.events.EventType = {
-  /**
-   * Generic change event. Triggered when the revision counter is increased.
-   * @event ol.events.Event#change
-   * @api
-   */
-  CHANGE: 'change',
-
-  CLICK: 'click',
-  DBLCLICK: 'dblclick',
-  DRAGENTER: 'dragenter',
-  DRAGOVER: 'dragover',
-  DROP: 'drop',
-  ERROR: 'error',
-  KEYDOWN: 'keydown',
-  KEYPRESS: 'keypress',
-  LOAD: 'load',
-  MOUSEDOWN: 'mousedown',
-  MOUSEMOVE: 'mousemove',
-  MOUSEOUT: 'mouseout',
-  MOUSEUP: 'mouseup',
-  MOUSEWHEEL: 'mousewheel',
-  MSPOINTERDOWN: 'mspointerdown',
-  RESIZE: 'resize',
-  TOUCHSTART: 'touchstart',
-  TOUCHMOVE: 'touchmove',
-  TOUCHEND: 'touchend',
-  WHEEL: 'wheel'
-};
-
-goog.provide('ol.Observable');
-
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.events.EventTarget');
-goog.require('ol.events.EventType');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * An event target providing convenient methods for listener registration
- * and unregistration. A generic `change` event is always available through
- * {@link ol.Observable#changed}.
- *
- * @constructor
- * @extends {ol.events.EventTarget}
- * @fires ol.events.Event
- * @struct
- * @api stable
- */
-ol.Observable = function() {
-
-  ol.events.EventTarget.call(this);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.revision_ = 0;
-
-};
-ol.inherits(ol.Observable, ol.events.EventTarget);
-
-
-/**
- * Removes an event listener using the key returned by `on()` or `once()`.
- * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()`
- *     or `once()` (or an array of keys).
- * @api stable
- */
-ol.Observable.unByKey = function(key) {
-  if (Array.isArray(key)) {
-    for (var i = 0, ii = key.length; i < ii; ++i) {
-      ol.events.unlistenByKey(key[i]);
-    }
-  } else {
-    ol.events.unlistenByKey(/** @type {ol.EventsKey} */ (key));
-  }
-};
-
-
-/**
- * Increases the revision counter and dispatches a 'change' event.
- * @api
- */
-ol.Observable.prototype.changed = function() {
-  ++this.revision_;
-  this.dispatchEvent(ol.events.EventType.CHANGE);
-};
-
-
-/**
- * Dispatches an event and calls all listeners listening for events
- * of this type. The event parameter can either be a string or an
- * Object with a `type` property.
- *
- * @param {{type: string,
- *     target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event|
- *     string} event Event object.
- * @function
- * @api
- */
-ol.Observable.prototype.dispatchEvent;
-
-
-/**
- * Get the version number for this object.  Each time the object is modified,
- * its version number will be incremented.
- * @return {number} Revision.
- * @api
- */
-ol.Observable.prototype.getRevision = function() {
-  return this.revision_;
-};
-
-
-/**
- * Listen for a certain type of event.
- * @param {string|Array.<string>} type The event type or array of event types.
- * @param {function(?): ?} listener The listener function.
- * @param {Object=} opt_this The object to use as `this` in `listener`.
- * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
- *     called with an array of event types as the first argument, the return
- *     will be an array of keys.
- * @api stable
- */
-ol.Observable.prototype.on = function(type, listener, opt_this) {
-  if (Array.isArray(type)) {
-    var len = type.length;
-    var keys = new Array(len);
-    for (var i = 0; i < len; ++i) {
-      keys[i] = ol.events.listen(this, type[i], listener, opt_this);
-    }
-    return keys;
-  } else {
-    return ol.events.listen(
-        this, /** @type {string} */ (type), listener, opt_this);
-  }
-};
-
-
-/**
- * Listen once for a certain type of event.
- * @param {string|Array.<string>} type The event type or array of event types.
- * @param {function(?): ?} listener The listener function.
- * @param {Object=} opt_this The object to use as `this` in `listener`.
- * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
- *     called with an array of event types as the first argument, the return
- *     will be an array of keys.
- * @api stable
- */
-ol.Observable.prototype.once = function(type, listener, opt_this) {
-  if (Array.isArray(type)) {
-    var len = type.length;
-    var keys = new Array(len);
-    for (var i = 0; i < len; ++i) {
-      keys[i] = ol.events.listenOnce(this, type[i], listener, opt_this);
-    }
-    return keys;
-  } else {
-    return ol.events.listenOnce(
-        this, /** @type {string} */ (type), listener, opt_this);
-  }
-};
-
-
-/**
- * Unlisten for a certain type of event.
- * @param {string|Array.<string>} type The event type or array of event types.
- * @param {function(?): ?} listener The listener function.
- * @param {Object=} opt_this The object which was used as `this` by the
- * `listener`.
- * @api stable
- */
-ol.Observable.prototype.un = function(type, listener, opt_this) {
-  if (Array.isArray(type)) {
-    for (var i = 0, ii = type.length; i < ii; ++i) {
-      ol.events.unlisten(this, type[i], listener, opt_this);
-    }
-    return;
-  } else {
-    ol.events.unlisten(this, /** @type {string} */ (type), listener, opt_this);
-  }
-};
-
-
-/**
- * Removes an event listener using the key returned by `on()` or `once()`.
- * Note that using the {@link ol.Observable.unByKey} static function is to
- * be preferred.
- * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()`
- *     or `once()` (or an array of keys).
- * @function
- * @api stable
- */
-ol.Observable.prototype.unByKey = ol.Observable.unByKey;
-
-goog.provide('ol.Object');
-goog.provide('ol.ObjectEvent');
-goog.provide('ol.ObjectEventType');
-
-goog.require('ol');
-goog.require('ol.Observable');
-goog.require('ol.events.Event');
-goog.require('ol.obj');
-
-
-/**
- * @enum {string}
- */
-ol.ObjectEventType = {
-  /**
-   * Triggered when a property is changed.
-   * @event ol.ObjectEvent#propertychange
-   * @api stable
-   */
-  PROPERTYCHANGE: 'propertychange'
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.Object} instances are instances of this type.
- *
- * @param {string} type The event type.
- * @param {string} key The property name.
- * @param {*} oldValue The old value for `key`.
- * @extends {ol.events.Event}
- * @implements {oli.ObjectEvent}
- * @constructor
- */
-ol.ObjectEvent = function(type, key, oldValue) {
-  ol.events.Event.call(this, type);
-
-  /**
-   * The name of the property whose value is changing.
-   * @type {string}
-   * @api stable
-   */
-  this.key = key;
-
-  /**
-   * The old value. To get the new value use `e.target.get(e.key)` where
-   * `e` is the event object.
-   * @type {*}
-   * @api stable
-   */
-  this.oldValue = oldValue;
-
-};
-ol.inherits(ol.ObjectEvent, ol.events.Event);
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Most non-trivial classes inherit from this.
- *
- * This extends {@link ol.Observable} with observable properties, where each
- * property is observable as well as the object as a whole.
- *
- * Classes that inherit from this have pre-defined properties, to which you can
- * add your owns. The pre-defined properties are listed in this documentation as
- * 'Observable Properties', and have their own accessors; for example,
- * {@link ol.Map} has a `target` property, accessed with `getTarget()`  and
- * changed with `setTarget()`. Not all properties are however settable. There
- * are also general-purpose accessors `get()` and `set()`. For example,
- * `get('target')` is equivalent to `getTarget()`.
- *
- * The `set` accessors trigger a change event, and you can monitor this by
- * registering a listener. For example, {@link ol.View} has a `center`
- * property, so `view.on('change:center', function(evt) {...});` would call the
- * function whenever the value of the center property changes. Within the
- * function, `evt.target` would be the view, so `evt.target.getCenter()` would
- * return the new center.
- *
- * You can add your own observable properties with
- * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`.
- * You can listen for changes on that property value with
- * `object.on('change:prop', listener)`. You can get a list of all
- * properties with {@link ol.Object#getProperties object.getProperties()}.
- *
- * Note that the observable properties are separate from standard JS properties.
- * You can, for example, give your map object a title with
- * `map.title='New title'` and with `map.set('title', 'Another title')`. The
- * first will be a `hasOwnProperty`; the second will appear in
- * `getProperties()`. Only the second is observable.
- *
- * Properties can be deleted by using the unset method. E.g.
- * object.unset('foo').
- *
- * @constructor
- * @extends {ol.Observable}
- * @param {Object.<string, *>=} opt_values An object with key-value pairs.
- * @fires ol.ObjectEvent
- * @api
- */
-ol.Object = function(opt_values) {
-  ol.Observable.call(this);
-
-  // Call ol.getUid to ensure that the order of objects' ids is the same as
-  // the order in which they were created.  This also helps to ensure that
-  // object properties are always added in the same order, which helps many
-  // JavaScript engines generate faster code.
-  ol.getUid(this);
-
-  /**
-   * @private
-   * @type {!Object.<string, *>}
-   */
-  this.values_ = {};
-
-  if (opt_values !== undefined) {
-    this.setProperties(opt_values);
-  }
-};
-ol.inherits(ol.Object, ol.Observable);
-
-
-/**
- * @private
- * @type {Object.<string, string>}
- */
-ol.Object.changeEventTypeCache_ = {};
-
-
-/**
- * @param {string} key Key name.
- * @return {string} Change name.
- */
-ol.Object.getChangeEventType = function(key) {
-  return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
-      ol.Object.changeEventTypeCache_[key] :
-      (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
-};
-
-
-/**
- * Gets a value.
- * @param {string} key Key name.
- * @return {*} Value.
- * @api stable
- */
-ol.Object.prototype.get = function(key) {
-  var value;
-  if (this.values_.hasOwnProperty(key)) {
-    value = this.values_[key];
-  }
-  return value;
-};
-
-
-/**
- * Get a list of object property names.
- * @return {Array.<string>} List of property names.
- * @api stable
- */
-ol.Object.prototype.getKeys = function() {
-  return Object.keys(this.values_);
-};
-
-
-/**
- * Get an object of all property names and values.
- * @return {Object.<string, *>} Object.
- * @api stable
- */
-ol.Object.prototype.getProperties = function() {
-  return ol.obj.assign({}, this.values_);
-};
-
-
-/**
- * @param {string} key Key name.
- * @param {*} oldValue Old value.
- */
-ol.Object.prototype.notify = function(key, oldValue) {
-  var eventType;
-  eventType = ol.Object.getChangeEventType(key);
-  this.dispatchEvent(new ol.ObjectEvent(eventType, key, oldValue));
-  eventType = ol.ObjectEventType.PROPERTYCHANGE;
-  this.dispatchEvent(new ol.ObjectEvent(eventType, key, oldValue));
-};
-
-
-/**
- * Sets a value.
- * @param {string} key Key name.
- * @param {*} value Value.
- * @param {boolean=} opt_silent Update without triggering an event.
- * @api stable
- */
-ol.Object.prototype.set = function(key, value, opt_silent) {
-  if (opt_silent) {
-    this.values_[key] = value;
-  } else {
-    var oldValue = this.values_[key];
-    this.values_[key] = value;
-    if (oldValue !== value) {
-      this.notify(key, oldValue);
-    }
-  }
-};
-
-
-/**
- * Sets a collection of key-value pairs.  Note that this changes any existing
- * properties and adds new ones (it does not remove any existing properties).
- * @param {Object.<string, *>} values Values.
- * @param {boolean=} opt_silent Update without triggering an event.
- * @api stable
- */
-ol.Object.prototype.setProperties = function(values, opt_silent) {
-  var key;
-  for (key in values) {
-    this.set(key, values[key], opt_silent);
-  }
-};
-
-
-/**
- * Unsets a property.
- * @param {string} key Key name.
- * @param {boolean=} opt_silent Unset without triggering an event.
- * @api stable
- */
-ol.Object.prototype.unset = function(key, opt_silent) {
-  if (key in this.values_) {
-    var oldValue = this.values_[key];
-    delete this.values_[key];
-    if (!opt_silent) {
-      this.notify(key, oldValue);
-    }
-  }
-};
-
-goog.provide('ol.array');
-
-goog.require('ol');
-
-
-/**
- * Performs a binary search on the provided sorted list and returns the index of the item if found. If it can't be found it'll return -1.
- * https://github.com/darkskyapp/binary-search
- *
- * @param {Array.<*>} haystack Items to search through.
- * @param {*} needle The item to look for.
- * @param {Function=} opt_comparator Comparator function.
- * @return {number} The index of the item if found, -1 if not.
- */
-ol.array.binarySearch = function(haystack, needle, opt_comparator) {
-  var mid, cmp;
-  var comparator = opt_comparator || ol.array.numberSafeCompareFunction;
-  var low = 0;
-  var high = haystack.length;
-  var found = false;
-
-  while (low < high) {
-    /* Note that "(low + high) >>> 1" may overflow, and results in a typecast
-     * to double (which gives the wrong results). */
-    mid = low + (high - low >> 1);
-    cmp = +comparator(haystack[mid], needle);
-
-    if (cmp < 0.0) { /* Too low. */
-      low  = mid + 1;
-
-    } else { /* Key found or too high */
-      high = mid;
-      found = !cmp;
-    }
-  }
-
-  /* Key not found. */
-  return found ? low : ~low;
-};
-
-
-/**
- * Compare function for array sort that is safe for numbers.
- * @param {*} a The first object to be compared.
- * @param {*} b The second object to be compared.
- * @return {number} A negative number, zero, or a positive number as the first
- *     argument is less than, equal to, or greater than the second.
- */
-ol.array.numberSafeCompareFunction = function(a, b) {
-  return a > b ? 1 : a < b ? -1 : 0;
-};
-
-
-/**
- * Whether the array contains the given object.
- * @param {Array.<*>} arr The array to test for the presence of the element.
- * @param {*} obj The object for which to test.
- * @return {boolean} The object is in the array.
- */
-ol.array.includes = function(arr, obj) {
-  return arr.indexOf(obj) >= 0;
-};
-
-
-/**
- * @param {Array.<number>} arr Array.
- * @param {number} target Target.
- * @param {number} direction 0 means return the nearest, > 0
- *    means return the largest nearest, < 0 means return the
- *    smallest nearest.
- * @return {number} Index.
- */
-ol.array.linearFindNearest = function(arr, target, direction) {
-  var n = arr.length;
-  if (arr[0] <= target) {
-    return 0;
-  } else if (target <= arr[n - 1]) {
-    return n - 1;
-  } else {
-    var i;
-    if (direction > 0) {
-      for (i = 1; i < n; ++i) {
-        if (arr[i] < target) {
-          return i - 1;
-        }
-      }
-    } else if (direction < 0) {
-      for (i = 1; i < n; ++i) {
-        if (arr[i] <= target) {
-          return i;
-        }
-      }
-    } else {
-      for (i = 1; i < n; ++i) {
-        if (arr[i] == target) {
-          return i;
-        } else if (arr[i] < target) {
-          if (arr[i - 1] - target < target - arr[i]) {
-            return i - 1;
-          } else {
-            return i;
-          }
-        }
-      }
-    }
-    return n - 1;
-  }
-};
-
-
-/**
- * @param {Array.<*>} arr Array.
- * @param {number} begin Begin index.
- * @param {number} end End index.
- */
-ol.array.reverseSubArray = function(arr, begin, end) {
-  ol.DEBUG && console.assert(begin >= 0,
-      'Array begin index should be equal to or greater than 0');
-  ol.DEBUG && console.assert(end < arr.length,
-      'Array end index should be less than the array length');
-  while (begin < end) {
-    var tmp = arr[begin];
-    arr[begin] = arr[end];
-    arr[end] = tmp;
-    ++begin;
-    --end;
-  }
-};
-
-
-/**
- * @param {Array.<*>} arr Array.
- * @return {!Array.<?>} Flattened Array.
- */
-ol.array.flatten = function(arr) {
-  var data = arr.reduce(function(flattened, value) {
-    if (Array.isArray(value)) {
-      return flattened.concat(ol.array.flatten(value));
-    } else {
-      return flattened.concat(value);
-    }
-  }, []);
-  return data;
-};
-
-
-/**
- * @param {Array.<VALUE>} arr The array to modify.
- * @param {Array.<VALUE>|VALUE} data The elements or arrays of elements
- *     to add to arr.
- * @template VALUE
- */
-ol.array.extend = function(arr, data) {
-  var i;
-  var extension = Array.isArray(data) ? data : [data];
-  var length = extension.length;
-  for (i = 0; i < length; i++) {
-    arr[arr.length] = extension[i];
-  }
-};
-
-
-/**
- * @param {Array.<VALUE>} arr The array to modify.
- * @param {VALUE} obj The element to remove.
- * @template VALUE
- * @return {boolean} If the element was removed.
- */
-ol.array.remove = function(arr, obj) {
-  var i = arr.indexOf(obj);
-  var found = i > -1;
-  if (found) {
-    arr.splice(i, 1);
-  }
-  return found;
-};
-
-
-/**
- * @param {Array.<VALUE>} arr The array to search in.
- * @param {function(VALUE, number, ?) : boolean} func The function to compare.
- * @template VALUE
- * @return {VALUE} The element found.
- */
-ol.array.find = function(arr, func) {
-  var length = arr.length >>> 0;
-  var value;
-
-  for (var i = 0; i < length; i++) {
-    value = arr[i];
-    if (func(value, i, arr)) {
-      return value;
-    }
-  }
-  return null;
-};
-
-
-/**
- * @param {Array|Uint8ClampedArray} arr1 The first array to compare.
- * @param {Array|Uint8ClampedArray} arr2 The second array to compare.
- * @return {boolean} Whether the two arrays are equal.
- */
-ol.array.equals = function(arr1, arr2) {
-  var len1 = arr1.length;
-  if (len1 !== arr2.length) {
-    return false;
-  }
-  for (var i = 0; i < len1; i++) {
-    if (arr1[i] !== arr2[i]) {
-      return false;
-    }
-  }
-  return true;
-};
-
-
-/**
- * @param {Array.<*>} arr The array to sort (modifies original).
- * @param {Function} compareFnc Comparison function.
- */
-ol.array.stableSort = function(arr, compareFnc) {
-  var length = arr.length;
-  var tmp = Array(arr.length);
-  var i;
-  for (i = 0; i < length; i++) {
-    tmp[i] = {index: i, value: arr[i]};
-  }
-  tmp.sort(function(a, b) {
-    return compareFnc(a.value, b.value) || a.index - b.index;
-  });
-  for (i = 0; i < arr.length; i++) {
-    arr[i] = tmp[i].value;
-  }
-};
-
-
-/**
- * @param {Array.<*>} arr The array to search in.
- * @param {Function} func Comparison function.
- * @return {number} Return index.
- */
-ol.array.findIndex = function(arr, func) {
-  var index;
-  var found = !arr.every(function(el, idx) {
-    index = idx;
-    return !func(el, idx, arr);
-  });
-  return found ? index : -1;
-};
-
-
-/**
- * @param {Array.<*>} arr The array to test.
- * @param {Function=} opt_func Comparison function.
- * @param {boolean=} opt_strict Strictly sorted (default false).
- * @return {boolean} Return index.
- */
-ol.array.isSorted = function(arr, opt_func, opt_strict) {
-  var compare = opt_func || ol.array.numberSafeCompareFunction;
-  return arr.every(function(currentVal, index) {
-    if (index === 0) {
-      return true;
-    }
-    var res = compare(arr[index - 1], currentVal);
-    return !(res > 0 || opt_strict && res === 0);
-  });
-};
-
-goog.provide('ol.ResolutionConstraint');
-
-goog.require('ol.array');
-goog.require('ol.math');
-
-
-/**
- * @param {Array.<number>} resolutions Resolutions.
- * @return {ol.ResolutionConstraintType} Zoom function.
- */
-ol.ResolutionConstraint.createSnapToResolutions = function(resolutions) {
-  return (
-      /**
-       * @param {number|undefined} resolution Resolution.
-       * @param {number} delta Delta.
-       * @param {number} direction Direction.
-       * @return {number|undefined} Resolution.
-       */
-      function(resolution, delta, direction) {
-        if (resolution !== undefined) {
-          var z =
-              ol.array.linearFindNearest(resolutions, resolution, direction);
-          z = ol.math.clamp(z + delta, 0, resolutions.length - 1);
-          var index = Math.floor(z);
-          if (z != index && index < resolutions.length - 1) {
-            var power = resolutions[index] / resolutions[index + 1];
-            return resolutions[index] / Math.pow(power, z - index);
-          } else {
-            return resolutions[index];
-          }
-        } else {
-          return undefined;
-        }
-      });
-};
-
-
-/**
- * @param {number} power Power.
- * @param {number} maxResolution Maximum resolution.
- * @param {number=} opt_maxLevel Maximum level.
- * @return {ol.ResolutionConstraintType} Zoom function.
- */
-ol.ResolutionConstraint.createSnapToPower = function(power, maxResolution, opt_maxLevel) {
-  return (
-      /**
-       * @param {number|undefined} resolution Resolution.
-       * @param {number} delta Delta.
-       * @param {number} direction Direction.
-       * @return {number|undefined} Resolution.
-       */
-      function(resolution, delta, direction) {
-        if (resolution !== undefined) {
-          var offset = -direction / 2 + 0.5;
-          var oldLevel = Math.floor(
-              Math.log(maxResolution / resolution) / Math.log(power) + offset);
-          var newLevel = Math.max(oldLevel + delta, 0);
-          if (opt_maxLevel !== undefined) {
-            newLevel = Math.min(newLevel, opt_maxLevel);
-          }
-          return maxResolution / Math.pow(power, newLevel);
-        } else {
-          return undefined;
-        }
-      });
-};
-
-goog.provide('ol.RotationConstraint');
-
-goog.require('ol.math');
-
-
-/**
- * @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
- * @return {number|undefined} Rotation.
- */
-ol.RotationConstraint.disable = function(rotation, delta) {
-  if (rotation !== undefined) {
-    return 0;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
- * @return {number|undefined} Rotation.
- */
-ol.RotationConstraint.none = function(rotation, delta) {
-  if (rotation !== undefined) {
-    return rotation + delta;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {number} n N.
- * @return {ol.RotationConstraintType} Rotation constraint.
- */
-ol.RotationConstraint.createSnapToN = function(n) {
-  var theta = 2 * Math.PI / n;
-  return (
-      /**
-       * @param {number|undefined} rotation Rotation.
-       * @param {number} delta Delta.
-       * @return {number|undefined} Rotation.
-       */
-      function(rotation, delta) {
-        if (rotation !== undefined) {
-          rotation = Math.floor((rotation + delta) / theta + 0.5) * theta;
-          return rotation;
-        } else {
-          return undefined;
-        }
-      });
-};
-
-
-/**
- * @param {number=} opt_tolerance Tolerance.
- * @return {ol.RotationConstraintType} Rotation constraint.
- */
-ol.RotationConstraint.createSnapToZero = function(opt_tolerance) {
-  var tolerance = opt_tolerance || ol.math.toRadians(5);
-  return (
-      /**
-       * @param {number|undefined} rotation Rotation.
-       * @param {number} delta Delta.
-       * @return {number|undefined} Rotation.
-       */
-      function(rotation, delta) {
-        if (rotation !== undefined) {
-          if (Math.abs(rotation + delta) <= tolerance) {
-            return 0;
-          } else {
-            return rotation + delta;
-          }
-        } else {
-          return undefined;
-        }
-      });
-};
-
-goog.provide('ol.string');
-
-/**
- * @param {number} number Number to be formatted
- * @param {number} width The desired width
- * @param {number=} opt_precision Precision of the output string (i.e. number of decimal places)
- * @returns {string} Formatted string
-*/
-ol.string.padNumber = function(number, width, opt_precision) {
-  var numberString = opt_precision !== undefined ? number.toFixed(opt_precision) : '' + number;
-  var decimal = numberString.indexOf('.');
-  decimal = decimal === -1 ? numberString.length : decimal;
-  return decimal > width ? numberString : new Array(1 + width - decimal).join('0') + numberString;
-};
-
-/**
- * Adapted from https://github.com/omichelsen/compare-versions/blob/master/index.js
- * @param {string|number} v1 First version
- * @param {string|number} v2 Second version
- * @returns {number} Value
- */
-ol.string.compareVersions = function(v1, v2) {
-  var s1 = ('' + v1).split('.');
-  var s2 = ('' + v2).split('.');
-
-  for (var i = 0; i < Math.max(s1.length, s2.length); i++) {
-    var n1 = parseInt(s1[i] || '0', 10);
-    var n2 = parseInt(s2[i] || '0', 10);
-
-    if (n1 > n2) return 1;
-    if (n2 > n1) return -1;
-  }
-
-  return 0;
-};
-
-goog.provide('ol.coordinate');
-
-goog.require('ol.math');
-goog.require('ol.string');
-
-
-/**
- * Add `delta` to `coordinate`. `coordinate` is modified in place and returned
- * by the function.
- *
- * Example:
- *
- *     var coord = [7.85, 47.983333];
- *     ol.coordinate.add(coord, [-2, 4]);
- *     // coord is now [5.85, 51.983333]
- *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.Coordinate} delta Delta.
- * @return {ol.Coordinate} The input coordinate adjusted by the given delta.
- * @api stable
- */
-ol.coordinate.add = function(coordinate, delta) {
-  coordinate[0] += delta[0];
-  coordinate[1] += delta[1];
-  return coordinate;
-};
-
-
-/**
- * Calculates the point closest to the passed coordinate on the passed segment.
- * This is the foot of the perpendicular of the coordinate to the segment when
- * the foot is on the segment, or the closest segment coordinate when the foot
- * is outside the segment.
- *
- * @param {ol.Coordinate} coordinate The coordinate.
- * @param {Array.<ol.Coordinate>} segment The two coordinates of the segment.
- * @return {ol.Coordinate} The foot of the perpendicular of the coordinate to
- *     the segment.
- */
-ol.coordinate.closestOnSegment = function(coordinate, segment) {
-  var x0 = coordinate[0];
-  var y0 = coordinate[1];
-  var start = segment[0];
-  var end = segment[1];
-  var x1 = start[0];
-  var y1 = start[1];
-  var x2 = end[0];
-  var y2 = end[1];
-  var dx = x2 - x1;
-  var dy = y2 - y1;
-  var along = (dx === 0 && dy === 0) ? 0 :
-      ((dx * (x0 - x1)) + (dy * (y0 - y1))) / ((dx * dx + dy * dy) || 0);
-  var x, y;
-  if (along <= 0) {
-    x = x1;
-    y = y1;
-  } else if (along >= 1) {
-    x = x2;
-    y = y2;
-  } else {
-    x = x1 + along * dx;
-    y = y1 + along * dy;
-  }
-  return [x, y];
-};
-
-
-/**
- * Returns a {@link ol.CoordinateFormatType} function that can be used to format
- * a {ol.Coordinate} to a string.
- *
- * Example without specifying the fractional digits:
- *
- *     var coord = [7.85, 47.983333];
- *     var stringifyFunc = ol.coordinate.createStringXY();
- *     var out = stringifyFunc(coord);
- *     // out is now '8, 48'
- *
- * Example with explicitly specifying 2 fractional digits:
- *
- *     var coord = [7.85, 47.983333];
- *     var stringifyFunc = ol.coordinate.createStringXY(2);
- *     var out = stringifyFunc(coord);
- *     // out is now '7.85, 47.98'
- *
- * @param {number=} opt_fractionDigits The number of digits to include
- *    after the decimal point. Default is `0`.
- * @return {ol.CoordinateFormatType} Coordinate format.
- * @api stable
- */
-ol.coordinate.createStringXY = function(opt_fractionDigits) {
-  return (
-      /**
-       * @param {ol.Coordinate|undefined} coordinate Coordinate.
-       * @return {string} String XY.
-       */
-      function(coordinate) {
-        return ol.coordinate.toStringXY(coordinate, opt_fractionDigits);
-      });
-};
-
-
-/**
- * @private
- * @param {number} degrees Degrees.
- * @param {string} hemispheres Hemispheres.
- * @param {number=} opt_fractionDigits The number of digits to include
- *    after the decimal point. Default is `0`.
- * @return {string} String.
- */
-ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres, opt_fractionDigits) {
-  var normalizedDegrees = ol.math.modulo(degrees + 180, 360) - 180;
-  var x = Math.abs(3600 * normalizedDegrees);
-  var dflPrecision = opt_fractionDigits || 0;
-  return Math.floor(x / 3600) + '\u00b0 ' +
-      ol.string.padNumber(Math.floor((x / 60) % 60), 2) + '\u2032 ' +
-      ol.string.padNumber((x % 60), 2, dflPrecision) + '\u2033 ' +
-      hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0);
-};
-
-
-/**
- * Transforms the given {@link ol.Coordinate} to a string using the given string
- * template. The strings `{x}` and `{y}` in the template will be replaced with
- * the first and second coordinate values respectively.
- *
- * Example without specifying the fractional digits:
- *
- *     var coord = [7.85, 47.983333];
- *     var template = 'Coordinate is ({x}|{y}).';
- *     var out = ol.coordinate.format(coord, template);
- *     // out is now 'Coordinate is (8|48).'
- *
- * Example explicitly specifying the fractional digits:
- *
- *     var coord = [7.85, 47.983333];
- *     var template = 'Coordinate is ({x}|{y}).';
- *     var out = ol.coordinate.format(coord, template, 2);
- *     // out is now 'Coordinate is (7.85|47.98).'
- *
- * @param {ol.Coordinate|undefined} coordinate Coordinate.
- * @param {string} template A template string with `{x}` and `{y}` placeholders
- *     that will be replaced by first and second coordinate values.
- * @param {number=} opt_fractionDigits The number of digits to include
- *    after the decimal point. Default is `0`.
- * @return {string} Formatted coordinate.
- * @api stable
- */
-ol.coordinate.format = function(coordinate, template, opt_fractionDigits) {
-  if (coordinate) {
-    return template
-      .replace('{x}', coordinate[0].toFixed(opt_fractionDigits))
-      .replace('{y}', coordinate[1].toFixed(opt_fractionDigits));
-  } else {
-    return '';
-  }
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate1 First coordinate.
- * @param {ol.Coordinate} coordinate2 Second coordinate.
- * @return {boolean} Whether the passed coordinates are equal.
- */
-ol.coordinate.equals = function(coordinate1, coordinate2) {
-  var equals = true;
-  for (var i = coordinate1.length - 1; i >= 0; --i) {
-    if (coordinate1[i] != coordinate2[i]) {
-      equals = false;
-      break;
-    }
-  }
-  return equals;
-};
-
-
-/**
- * Rotate `coordinate` by `angle`. `coordinate` is modified in place and
- * returned by the function.
- *
- * Example:
- *
- *     var coord = [7.85, 47.983333];
- *     var rotateRadians = Math.PI / 2; // 90 degrees
- *     ol.coordinate.rotate(coord, rotateRadians);
- *     // coord is now [-47.983333, 7.85]
- *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} angle Angle in radian.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
- */
-ol.coordinate.rotate = function(coordinate, angle) {
-  var cosAngle = Math.cos(angle);
-  var sinAngle = Math.sin(angle);
-  var x = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
-  var y = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
-  coordinate[0] = x;
-  coordinate[1] = y;
-  return coordinate;
-};
-
-
-/**
- * Scale `coordinate` by `scale`. `coordinate` is modified in place and returned
- * by the function.
- *
- * Example:
- *
- *     var coord = [7.85, 47.983333];
- *     var scale = 1.2;
- *     ol.coordinate.scale(coord, scale);
- *     // coord is now [9.42, 57.5799996]
- *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} scale Scale factor.
- * @return {ol.Coordinate} Coordinate.
- */
-ol.coordinate.scale = function(coordinate, scale) {
-  coordinate[0] *= scale;
-  coordinate[1] *= scale;
-  return coordinate;
-};
-
-
-/**
- * Subtract `delta` to `coordinate`. `coordinate` is modified in place and
- * returned by the function.
- *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.Coordinate} delta Delta.
- * @return {ol.Coordinate} Coordinate.
- */
-ol.coordinate.sub = function(coordinate, delta) {
-  coordinate[0] -= delta[0];
-  coordinate[1] -= delta[1];
-  return coordinate;
-};
-
-
-/**
- * @param {ol.Coordinate} coord1 First coordinate.
- * @param {ol.Coordinate} coord2 Second coordinate.
- * @return {number} Squared distance between coord1 and coord2.
- */
-ol.coordinate.squaredDistance = function(coord1, coord2) {
-  var dx = coord1[0] - coord2[0];
-  var dy = coord1[1] - coord2[1];
-  return dx * dx + dy * dy;
-};
-
-
-/**
- * Calculate the squared distance from a coordinate to a line segment.
- *
- * @param {ol.Coordinate} coordinate Coordinate of the point.
- * @param {Array.<ol.Coordinate>} segment Line segment (2 coordinates).
- * @return {number} Squared distance from the point to the line segment.
- */
-ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
-  return ol.coordinate.squaredDistance(coordinate,
-      ol.coordinate.closestOnSegment(coordinate, segment));
-};
-
-
-/**
- * Format a geographic coordinate with the hemisphere, degrees, minutes, and
- * seconds.
- *
- * Example without specifying fractional digits:
- *
- *     var coord = [7.85, 47.983333];
- *     var out = ol.coordinate.toStringHDMS(coord);
- *     // out is now '47° 58′ 60″ N 7° 50′ 60″ E'
- *
- * Example explicitly specifying 1 fractional digit:
- *
- *     var coord = [7.85, 47.983333];
- *     var out = ol.coordinate.toStringHDMS(coord, 1);
- *     // out is now '47° 58′ 60.0″ N 7° 50′ 60.0″ E'
- *
- * @param {ol.Coordinate|undefined} coordinate Coordinate.
- * @param {number=} opt_fractionDigits The number of digits to include
- *    after the decimal point. Default is `0`.
- * @return {string} Hemisphere, degrees, minutes and seconds.
- * @api stable
- */
-ol.coordinate.toStringHDMS = function(coordinate, opt_fractionDigits) {
-  if (coordinate) {
-    return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS', opt_fractionDigits) + ' ' +
-        ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW', opt_fractionDigits);
-  } else {
-    return '';
-  }
-};
-
-
-/**
- * Format a coordinate as a comma delimited string.
- *
- * Example without specifying fractional digits:
- *
- *     var coord = [7.85, 47.983333];
- *     var out = ol.coordinate.toStringXY(coord);
- *     // out is now '8, 48'
- *
- * Example explicitly specifying 1 fractional digit:
- *
- *     var coord = [7.85, 47.983333];
- *     var out = ol.coordinate.toStringXY(coord, 1);
- *     // out is now '7.8, 48.0'
- *
- * @param {ol.Coordinate|undefined} coordinate Coordinate.
- * @param {number=} opt_fractionDigits The number of digits to include
- *    after the decimal point. Default is `0`.
- * @return {string} XY.
- * @api stable
- */
-ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
-  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
-};
-
-goog.provide('ol.extent');
-goog.provide('ol.extent.Corner');
-goog.provide('ol.extent.Relationship');
-
-goog.require('ol');
-goog.require('ol.asserts');
-
-
-/**
- * Extent corner.
- * @enum {string}
- */
-ol.extent.Corner = {
-  BOTTOM_LEFT: 'bottom-left',
-  BOTTOM_RIGHT: 'bottom-right',
-  TOP_LEFT: 'top-left',
-  TOP_RIGHT: 'top-right'
-};
-
-
-/**
- * Relationship to an extent.
- * @enum {number}
- */
-ol.extent.Relationship = {
-  UNKNOWN: 0,
-  INTERSECTING: 1,
-  ABOVE: 2,
-  RIGHT: 4,
-  BELOW: 8,
-  LEFT: 16
-};
-
-
-/**
- * Build an extent that includes all given coordinates.
- *
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @return {ol.Extent} Bounding extent.
- * @api stable
- */
-ol.extent.boundingExtent = function(coordinates) {
-  var extent = ol.extent.createEmpty();
-  for (var i = 0, ii = coordinates.length; i < ii; ++i) {
-    ol.extent.extendCoordinate(extent, coordinates[i]);
-  }
-  return extent;
-};
-
-
-/**
- * @param {Array.<number>} xs Xs.
- * @param {Array.<number>} ys Ys.
- * @param {ol.Extent=} opt_extent Destination extent.
- * @private
- * @return {ol.Extent} Extent.
- */
-ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) {
-  ol.DEBUG && console.assert(xs.length > 0, 'xs length should be larger than 0');
-  ol.DEBUG && console.assert(ys.length > 0, 'ys length should be larger than 0');
-  var minX = Math.min.apply(null, xs);
-  var minY = Math.min.apply(null, ys);
-  var maxX = Math.max.apply(null, xs);
-  var maxY = Math.max.apply(null, ys);
-  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
-};
-
-
-/**
- * Return extent increased by the provided value.
- * @param {ol.Extent} extent Extent.
- * @param {number} value The amount by which the extent should be buffered.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- * @api stable
- */
-ol.extent.buffer = function(extent, value, opt_extent) {
-  if (opt_extent) {
-    opt_extent[0] = extent[0] - value;
-    opt_extent[1] = extent[1] - value;
-    opt_extent[2] = extent[2] + value;
-    opt_extent[3] = extent[3] + value;
-    return opt_extent;
-  } else {
-    return [
-      extent[0] - value,
-      extent[1] - value,
-      extent[2] + value,
-      extent[3] + value
-    ];
-  }
-};
-
-
-/**
- * Creates a clone of an extent.
- *
- * @param {ol.Extent} extent Extent to clone.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} The clone.
- */
-ol.extent.clone = function(extent, opt_extent) {
-  if (opt_extent) {
-    opt_extent[0] = extent[0];
-    opt_extent[1] = extent[1];
-    opt_extent[2] = extent[2];
-    opt_extent[3] = extent[3];
-    return opt_extent;
-  } else {
-    return extent.slice();
-  }
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {number} Closest squared distance.
- */
-ol.extent.closestSquaredDistanceXY = function(extent, x, y) {
-  var dx, dy;
-  if (x < extent[0]) {
-    dx = extent[0] - x;
-  } else if (extent[2] < x) {
-    dx = x - extent[2];
-  } else {
-    dx = 0;
-  }
-  if (y < extent[1]) {
-    dy = extent[1] - y;
-  } else if (extent[3] < y) {
-    dy = y - extent[3];
-  } else {
-    dy = 0;
-  }
-  return dx * dx + dy * dy;
-};
-
-
-/**
- * Check if the passed coordinate is contained or on the edge of the extent.
- *
- * @param {ol.Extent} extent Extent.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {boolean} The coordinate is contained in the extent.
- * @api stable
- */
-ol.extent.containsCoordinate = function(extent, coordinate) {
-  return ol.extent.containsXY(extent, coordinate[0], coordinate[1]);
-};
-
-
-/**
- * Check if one extent contains another.
- *
- * An extent is deemed contained if it lies completely within the other extent,
- * including if they share one or more edges.
- *
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @return {boolean} The second extent is contained by or on the edge of the
- *     first.
- * @api stable
- */
-ol.extent.containsExtent = function(extent1, extent2) {
-  return extent1[0] <= extent2[0] && extent2[2] <= extent1[2] &&
-      extent1[1] <= extent2[1] && extent2[3] <= extent1[3];
-};
-
-
-/**
- * Check if the passed coordinate is contained or on the edge of the extent.
- *
- * @param {ol.Extent} extent Extent.
- * @param {number} x X coordinate.
- * @param {number} y Y coordinate.
- * @return {boolean} The x, y values are contained in the extent.
- * @api stable
- */
-ol.extent.containsXY = function(extent, x, y) {
-  return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3];
-};
-
-
-/**
- * Get the relationship between a coordinate and extent.
- * @param {ol.Extent} extent The extent.
- * @param {ol.Coordinate} coordinate The coordinate.
- * @return {number} The relationship (bitwise compare with
- *     ol.extent.Relationship).
- */
-ol.extent.coordinateRelationship = function(extent, coordinate) {
-  var minX = extent[0];
-  var minY = extent[1];
-  var maxX = extent[2];
-  var maxY = extent[3];
-  var x = coordinate[0];
-  var y = coordinate[1];
-  var relationship = ol.extent.Relationship.UNKNOWN;
-  if (x < minX) {
-    relationship = relationship | ol.extent.Relationship.LEFT;
-  } else if (x > maxX) {
-    relationship = relationship | ol.extent.Relationship.RIGHT;
-  }
-  if (y < minY) {
-    relationship = relationship | ol.extent.Relationship.BELOW;
-  } else if (y > maxY) {
-    relationship = relationship | ol.extent.Relationship.ABOVE;
-  }
-  if (relationship === ol.extent.Relationship.UNKNOWN) {
-    relationship = ol.extent.Relationship.INTERSECTING;
-  }
-  return relationship;
-};
-
-
-/**
- * Create an empty extent.
- * @return {ol.Extent} Empty extent.
- * @api stable
- */
-ol.extent.createEmpty = function() {
-  return [Infinity, Infinity, -Infinity, -Infinity];
-};
-
-
-/**
- * Create a new extent or update the provided extent.
- * @param {number} minX Minimum X.
- * @param {number} minY Minimum Y.
- * @param {number} maxX Maximum X.
- * @param {number} maxY Maximum Y.
- * @param {ol.Extent=} opt_extent Destination extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) {
-  if (opt_extent) {
-    opt_extent[0] = minX;
-    opt_extent[1] = minY;
-    opt_extent[2] = maxX;
-    opt_extent[3] = maxY;
-    return opt_extent;
-  } else {
-    return [minX, minY, maxX, maxY];
-  }
-};
-
-
-/**
- * Create a new empty extent or make the provided one empty.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.createOrUpdateEmpty = function(opt_extent) {
-  return ol.extent.createOrUpdate(
-      Infinity, Infinity, -Infinity, -Infinity, opt_extent);
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) {
-  var x = coordinate[0];
-  var y = coordinate[1];
-  return ol.extent.createOrUpdate(x, y, x, y, opt_extent);
-};
-
-
-/**
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) {
-  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
-  return ol.extent.extendCoordinates(extent, coordinates);
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.createOrUpdateFromFlatCoordinates = function(flatCoordinates, offset, end, stride, opt_extent) {
-  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
-  return ol.extent.extendFlatCoordinates(
-      extent, flatCoordinates, offset, end, stride);
-};
-
-
-/**
- * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.createOrUpdateFromRings = function(rings, opt_extent) {
-  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
-  return ol.extent.extendRings(extent, rings);
-};
-
-
-/**
- * Empty an extent in place.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.empty = function(extent) {
-  extent[0] = extent[1] = Infinity;
-  extent[2] = extent[3] = -Infinity;
-  return extent;
-};
-
-
-/**
- * Determine if two extents are equivalent.
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @return {boolean} The two extents are equivalent.
- * @api stable
- */
-ol.extent.equals = function(extent1, extent2) {
-  return extent1[0] == extent2[0] && extent1[2] == extent2[2] &&
-      extent1[1] == extent2[1] && extent1[3] == extent2[3];
-};
-
-
-/**
- * Modify an extent to include another extent.
- * @param {ol.Extent} extent1 The extent to be modified.
- * @param {ol.Extent} extent2 The extent that will be included in the first.
- * @return {ol.Extent} A reference to the first (extended) extent.
- * @api stable
- */
-ol.extent.extend = function(extent1, extent2) {
-  if (extent2[0] < extent1[0]) {
-    extent1[0] = extent2[0];
-  }
-  if (extent2[2] > extent1[2]) {
-    extent1[2] = extent2[2];
-  }
-  if (extent2[1] < extent1[1]) {
-    extent1[1] = extent2[1];
-  }
-  if (extent2[3] > extent1[3]) {
-    extent1[3] = extent2[3];
-  }
-  return extent1;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Coordinate} coordinate Coordinate.
- */
-ol.extent.extendCoordinate = function(extent, coordinate) {
-  if (coordinate[0] < extent[0]) {
-    extent[0] = coordinate[0];
-  }
-  if (coordinate[0] > extent[2]) {
-    extent[2] = coordinate[0];
-  }
-  if (coordinate[1] < extent[1]) {
-    extent[1] = coordinate[1];
-  }
-  if (coordinate[1] > extent[3]) {
-    extent[3] = coordinate[1];
-  }
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @return {ol.Extent} Extent.
- */
-ol.extent.extendCoordinates = function(extent, coordinates) {
-  var i, ii;
-  for (i = 0, ii = coordinates.length; i < ii; ++i) {
-    ol.extent.extendCoordinate(extent, coordinates[i]);
-  }
-  return extent;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {ol.Extent} Extent.
- */
-ol.extent.extendFlatCoordinates = function(extent, flatCoordinates, offset, end, stride) {
-  for (; offset < end; offset += stride) {
-    ol.extent.extendXY(
-        extent, flatCoordinates[offset], flatCoordinates[offset + 1]);
-  }
-  return extent;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
- * @return {ol.Extent} Extent.
- */
-ol.extent.extendRings = function(extent, rings) {
-  var i, ii;
-  for (i = 0, ii = rings.length; i < ii; ++i) {
-    ol.extent.extendCoordinates(extent, rings[i]);
-  }
-  return extent;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} x X.
- * @param {number} y Y.
- */
-ol.extent.extendXY = function(extent, x, y) {
-  extent[0] = Math.min(extent[0], x);
-  extent[1] = Math.min(extent[1], y);
-  extent[2] = Math.max(extent[2], x);
-  extent[3] = Math.max(extent[3], y);
-};
-
-
-/**
- * This function calls `callback` for each corner of the extent. If the
- * callback returns a truthy value the function returns that value
- * immediately. Otherwise the function returns `false`.
- * @param {ol.Extent} extent Extent.
- * @param {function(this:T, ol.Coordinate): S} callback Callback.
- * @param {T=} opt_this Value to use as `this` when executing `callback`.
- * @return {S|boolean} Value.
- * @template S, T
- */
-ol.extent.forEachCorner = function(extent, callback, opt_this) {
-  var val;
-  val = callback.call(opt_this, ol.extent.getBottomLeft(extent));
-  if (val) {
-    return val;
-  }
-  val = callback.call(opt_this, ol.extent.getBottomRight(extent));
-  if (val) {
-    return val;
-  }
-  val = callback.call(opt_this, ol.extent.getTopRight(extent));
-  if (val) {
-    return val;
-  }
-  val = callback.call(opt_this, ol.extent.getTopLeft(extent));
-  if (val) {
-    return val;
-  }
-  return false;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @return {number} Area.
- */
-ol.extent.getArea = function(extent) {
-  var area = 0;
-  if (!ol.extent.isEmpty(extent)) {
-    area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent);
-  }
-  return area;
-};
-
-
-/**
- * Get the bottom left coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Bottom left coordinate.
- * @api stable
- */
-ol.extent.getBottomLeft = function(extent) {
-  return [extent[0], extent[1]];
-};
-
-
-/**
- * Get the bottom right coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Bottom right coordinate.
- * @api stable
- */
-ol.extent.getBottomRight = function(extent) {
-  return [extent[2], extent[1]];
-};
-
-
-/**
- * Get the center coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Center.
- * @api stable
- */
-ol.extent.getCenter = function(extent) {
-  return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2];
-};
-
-
-/**
- * Get a corner coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @param {ol.extent.Corner} corner Corner.
- * @return {ol.Coordinate} Corner coordinate.
- */
-ol.extent.getCorner = function(extent, corner) {
-  var coordinate;
-  if (corner === ol.extent.Corner.BOTTOM_LEFT) {
-    coordinate = ol.extent.getBottomLeft(extent);
-  } else if (corner === ol.extent.Corner.BOTTOM_RIGHT) {
-    coordinate = ol.extent.getBottomRight(extent);
-  } else if (corner === ol.extent.Corner.TOP_LEFT) {
-    coordinate = ol.extent.getTopLeft(extent);
-  } else if (corner === ol.extent.Corner.TOP_RIGHT) {
-    coordinate = ol.extent.getTopRight(extent);
-  } else {
-    ol.asserts.assert(false, 13); // Invalid corner
-  }
-  return /** @type {!ol.Coordinate} */ (coordinate);
-};
-
-
-/**
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @return {number} Enlarged area.
- */
-ol.extent.getEnlargedArea = function(extent1, extent2) {
-  var minX = Math.min(extent1[0], extent2[0]);
-  var minY = Math.min(extent1[1], extent2[1]);
-  var maxX = Math.max(extent1[2], extent2[2]);
-  var maxY = Math.max(extent1[3], extent2[3]);
-  return (maxX - minX) * (maxY - minY);
-};
-
-
-/**
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Size} size Size.
- * @param {ol.Extent=} opt_extent Destination extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.getForViewAndSize = function(center, resolution, rotation, size, opt_extent) {
-  var dx = resolution * size[0] / 2;
-  var dy = resolution * size[1] / 2;
-  var cosRotation = Math.cos(rotation);
-  var sinRotation = Math.sin(rotation);
-  var xCos = dx * cosRotation;
-  var xSin = dx * sinRotation;
-  var yCos = dy * cosRotation;
-  var ySin = dy * sinRotation;
-  var x = center[0];
-  var y = center[1];
-  var x0 = x - xCos + ySin;
-  var x1 = x - xCos - ySin;
-  var x2 = x + xCos - ySin;
-  var x3 = x + xCos + ySin;
-  var y0 = y - xSin - yCos;
-  var y1 = y - xSin + yCos;
-  var y2 = y + xSin + yCos;
-  var y3 = y + xSin - yCos;
-  return ol.extent.createOrUpdate(
-      Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3),
-      Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3),
-      opt_extent);
-};
-
-
-/**
- * Get the height of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {number} Height.
- * @api stable
- */
-ol.extent.getHeight = function(extent) {
-  return extent[3] - extent[1];
-};
-
-
-/**
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @return {number} Intersection area.
- */
-ol.extent.getIntersectionArea = function(extent1, extent2) {
-  var intersection = ol.extent.getIntersection(extent1, extent2);
-  return ol.extent.getArea(intersection);
-};
-
-
-/**
- * Get the intersection of two extents.
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @param {ol.Extent=} opt_extent Optional extent to populate with intersection.
- * @return {ol.Extent} Intersecting extent.
- * @api stable
- */
-ol.extent.getIntersection = function(extent1, extent2, opt_extent) {
-  var intersection = opt_extent ? opt_extent : ol.extent.createEmpty();
-  if (ol.extent.intersects(extent1, extent2)) {
-    if (extent1[0] > extent2[0]) {
-      intersection[0] = extent1[0];
-    } else {
-      intersection[0] = extent2[0];
-    }
-    if (extent1[1] > extent2[1]) {
-      intersection[1] = extent1[1];
-    } else {
-      intersection[1] = extent2[1];
-    }
-    if (extent1[2] < extent2[2]) {
-      intersection[2] = extent1[2];
-    } else {
-      intersection[2] = extent2[2];
-    }
-    if (extent1[3] < extent2[3]) {
-      intersection[3] = extent1[3];
-    } else {
-      intersection[3] = extent2[3];
-    }
-  }
-  return intersection;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @return {number} Margin.
- */
-ol.extent.getMargin = function(extent) {
-  return ol.extent.getWidth(extent) + ol.extent.getHeight(extent);
-};
-
-
-/**
- * Get the size (width, height) of an extent.
- * @param {ol.Extent} extent The extent.
- * @return {ol.Size} The extent size.
- * @api stable
- */
-ol.extent.getSize = function(extent) {
-  return [extent[2] - extent[0], extent[3] - extent[1]];
-};
-
-
-/**
- * Get the top left coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Top left coordinate.
- * @api stable
- */
-ol.extent.getTopLeft = function(extent) {
-  return [extent[0], extent[3]];
-};
-
-
-/**
- * Get the top right coordinate of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {ol.Coordinate} Top right coordinate.
- * @api stable
- */
-ol.extent.getTopRight = function(extent) {
-  return [extent[2], extent[3]];
-};
-
-
-/**
- * Get the width of an extent.
- * @param {ol.Extent} extent Extent.
- * @return {number} Width.
- * @api stable
- */
-ol.extent.getWidth = function(extent) {
-  return extent[2] - extent[0];
-};
-
-
-/**
- * Determine if one extent intersects another.
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent.
- * @return {boolean} The two extents intersect.
- * @api stable
- */
-ol.extent.intersects = function(extent1, extent2) {
-  return extent1[0] <= extent2[2] &&
-      extent1[2] >= extent2[0] &&
-      extent1[1] <= extent2[3] &&
-      extent1[3] >= extent2[1];
-};
-
-
-/**
- * Determine if an extent is empty.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} Is empty.
- * @api stable
- */
-ol.extent.isEmpty = function(extent) {
-  return extent[2] < extent[0] || extent[3] < extent[1];
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @return {boolean} Is infinite.
- */
-ol.extent.isInfinite = function(extent) {
-  return extent[0] == -Infinity || extent[1] == -Infinity ||
-      extent[2] == Infinity || extent[3] == Infinity;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {ol.Coordinate} Coordinate.
- */
-ol.extent.normalize = function(extent, coordinate) {
-  return [
-    (coordinate[0] - extent[0]) / (extent[2] - extent[0]),
-    (coordinate[1] - extent[1]) / (extent[3] - extent[1])
-  ];
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} Extent.
- */
-ol.extent.returnOrUpdate = function(extent, opt_extent) {
-  if (opt_extent) {
-    opt_extent[0] = extent[0];
-    opt_extent[1] = extent[1];
-    opt_extent[2] = extent[2];
-    opt_extent[3] = extent[3];
-    return opt_extent;
-  } else {
-    return extent;
-  }
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} value Value.
- */
-ol.extent.scaleFromCenter = function(extent, value) {
-  var deltaX = ((extent[2] - extent[0]) / 2) * (value - 1);
-  var deltaY = ((extent[3] - extent[1]) / 2) * (value - 1);
-  extent[0] -= deltaX;
-  extent[2] += deltaX;
-  extent[1] -= deltaY;
-  extent[3] += deltaY;
-};
-
-
-/**
- * Determine if the segment between two coordinates intersects (crosses,
- * touches, or is contained by) the provided extent.
- * @param {ol.Extent} extent The extent.
- * @param {ol.Coordinate} start Segment start coordinate.
- * @param {ol.Coordinate} end Segment end coordinate.
- * @return {boolean} The segment intersects the extent.
- */
-ol.extent.intersectsSegment = function(extent, start, end) {
-  var intersects = false;
-  var startRel = ol.extent.coordinateRelationship(extent, start);
-  var endRel = ol.extent.coordinateRelationship(extent, end);
-  if (startRel === ol.extent.Relationship.INTERSECTING ||
-      endRel === ol.extent.Relationship.INTERSECTING) {
-    intersects = true;
-  } else {
-    var minX = extent[0];
-    var minY = extent[1];
-    var maxX = extent[2];
-    var maxY = extent[3];
-    var startX = start[0];
-    var startY = start[1];
-    var endX = end[0];
-    var endY = end[1];
-    var slope = (endY - startY) / (endX - startX);
-    var x, y;
-    if (!!(endRel & ol.extent.Relationship.ABOVE) &&
-        !(startRel & ol.extent.Relationship.ABOVE)) {
-      // potentially intersects top
-      x = endX - ((endY - maxY) / slope);
-      intersects = x >= minX && x <= maxX;
-    }
-    if (!intersects && !!(endRel & ol.extent.Relationship.RIGHT) &&
-        !(startRel & ol.extent.Relationship.RIGHT)) {
-      // potentially intersects right
-      y = endY - ((endX - maxX) * slope);
-      intersects = y >= minY && y <= maxY;
-    }
-    if (!intersects && !!(endRel & ol.extent.Relationship.BELOW) &&
-        !(startRel & ol.extent.Relationship.BELOW)) {
-      // potentially intersects bottom
-      x = endX - ((endY - minY) / slope);
-      intersects = x >= minX && x <= maxX;
-    }
-    if (!intersects && !!(endRel & ol.extent.Relationship.LEFT) &&
-        !(startRel & ol.extent.Relationship.LEFT)) {
-      // potentially intersects left
-      y = endY - ((endX - minX) * slope);
-      intersects = y >= minY && y <= maxY;
-    }
-
-  }
-  return intersects;
-};
-
-
-/**
- * @param {ol.Extent} extent1 Extent 1.
- * @param {ol.Extent} extent2 Extent 2.
- * @return {boolean} Touches.
- */
-ol.extent.touches = function(extent1, extent2) {
-  var intersects = ol.extent.intersects(extent1, extent2);
-  return intersects &&
-      (extent1[0] == extent2[2] || extent1[2] == extent2[0] ||
-       extent1[1] == extent2[3] || extent1[3] == extent2[1]);
-};
-
-
-/**
- * Apply a transform function to the extent.
- * @param {ol.Extent} extent Extent.
- * @param {ol.TransformFunction} transformFn Transform function.  Called with
- * [minX, minY, maxX, maxY] extent coordinates.
- * @param {ol.Extent=} opt_extent Destination extent.
- * @return {ol.Extent} Extent.
- * @api stable
- */
-ol.extent.applyTransform = function(extent, transformFn, opt_extent) {
-  var coordinates = [
-    extent[0], extent[1],
-    extent[0], extent[3],
-    extent[2], extent[1],
-    extent[2], extent[3]
-  ];
-  transformFn(coordinates, coordinates, 2);
-  var xs = [coordinates[0], coordinates[2], coordinates[4], coordinates[6]];
-  var ys = [coordinates[1], coordinates[3], coordinates[5], coordinates[7]];
-  return ol.extent.boundingExtentXYs_(xs, ys, opt_extent);
-};
-
-goog.provide('ol.functions');
-
-/**
- * Always returns true.
- * @returns {boolean} true.
- */
-ol.functions.TRUE = function() {
-  return true;
-};
-
-/**
- * Always returns false.
- * @returns {boolean} false.
- */
-ol.functions.FALSE = function() {
-  return false;
-};
-
-/**
- * @license
- * Latitude/longitude spherical geodesy formulae taken from
- * http://www.movable-type.co.uk/scripts/latlong.html
- * Licensed under CC-BY-3.0.
- */
-
-goog.provide('ol.Sphere');
-
-goog.require('ol.math');
-
-
-/**
- * @classdesc
- * Class to create objects that can be used with {@link
- * ol.geom.Polygon.circular}.
- *
- * For example to create a sphere whose radius is equal to the semi-major
- * axis of the WGS84 ellipsoid:
- *
- * ```js
- * var wgs84Sphere= new ol.Sphere(6378137);
- * ```
- *
- * @constructor
- * @param {number} radius Radius.
- * @api
- */
-ol.Sphere = function(radius) {
-
-  /**
-   * @type {number}
-   */
-  this.radius = radius;
-
-};
-
-
-/**
- * Returns the geodesic area for a list of coordinates.
- *
- * [Reference](http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409)
- * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
- * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
- * Laboratory, Pasadena, CA, June 2007
- *
- * @param {Array.<ol.Coordinate>} coordinates List of coordinates of a linear
- * ring. If the ring is oriented clockwise, the area will be positive,
- * otherwise it will be negative.
- * @return {number} Area.
- * @api
- */
-ol.Sphere.prototype.geodesicArea = function(coordinates) {
-  var area = 0, len = coordinates.length;
-  var x1 = coordinates[len - 1][0];
-  var y1 = coordinates[len - 1][1];
-  for (var i = 0; i < len; i++) {
-    var x2 = coordinates[i][0], y2 = coordinates[i][1];
-    area += ol.math.toRadians(x2 - x1) *
-        (2 + Math.sin(ol.math.toRadians(y1)) +
-        Math.sin(ol.math.toRadians(y2)));
-    x1 = x2;
-    y1 = y2;
-  }
-  return area * this.radius * this.radius / 2.0;
-};
-
-
-/**
- * Returns the distance from c1 to c2 using the haversine formula.
- *
- * @param {ol.Coordinate} c1 Coordinate 1.
- * @param {ol.Coordinate} c2 Coordinate 2.
- * @return {number} Haversine distance.
- * @api
- */
-ol.Sphere.prototype.haversineDistance = function(c1, c2) {
-  var lat1 = ol.math.toRadians(c1[1]);
-  var lat2 = ol.math.toRadians(c2[1]);
-  var deltaLatBy2 = (lat2 - lat1) / 2;
-  var deltaLonBy2 = ol.math.toRadians(c2[0] - c1[0]) / 2;
-  var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) +
-      Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) *
-      Math.cos(lat1) * Math.cos(lat2);
-  return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
-};
-
-
-/**
- * Returns the coordinate at the given distance and bearing from `c1`.
- *
- * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees).
- * @param {number} distance The great-circle distance between the origin
- *     point and the target point.
- * @param {number} bearing The bearing (in radians).
- * @return {ol.Coordinate} The target point.
- */
-ol.Sphere.prototype.offset = function(c1, distance, bearing) {
-  var lat1 = ol.math.toRadians(c1[1]);
-  var lon1 = ol.math.toRadians(c1[0]);
-  var dByR = distance / this.radius;
-  var lat = Math.asin(
-      Math.sin(lat1) * Math.cos(dByR) +
-      Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
-  var lon = lon1 + Math.atan2(
-      Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1),
-      Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat));
-  return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
-};
-
-goog.provide('ol.sphere.NORMAL');
-
-goog.require('ol.Sphere');
-
-
-/**
- * The normal sphere.
- * @const
- * @type {ol.Sphere}
- */
-ol.sphere.NORMAL = new ol.Sphere(6370997);
-
-goog.provide('ol.proj');
-goog.provide('ol.proj.METERS_PER_UNIT');
-goog.provide('ol.proj.Projection');
-goog.provide('ol.proj.Units');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.sphere.NORMAL');
-
-
-/**
- * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or
- * `'us-ft'`.
- * @enum {string}
- */
-ol.proj.Units = {
-  DEGREES: 'degrees',
-  FEET: 'ft',
-  METERS: 'm',
-  PIXELS: 'pixels',
-  TILE_PIXELS: 'tile-pixels',
-  USFEET: 'us-ft'
-};
-
-
-/**
- * Meters per unit lookup table.
- * @const
- * @type {Object.<ol.proj.Units, number>}
- * @api stable
- */
-ol.proj.METERS_PER_UNIT = {};
-ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] =
-    2 * Math.PI * ol.sphere.NORMAL.radius / 360;
-ol.proj.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048;
-ol.proj.METERS_PER_UNIT[ol.proj.Units.METERS] = 1;
-ol.proj.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937;
-
-
-/**
- * @classdesc
- * Projection definition class. One of these is created for each projection
- * supported in the application and stored in the {@link ol.proj} namespace.
- * You can use these in applications, but this is not required, as API params
- * and options use {@link ol.ProjectionLike} which means the simple string
- * code will suffice.
- *
- * You can use {@link ol.proj.get} to retrieve the object for a particular
- * projection.
- *
- * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together
- * with the following aliases:
- * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326,
- *     urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84,
- *     http://www.opengis.net/gml/srs/epsg.xml#4326,
- *     urn:x-ogc:def:crs:EPSG:4326
- * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913,
- *     urn:ogc:def:crs:EPSG:6.18:3:3857,
- *     http://www.opengis.net/gml/srs/epsg.xml#3857
- *
- * If you use proj4js, aliases can be added using `proj4.defs()`; see
- * [documentation](https://github.com/proj4js/proj4js). To set an alternative
- * namespace for proj4, use {@link ol.proj.setProj4}.
- *
- * @constructor
- * @param {olx.ProjectionOptions} options Projection options.
- * @struct
- * @api stable
- */
-ol.proj.Projection = function(options) {
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.code_ = options.code;
-
-  /**
-   * @private
-   * @type {ol.proj.Units}
-   */
-  this.units_ = /** @type {ol.proj.Units} */ (options.units);
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = options.extent !== undefined ? options.extent : null;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.worldExtent_ = options.worldExtent !== undefined ?
-      options.worldExtent : null;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.axisOrientation_ = options.axisOrientation !== undefined ?
-      options.axisOrientation : 'enu';
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.global_ = options.global !== undefined ? options.global : false;
-
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.canWrapX_ = !!(this.global_ && this.extent_);
-
-  /**
-  * @private
-  * @type {function(number, ol.Coordinate):number}
-  */
-  this.getPointResolutionFunc_ = options.getPointResolution !== undefined ?
-      options.getPointResolution : this.getPointResolution_;
-
-  /**
-   * @private
-   * @type {ol.tilegrid.TileGrid}
-   */
-  this.defaultTileGrid_ = null;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.metersPerUnit_ = options.metersPerUnit;
-
-  var projections = ol.proj.projections_;
-  var code = options.code;
-  ol.DEBUG && console.assert(code !== undefined,
-      'Option "code" is required for constructing instance');
-  if (ol.ENABLE_PROJ4JS) {
-    var proj4js = ol.proj.proj4_ || window['proj4'];
-    if (typeof proj4js == 'function' && projections[code] === undefined) {
-      var def = proj4js.defs(code);
-      if (def !== undefined) {
-        if (def.axis !== undefined && options.axisOrientation === undefined) {
-          this.axisOrientation_ = def.axis;
-        }
-        if (options.metersPerUnit === undefined) {
-          this.metersPerUnit_ = def.to_meter;
-        }
-        if (options.units === undefined) {
-          this.units_ = def.units;
-        }
-        var currentCode, currentDef, currentProj, proj4Transform;
-        for (currentCode in projections) {
-          currentDef = proj4js.defs(currentCode);
-          if (currentDef !== undefined) {
-            currentProj = ol.proj.get(currentCode);
-            if (currentDef === def) {
-              ol.proj.addEquivalentProjections([currentProj, this]);
-            } else {
-              proj4Transform = proj4js(currentCode, code);
-              ol.proj.addCoordinateTransforms(currentProj, this,
-                  proj4Transform.forward, proj4Transform.inverse);
-            }
-          }
-        }
-      }
-    }
-  }
-
-};
-
-
-/**
- * @return {boolean} The projection is suitable for wrapping the x-axis
- */
-ol.proj.Projection.prototype.canWrapX = function() {
-  return this.canWrapX_;
-};
-
-
-/**
- * Get the code for this projection, e.g. 'EPSG:4326'.
- * @return {string} Code.
- * @api stable
- */
-ol.proj.Projection.prototype.getCode = function() {
-  return this.code_;
-};
-
-
-/**
- * Get the validity extent for this projection.
- * @return {ol.Extent} Extent.
- * @api stable
- */
-ol.proj.Projection.prototype.getExtent = function() {
-  return this.extent_;
-};
-
-
-/**
- * Get the units of this projection.
- * @return {ol.proj.Units} Units.
- * @api stable
- */
-ol.proj.Projection.prototype.getUnits = function() {
-  return this.units_;
-};
-
-
-/**
- * Get the amount of meters per unit of this projection.  If the projection is
- * not configured with `metersPerUnit` or a units identifier, the return is
- * `undefined`.
- * @return {number|undefined} Meters.
- * @api stable
- */
-ol.proj.Projection.prototype.getMetersPerUnit = function() {
-  return this.metersPerUnit_ || ol.proj.METERS_PER_UNIT[this.units_];
-};
-
-
-/**
- * Get the world extent for this projection.
- * @return {ol.Extent} Extent.
- * @api
- */
-ol.proj.Projection.prototype.getWorldExtent = function() {
-  return this.worldExtent_;
-};
-
-
-/**
- * Get the axis orientation of this projection.
- * Example values are:
- * enu - the default easting, northing, elevation.
- * neu - northing, easting, up - useful for "lat/long" geographic coordinates,
- *     or south orientated transverse mercator.
- * wnu - westing, northing, up - some planetary coordinate systems have
- *     "west positive" coordinate systems
- * @return {string} Axis orientation.
- */
-ol.proj.Projection.prototype.getAxisOrientation = function() {
-  return this.axisOrientation_;
-};
-
-
-/**
- * Is this projection a global projection which spans the whole world?
- * @return {boolean} Whether the projection is global.
- * @api stable
- */
-ol.proj.Projection.prototype.isGlobal = function() {
-  return this.global_;
-};
-
-
-/**
-* Set if the projection is a global projection which spans the whole world
-* @param {boolean} global Whether the projection is global.
-* @api stable
-*/
-ol.proj.Projection.prototype.setGlobal = function(global) {
-  this.global_ = global;
-  this.canWrapX_ = !!(global && this.extent_);
-};
-
-
-/**
- * @return {ol.tilegrid.TileGrid} The default tile grid.
- */
-ol.proj.Projection.prototype.getDefaultTileGrid = function() {
-  return this.defaultTileGrid_;
-};
-
-
-/**
- * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid.
- */
-ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) {
-  this.defaultTileGrid_ = tileGrid;
-};
-
-
-/**
- * Set the validity extent for this projection.
- * @param {ol.Extent} extent Extent.
- * @api stable
- */
-ol.proj.Projection.prototype.setExtent = function(extent) {
-  this.extent_ = extent;
-  this.canWrapX_ = !!(this.global_ && extent);
-};
-
-
-/**
- * Set the world extent for this projection.
- * @param {ol.Extent} worldExtent World extent
- *     [minlon, minlat, maxlon, maxlat].
- * @api
- */
-ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) {
-  this.worldExtent_ = worldExtent;
-};
-
-
-/**
-* Set the getPointResolution function for this projection.
-* @param {function(number, ol.Coordinate):number} func Function
-* @api
-*/
-ol.proj.Projection.prototype.setGetPointResolution = function(func) {
-  this.getPointResolutionFunc_ = func;
-};
-
-
-/**
-* Default version.
-* Get the resolution of the point in degrees or distance units.
-* For projections with degrees as the unit this will simply return the
-* provided resolution. For other projections the point resolution is
-* estimated by transforming the 'point' pixel to EPSG:4326,
-* measuring its width and height on the normal sphere,
-* and taking the average of the width and height.
-* @param {number} resolution Nominal resolution in projection units.
-* @param {ol.Coordinate} point Point to find adjusted resolution at.
-* @return {number} Point resolution at point in projection units.
-* @private
-*/
-ol.proj.Projection.prototype.getPointResolution_ = function(resolution, point) {
-  var units = this.getUnits();
-  if (units == ol.proj.Units.DEGREES) {
-    return resolution;
-  } else {
-    // Estimate point resolution by transforming the center pixel to EPSG:4326,
-    // measuring its width and height on the normal sphere, and taking the
-    // average of the width and height.
-    var toEPSG4326 = ol.proj.getTransformFromProjections(
-        this, ol.proj.get('EPSG:4326'));
-    var vertices = [
-      point[0] - resolution / 2, point[1],
-      point[0] + resolution / 2, point[1],
-      point[0], point[1] - resolution / 2,
-      point[0], point[1] + resolution / 2
-    ];
-    vertices = toEPSG4326(vertices, vertices, 2);
-    var width = ol.sphere.NORMAL.haversineDistance(
-        vertices.slice(0, 2), vertices.slice(2, 4));
-    var height = ol.sphere.NORMAL.haversineDistance(
-        vertices.slice(4, 6), vertices.slice(6, 8));
-    var pointResolution = (width + height) / 2;
-    var metersPerUnit = this.getMetersPerUnit();
-    if (metersPerUnit !== undefined) {
-      pointResolution /= metersPerUnit;
-    }
-    return pointResolution;
-  }
-};
-
-
-/**
- * Get the resolution of the point in degrees or distance units.
- * For projections with degrees as the unit this will simply return the
- * provided resolution. The default for other projections is to estimate
- * the point resolution by transforming the 'point' pixel to EPSG:4326,
- * measuring its width and height on the normal sphere,
- * and taking the average of the width and height.
- * An alternative implementation may be given when constructing a
- * projection. For many local projections,
- * such a custom function will return the resolution unchanged.
- * @param {number} resolution Resolution in projection units.
- * @param {ol.Coordinate} point Point.
- * @return {number} Point resolution in projection units.
- * @api
- */
-ol.proj.Projection.prototype.getPointResolution = function(resolution, point) {
-  return this.getPointResolutionFunc_(resolution, point);
-};
-
-
-/**
- * @private
- * @type {Object.<string, ol.proj.Projection>}
- */
-ol.proj.projections_ = {};
-
-
-/**
- * @private
- * @type {Object.<string, Object.<string, ol.TransformFunction>>}
- */
-ol.proj.transforms_ = {};
-
-
-/**
- * @private
- * @type {proj4}
- */
-ol.proj.proj4_ = null;
-
-
-if (ol.ENABLE_PROJ4JS) {
-  /**
-   * Register proj4. If not explicitly registered, it will be assumed that
-   * proj4js will be loaded in the global namespace. For example in a
-   * browserify ES6 environment you could use:
-   *
-   *     import ol from 'openlayers';
-   *     import proj4 from 'proj4';
-   *     ol.proj.setProj4(proj4);
-   *
-   * @param {proj4} proj4 Proj4.
-   * @api
-   */
-  ol.proj.setProj4 = function(proj4) {
-    ol.DEBUG && console.assert(typeof proj4 == 'function',
-        'proj4 argument should be a function');
-    ol.proj.proj4_ = proj4;
-  };
-}
-
-
-/**
- * Registers transformation functions that don't alter coordinates. Those allow
- * to transform between projections with equal meaning.
- *
- * @param {Array.<ol.proj.Projection>} projections Projections.
- * @api
- */
-ol.proj.addEquivalentProjections = function(projections) {
-  ol.proj.addProjections(projections);
-  projections.forEach(function(source) {
-    projections.forEach(function(destination) {
-      if (source !== destination) {
-        ol.proj.addTransform(source, destination, ol.proj.cloneTransform);
-      }
-    });
-  });
-};
-
-
-/**
- * Registers transformation functions to convert coordinates in any projection
- * in projection1 to any projection in projection2.
- *
- * @param {Array.<ol.proj.Projection>} projections1 Projections with equal
- *     meaning.
- * @param {Array.<ol.proj.Projection>} projections2 Projections with equal
- *     meaning.
- * @param {ol.TransformFunction} forwardTransform Transformation from any
- *   projection in projection1 to any projection in projection2.
- * @param {ol.TransformFunction} inverseTransform Transform from any projection
- *   in projection2 to any projection in projection1..
- */
-ol.proj.addEquivalentTransforms = function(projections1, projections2, forwardTransform, inverseTransform) {
-  projections1.forEach(function(projection1) {
-    projections2.forEach(function(projection2) {
-      ol.proj.addTransform(projection1, projection2, forwardTransform);
-      ol.proj.addTransform(projection2, projection1, inverseTransform);
-    });
-  });
-};
-
-
-/**
- * Add a Projection object to the list of supported projections that can be
- * looked up by their code.
- *
- * @param {ol.proj.Projection} projection Projection instance.
- * @api stable
- */
-ol.proj.addProjection = function(projection) {
-  ol.proj.projections_[projection.getCode()] = projection;
-  ol.proj.addTransform(projection, projection, ol.proj.cloneTransform);
-};
-
-
-/**
- * @param {Array.<ol.proj.Projection>} projections Projections.
- */
-ol.proj.addProjections = function(projections) {
-  var addedProjections = [];
-  projections.forEach(function(projection) {
-    addedProjections.push(ol.proj.addProjection(projection));
-  });
-};
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.proj.clearAllProjections = function() {
-  ol.proj.projections_ = {};
-  ol.proj.transforms_ = {};
-};
-
-
-/**
- * @param {ol.proj.Projection|string|undefined} projection Projection.
- * @param {string} defaultCode Default code.
- * @return {ol.proj.Projection} Projection.
- */
-ol.proj.createProjection = function(projection, defaultCode) {
-  if (!projection) {
-    return ol.proj.get(defaultCode);
-  } else if (typeof projection === 'string') {
-    return ol.proj.get(projection);
-  } else {
-    return /** @type {ol.proj.Projection} */ (projection);
-  }
-};
-
-
-/**
- * Registers a conversion function to convert coordinates from the source
- * projection to the destination projection.
- *
- * @param {ol.proj.Projection} source Source.
- * @param {ol.proj.Projection} destination Destination.
- * @param {ol.TransformFunction} transformFn Transform.
- */
-ol.proj.addTransform = function(source, destination, transformFn) {
-  var sourceCode = source.getCode();
-  var destinationCode = destination.getCode();
-  var transforms = ol.proj.transforms_;
-  if (!(sourceCode in transforms)) {
-    transforms[sourceCode] = {};
-  }
-  transforms[sourceCode][destinationCode] = transformFn;
-};
-
-
-/**
- * Registers coordinate transform functions to convert coordinates between the
- * source projection and the destination projection.
- * The forward and inverse functions convert coordinate pairs; this function
- * converts these into the functions used internally which also handle
- * extents and coordinate arrays.
- *
- * @param {ol.ProjectionLike} source Source projection.
- * @param {ol.ProjectionLike} destination Destination projection.
- * @param {function(ol.Coordinate): ol.Coordinate} forward The forward transform
- *     function (that is, from the source projection to the destination
- *     projection) that takes a {@link ol.Coordinate} as argument and returns
- *     the transformed {@link ol.Coordinate}.
- * @param {function(ol.Coordinate): ol.Coordinate} inverse The inverse transform
- *     function (that is, from the destination projection to the source
- *     projection) that takes a {@link ol.Coordinate} as argument and returns
- *     the transformed {@link ol.Coordinate}.
- * @api stable
- */
-ol.proj.addCoordinateTransforms = function(source, destination, forward, inverse) {
-  var sourceProj = ol.proj.get(source);
-  var destProj = ol.proj.get(destination);
-  ol.proj.addTransform(sourceProj, destProj,
-      ol.proj.createTransformFromCoordinateTransform(forward));
-  ol.proj.addTransform(destProj, sourceProj,
-      ol.proj.createTransformFromCoordinateTransform(inverse));
-};
-
-
-/**
- * Creates a {@link ol.TransformFunction} from a simple 2D coordinate transform
- * function.
- * @param {function(ol.Coordinate): ol.Coordinate} transform Coordinate
- *     transform.
- * @return {ol.TransformFunction} Transform function.
- */
-ol.proj.createTransformFromCoordinateTransform = function(transform) {
-  return (
-      /**
-       * @param {Array.<number>} input Input.
-       * @param {Array.<number>=} opt_output Output.
-       * @param {number=} opt_dimension Dimension.
-       * @return {Array.<number>} Output.
-       */
-      function(input, opt_output, opt_dimension) {
-        var length = input.length;
-        var dimension = opt_dimension !== undefined ? opt_dimension : 2;
-        var output = opt_output !== undefined ? opt_output : new Array(length);
-        var point, i, j;
-        for (i = 0; i < length; i += dimension) {
-          point = transform([input[i], input[i + 1]]);
-          output[i] = point[0];
-          output[i + 1] = point[1];
-          for (j = dimension - 1; j >= 2; --j) {
-            output[i + j] = input[i + j];
-          }
-        }
-        return output;
-      });
-};
-
-
-/**
- * Unregisters the conversion function to convert coordinates from the source
- * projection to the destination projection.  This method is used to clean up
- * cached transforms during testing.
- *
- * @param {ol.proj.Projection} source Source projection.
- * @param {ol.proj.Projection} destination Destination projection.
- * @return {ol.TransformFunction} transformFn The unregistered transform.
- */
-ol.proj.removeTransform = function(source, destination) {
-  var sourceCode = source.getCode();
-  var destinationCode = destination.getCode();
-  var transforms = ol.proj.transforms_;
-  ol.DEBUG && console.assert(sourceCode in transforms,
-      'sourceCode should be in transforms');
-  ol.DEBUG && console.assert(destinationCode in transforms[sourceCode],
-      'destinationCode should be in transforms of sourceCode');
-  var transform = transforms[sourceCode][destinationCode];
-  delete transforms[sourceCode][destinationCode];
-  if (ol.obj.isEmpty(transforms[sourceCode])) {
-    delete transforms[sourceCode];
-  }
-  return transform;
-};
-
-
-/**
- * Transforms a coordinate from longitude/latitude to a different projection.
- * @param {ol.Coordinate} coordinate Coordinate as longitude and latitude, i.e.
- *     an array with longitude as 1st and latitude as 2nd element.
- * @param {ol.ProjectionLike=} opt_projection Target projection. The
- *     default is Web Mercator, i.e. 'EPSG:3857'.
- * @return {ol.Coordinate} Coordinate projected to the target projection.
- * @api stable
- */
-ol.proj.fromLonLat = function(coordinate, opt_projection) {
-  return ol.proj.transform(coordinate, 'EPSG:4326',
-      opt_projection !== undefined ? opt_projection : 'EPSG:3857');
-};
-
-
-/**
- * Transforms a coordinate to longitude/latitude.
- * @param {ol.Coordinate} coordinate Projected coordinate.
- * @param {ol.ProjectionLike=} opt_projection Projection of the coordinate.
- *     The default is Web Mercator, i.e. 'EPSG:3857'.
- * @return {ol.Coordinate} Coordinate as longitude and latitude, i.e. an array
- *     with longitude as 1st and latitude as 2nd element.
- * @api stable
- */
-ol.proj.toLonLat = function(coordinate, opt_projection) {
-  return ol.proj.transform(coordinate,
-      opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326');
-};
-
-
-/**
- * Fetches a Projection object for the code specified.
- *
- * @param {ol.ProjectionLike} projectionLike Either a code string which is
- *     a combination of authority and identifier such as "EPSG:4326", or an
- *     existing projection object, or undefined.
- * @return {ol.proj.Projection} Projection object, or null if not in list.
- * @api stable
- */
-ol.proj.get = function(projectionLike) {
-  var projection;
-  if (projectionLike instanceof ol.proj.Projection) {
-    projection = projectionLike;
-  } else if (typeof projectionLike === 'string') {
-    var code = projectionLike;
-    projection = ol.proj.projections_[code];
-    if (ol.ENABLE_PROJ4JS) {
-      var proj4js = ol.proj.proj4_ || window['proj4'];
-      if (projection === undefined && typeof proj4js == 'function' &&
-          proj4js.defs(code) !== undefined) {
-        projection = new ol.proj.Projection({code: code});
-        ol.proj.addProjection(projection);
-      }
-    }
-  }
-  return projection || null;
-};
-
-
-/**
- * Checks if two projections are the same, that is every coordinate in one
- * projection does represent the same geographic point as the same coordinate in
- * the other projection.
- *
- * @param {ol.proj.Projection} projection1 Projection 1.
- * @param {ol.proj.Projection} projection2 Projection 2.
- * @return {boolean} Equivalent.
- * @api
- */
-ol.proj.equivalent = function(projection1, projection2) {
-  if (projection1 === projection2) {
-    return true;
-  }
-  var equalUnits = projection1.getUnits() === projection2.getUnits();
-  if (projection1.getCode() === projection2.getCode()) {
-    return equalUnits;
-  } else {
-    var transformFn = ol.proj.getTransformFromProjections(
-        projection1, projection2);
-    return transformFn === ol.proj.cloneTransform && equalUnits;
-  }
-};
-
-
-/**
- * Given the projection-like objects, searches for a transformation
- * function to convert a coordinates array from the source projection to the
- * destination projection.
- *
- * @param {ol.ProjectionLike} source Source.
- * @param {ol.ProjectionLike} destination Destination.
- * @return {ol.TransformFunction} Transform function.
- * @api stable
- */
-ol.proj.getTransform = function(source, destination) {
-  var sourceProjection = ol.proj.get(source);
-  var destinationProjection = ol.proj.get(destination);
-  return ol.proj.getTransformFromProjections(
-      sourceProjection, destinationProjection);
-};
-
-
-/**
- * Searches in the list of transform functions for the function for converting
- * coordinates from the source projection to the destination projection.
- *
- * @param {ol.proj.Projection} sourceProjection Source Projection object.
- * @param {ol.proj.Projection} destinationProjection Destination Projection
- *     object.
- * @return {ol.TransformFunction} Transform function.
- */
-ol.proj.getTransformFromProjections = function(sourceProjection, destinationProjection) {
-  var transforms = ol.proj.transforms_;
-  var sourceCode = sourceProjection.getCode();
-  var destinationCode = destinationProjection.getCode();
-  var transform;
-  if (sourceCode in transforms && destinationCode in transforms[sourceCode]) {
-    transform = transforms[sourceCode][destinationCode];
-  }
-  if (transform === undefined) {
-    ol.DEBUG && console.assert(transform !== undefined, 'transform should be defined');
-    transform = ol.proj.identityTransform;
-  }
-  return transform;
-};
-
-
-/**
- * @param {Array.<number>} input Input coordinate array.
- * @param {Array.<number>=} opt_output Output array of coordinate values.
- * @param {number=} opt_dimension Dimension.
- * @return {Array.<number>} Input coordinate array (same array as input).
- */
-ol.proj.identityTransform = function(input, opt_output, opt_dimension) {
-  if (opt_output !== undefined && input !== opt_output) {
-    // TODO: consider making this a warning instead
-    ol.DEBUG && console.assert(false, 'This should not be used internally.');
-    for (var i = 0, ii = input.length; i < ii; ++i) {
-      opt_output[i] = input[i];
-    }
-    input = opt_output;
-  }
-  return input;
-};
-
-
-/**
- * @param {Array.<number>} input Input coordinate array.
- * @param {Array.<number>=} opt_output Output array of coordinate values.
- * @param {number=} opt_dimension Dimension.
- * @return {Array.<number>} Output coordinate array (new array, same coordinate
- *     values).
- */
-ol.proj.cloneTransform = function(input, opt_output, opt_dimension) {
-  var output;
-  if (opt_output !== undefined) {
-    for (var i = 0, ii = input.length; i < ii; ++i) {
-      opt_output[i] = input[i];
-    }
-    output = opt_output;
-  } else {
-    output = input.slice();
-  }
-  return output;
-};
-
-
-/**
- * Transforms a coordinate from source projection to destination projection.
- * This returns a new coordinate (and does not modify the original).
- *
- * See {@link ol.proj.transformExtent} for extent transformation.
- * See the transform method of {@link ol.geom.Geometry} and its subclasses for
- * geometry transforms.
- *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.ProjectionLike} source Source projection-like.
- * @param {ol.ProjectionLike} destination Destination projection-like.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
- */
-ol.proj.transform = function(coordinate, source, destination) {
-  var transformFn = ol.proj.getTransform(source, destination);
-  return transformFn(coordinate, undefined, coordinate.length);
-};
-
-
-/**
- * Transforms an extent from source projection to destination projection.  This
- * returns a new extent (and does not modify the original).
- *
- * @param {ol.Extent} extent The extent to transform.
- * @param {ol.ProjectionLike} source Source projection-like.
- * @param {ol.ProjectionLike} destination Destination projection-like.
- * @return {ol.Extent} The transformed extent.
- * @api stable
- */
-ol.proj.transformExtent = function(extent, source, destination) {
-  var transformFn = ol.proj.getTransform(source, destination);
-  return ol.extent.applyTransform(extent, transformFn);
-};
-
-
-/**
- * Transforms the given point to the destination projection.
- *
- * @param {ol.Coordinate} point Point.
- * @param {ol.proj.Projection} sourceProjection Source projection.
- * @param {ol.proj.Projection} destinationProjection Destination projection.
- * @return {ol.Coordinate} Point.
- */
-ol.proj.transformWithProjections = function(point, sourceProjection, destinationProjection) {
-  var transformFn = ol.proj.getTransformFromProjections(
-      sourceProjection, destinationProjection);
-  return transformFn(point);
-};
-
-goog.provide('ol.geom.Geometry');
-goog.provide('ol.geom.GeometryLayout');
-goog.provide('ol.geom.GeometryType');
-
-goog.require('ol');
-goog.require('ol.functions');
-goog.require('ol.Object');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.proj.Units');
-
-
-/**
- * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
- * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
- * `'GeometryCollection'`, `'Circle'`.
- * @enum {string}
- */
-ol.geom.GeometryType = {
-  POINT: 'Point',
-  LINE_STRING: 'LineString',
-  LINEAR_RING: 'LinearRing',
-  POLYGON: 'Polygon',
-  MULTI_POINT: 'MultiPoint',
-  MULTI_LINE_STRING: 'MultiLineString',
-  MULTI_POLYGON: 'MultiPolygon',
-  GEOMETRY_COLLECTION: 'GeometryCollection',
-  CIRCLE: 'Circle'
-};
-
-
-/**
- * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z')
- * or measure ('M') coordinate is available. Supported values are `'XY'`,
- * `'XYZ'`, `'XYM'`, `'XYZM'`.
- * @enum {string}
- */
-ol.geom.GeometryLayout = {
-  XY: 'XY',
-  XYZ: 'XYZ',
-  XYM: 'XYM',
-  XYZM: 'XYZM'
-};
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for vector geometries.
- *
- * To get notified of changes to the geometry, register a listener for the
- * generic `change` event on your geometry instance.
- *
- * @constructor
- * @extends {ol.Object}
- * @api stable
- */
-ol.geom.Geometry = function() {
-
-  ol.Object.call(this);
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = ol.extent.createEmpty();
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.extentRevision_ = -1;
-
-  /**
-   * @protected
-   * @type {Object.<string, ol.geom.Geometry>}
-   */
-  this.simplifiedGeometryCache = {};
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.simplifiedGeometryMaxMinSquaredTolerance = 0;
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.simplifiedGeometryRevision = 0;
-
-};
-ol.inherits(ol.geom.Geometry, ol.Object);
-
-
-/**
- * Make a complete copy of the geometry.
- * @abstract
- * @return {!ol.geom.Geometry} Clone.
- */
-ol.geom.Geometry.prototype.clone = function() {};
-
-
-/**
- * @abstract
- * @param {number} x X.
- * @param {number} y Y.
- * @param {ol.Coordinate} closestPoint Closest point.
- * @param {number} minSquaredDistance Minimum squared distance.
- * @return {number} Minimum squared distance.
- */
-ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {};
-
-
-/**
- * Return the closest point of the geometry to the passed point as
- * {@link ol.Coordinate coordinate}.
- * @param {ol.Coordinate} point Point.
- * @param {ol.Coordinate=} opt_closestPoint Closest point.
- * @return {ol.Coordinate} Closest point.
- * @api stable
- */
-ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) {
-  var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN];
-  this.closestPointXY(point[0], point[1], closestPoint, Infinity);
-  return closestPoint;
-};
-
-
-/**
- * Returns true if this geometry includes the specified coordinate. If the
- * coordinate is on the boundary of the geometry, returns false.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {boolean} Contains coordinate.
- * @api
- */
-ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) {
-  return this.containsXY(coordinate[0], coordinate[1]);
-};
-
-
-/**
- * @abstract
- * @param {ol.Extent} extent Extent.
- * @protected
- * @return {ol.Extent} extent Extent.
- */
-ol.geom.Geometry.prototype.computeExtent = function(extent) {};
-
-
-/**
- * @param {number} x X.
- * @param {number} y Y.
- * @return {boolean} Contains (x, y).
- */
-ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE;
-
-
-/**
- * Get the extent of the geometry.
- * @param {ol.Extent=} opt_extent Extent.
- * @return {ol.Extent} extent Extent.
- * @api stable
- */
-ol.geom.Geometry.prototype.getExtent = function(opt_extent) {
-  if (this.extentRevision_ != this.getRevision()) {
-    this.extent_ = this.computeExtent(this.extent_);
-    this.extentRevision_ = this.getRevision();
-  }
-  return ol.extent.returnOrUpdate(this.extent_, opt_extent);
-};
-
-
-/**
- * Rotate the geometry around a given coordinate. This modifies the geometry
- * coordinates in place.
- * @abstract
- * @param {number} angle Rotation angle in radians.
- * @param {ol.Coordinate} anchor The rotation center.
- * @api
- */
-ol.geom.Geometry.prototype.rotate = function(angle, anchor) {};
-
-
-/**
- * Scale the geometry (with an optional origin).  This modifies the geometry
- * coordinates in place.
- * @abstract
- * @param {number} sx The scaling factor in the x-direction.
- * @param {number=} opt_sy The scaling factor in the y-direction (defaults to
- *     sx).
- * @param {ol.Coordinate=} opt_anchor The scale origin (defaults to the center
- *     of the geometry extent).
- * @api
- */
-ol.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {};
-
-
-/**
- * Create a simplified version of this geometry.  For linestrings, this uses
- * the the {@link
- * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
- * Douglas Peucker} algorithm.  For polygons, a quantization-based
- * simplification is used to preserve topology.
- * @function
- * @param {number} tolerance The tolerance distance for simplification.
- * @return {ol.geom.Geometry} A new, simplified version of the original
- *     geometry.
- * @api
- */
-ol.geom.Geometry.prototype.simplify = function(tolerance) {
-  return this.getSimplifiedGeometry(tolerance * tolerance);
-};
-
-
-/**
- * Create a simplified version of this geometry using the Douglas Peucker
- * algorithm.
- * @see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
- * @abstract
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.Geometry} Simplified geometry.
- */
-ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {};
-
-
-/**
- * Get the type of this geometry.
- * @abstract
- * @return {ol.geom.GeometryType} Geometry type.
- */
-ol.geom.Geometry.prototype.getType = function() {};
-
-
-/**
- * Apply a transform function to each coordinate of the geometry.
- * The geometry is modified in place.
- * If you do not want the geometry modified in place, first `clone()` it and
- * then use this function on the clone.
- * @abstract
- * @param {ol.TransformFunction} transformFn Transform.
- */
-ol.geom.Geometry.prototype.applyTransform = function(transformFn) {};
-
-
-/**
- * Test if the geometry and the passed extent intersect.
- * @abstract
- * @param {ol.Extent} extent Extent.
- * @return {boolean} `true` if the geometry and the extent intersect.
- */
-ol.geom.Geometry.prototype.intersectsExtent = function(extent) {};
-
-
-/**
- * Translate the geometry.  This modifies the geometry coordinates in place.  If
- * instead you want a new geometry, first `clone()` this geometry.
- * @abstract
- * @param {number} deltaX Delta X.
- * @param {number} deltaY Delta Y.
- */
-ol.geom.Geometry.prototype.translate = function(deltaX, deltaY) {};
-
-
-/**
- * Transform each coordinate of the geometry from one coordinate reference
- * system to another. The geometry is modified in place.
- * For example, a line will be transformed to a line and a circle to a circle.
- * If you do not want the geometry modified in place, first `clone()` it and
- * then use this function on the clone.
- *
- * @param {ol.ProjectionLike} source The current projection.  Can be a
- *     string identifier or a {@link ol.proj.Projection} object.
- * @param {ol.ProjectionLike} destination The desired projection.  Can be a
- *     string identifier or a {@link ol.proj.Projection} object.
- * @return {ol.geom.Geometry} This geometry.  Note that original geometry is
- *     modified in place.
- * @api stable
- */
-ol.geom.Geometry.prototype.transform = function(source, destination) {
-  ol.DEBUG && console.assert(
-      ol.proj.get(source).getUnits() !== ol.proj.Units.TILE_PIXELS &&
-      ol.proj.get(destination).getUnits() !== ol.proj.Units.TILE_PIXELS,
-      'cannot transform geometries with TILE_PIXELS units');
-  this.applyTransform(ol.proj.getTransform(source, destination));
-  return this;
-};
-
-goog.provide('ol.geom.flat.transform');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.Transform} transform Transform.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed coordinates.
- */
-ol.geom.flat.transform.transform2D = function(flatCoordinates, offset, end, stride, transform, opt_dest) {
-  var dest = opt_dest ? opt_dest : [];
-  var i = 0;
-  var j;
-  for (j = offset; j < end; j += stride) {
-    var x = flatCoordinates[j];
-    var y = flatCoordinates[j + 1];
-    dest[i++] = transform[0] * x + transform[2] * y + transform[4];
-    dest[i++] = transform[1] * x + transform[3] * y + transform[5];
-  }
-  if (opt_dest && dest.length != i) {
-    dest.length = i;
-  }
-  return dest;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} angle Angle.
- * @param {Array.<number>} anchor Rotation anchor point.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed coordinates.
- */
-ol.geom.flat.transform.rotate = function(flatCoordinates, offset, end, stride, angle, anchor, opt_dest) {
-  var dest = opt_dest ? opt_dest : [];
-  var cos = Math.cos(angle);
-  var sin = Math.sin(angle);
-  var anchorX = anchor[0];
-  var anchorY = anchor[1];
-  var i = 0;
-  for (var j = offset; j < end; j += stride) {
-    var deltaX = flatCoordinates[j] - anchorX;
-    var deltaY = flatCoordinates[j + 1] - anchorY;
-    dest[i++] = anchorX + deltaX * cos - deltaY * sin;
-    dest[i++] = anchorY + deltaX * sin + deltaY * cos;
-    for (var k = j + 2; k < j + stride; ++k) {
-      dest[i++] = flatCoordinates[k];
-    }
-  }
-  if (opt_dest && dest.length != i) {
-    dest.length = i;
-  }
-  return dest;
-};
-
-
-/**
- * Scale the coordinates.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} sx Scale factor in the x-direction.
- * @param {number} sy Scale factor in the y-direction.
- * @param {Array.<number>} anchor Scale anchor point.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed coordinates.
- */
-ol.geom.flat.transform.scale = function(flatCoordinates, offset, end, stride, sx, sy, anchor, opt_dest) {
-  var dest = opt_dest ? opt_dest : [];
-  var anchorX = anchor[0];
-  var anchorY = anchor[1];
-  var i = 0;
-  for (var j = offset; j < end; j += stride) {
-    var deltaX = flatCoordinates[j] - anchorX;
-    var deltaY = flatCoordinates[j + 1] - anchorY;
-    dest[i++] = anchorX + sx * deltaX;
-    dest[i++] = anchorY + sy * deltaY;
-    for (var k = j + 2; k < j + stride; ++k) {
-      dest[i++] = flatCoordinates[k];
-    }
-  }
-  if (opt_dest && dest.length != i) {
-    dest.length = i;
-  }
-  return dest;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} deltaX Delta X.
- * @param {number} deltaY Delta Y.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed coordinates.
- */
-ol.geom.flat.transform.translate = function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) {
-  var dest = opt_dest ? opt_dest : [];
-  var i = 0;
-  var j, k;
-  for (j = offset; j < end; j += stride) {
-    dest[i++] = flatCoordinates[j] + deltaX;
-    dest[i++] = flatCoordinates[j + 1] + deltaY;
-    for (k = j + 2; k < j + stride; ++k) {
-      dest[i++] = flatCoordinates[k];
-    }
-  }
-  if (opt_dest && dest.length != i) {
-    dest.length = i;
-  }
-  return dest;
-};
-
-goog.provide('ol.geom.SimpleGeometry');
-
-goog.require('ol');
-goog.require('ol.functions');
-goog.require('ol.extent');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.flat.transform');
-goog.require('ol.obj');
-
-
-/**
- * @classdesc
- * Abstract base class; only used for creating subclasses; do not instantiate
- * in apps, as cannot be rendered.
- *
- * @constructor
- * @extends {ol.geom.Geometry}
- * @api stable
- */
-ol.geom.SimpleGeometry = function() {
-
-  ol.geom.Geometry.call(this);
-
-  /**
-   * @protected
-   * @type {ol.geom.GeometryLayout}
-   */
-  this.layout = ol.geom.GeometryLayout.XY;
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.stride = 2;
-
-  /**
-   * @protected
-   * @type {Array.<number>}
-   */
-  this.flatCoordinates = null;
-
-};
-ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
-
-
-/**
- * @param {number} stride Stride.
- * @private
- * @return {ol.geom.GeometryLayout} layout Layout.
- */
-ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) {
-  var layout;
-  if (stride == 2) {
-    layout = ol.geom.GeometryLayout.XY;
-  } else if (stride == 3) {
-    layout = ol.geom.GeometryLayout.XYZ;
-  } else if (stride == 4) {
-    layout = ol.geom.GeometryLayout.XYZM;
-  }
-  ol.DEBUG && console.assert(layout, 'unsupported stride: ' + stride);
-  return /** @type {ol.geom.GeometryLayout} */ (layout);
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @return {number} Stride.
- */
-ol.geom.SimpleGeometry.getStrideForLayout = function(layout) {
-  var stride;
-  if (layout == ol.geom.GeometryLayout.XY) {
-    stride = 2;
-  } else if (layout == ol.geom.GeometryLayout.XYZ || layout == ol.geom.GeometryLayout.XYM) {
-    stride = 3;
-  } else if (layout == ol.geom.GeometryLayout.XYZM) {
-    stride = 4;
-  }
-  ol.DEBUG && console.assert(stride, 'unsupported layout: ' + layout);
-  return /** @type {number} */ (stride);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE;
-
-
-/**
- * @inheritDoc
- */
-ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
-  return ol.extent.createOrUpdateFromFlatCoordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      extent);
-};
-
-
-/**
- * @abstract
- * @return {Array} Coordinates.
- */
-ol.geom.SimpleGeometry.prototype.getCoordinates = function() {};
-
-
-/**
- * Return the first coordinate of the geometry.
- * @return {ol.Coordinate} First coordinate.
- * @api stable
- */
-ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
-  return this.flatCoordinates.slice(0, this.stride);
-};
-
-
-/**
- * @return {Array.<number>} Flat coordinates.
- */
-ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
-  return this.flatCoordinates;
-};
-
-
-/**
- * Return the last coordinate of the geometry.
- * @return {ol.Coordinate} Last point.
- * @api stable
- */
-ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
-  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
-};
-
-
-/**
- * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
- * @return {ol.geom.GeometryLayout} Layout.
- * @api stable
- */
-ol.geom.SimpleGeometry.prototype.getLayout = function() {
-  return this.layout;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {
-  if (this.simplifiedGeometryRevision != this.getRevision()) {
-    ol.obj.clear(this.simplifiedGeometryCache);
-    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
-    this.simplifiedGeometryRevision = this.getRevision();
-  }
-  // If squaredTolerance is negative or if we know that simplification will not
-  // have any effect then just return this.
-  if (squaredTolerance < 0 ||
-      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
-       squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) {
-    return this;
-  }
-  var key = squaredTolerance.toString();
-  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
-    return this.simplifiedGeometryCache[key];
-  } else {
-    var simplifiedGeometry =
-        this.getSimplifiedGeometryInternal(squaredTolerance);
-    var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates();
-    if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) {
-      this.simplifiedGeometryCache[key] = simplifiedGeometry;
-      return simplifiedGeometry;
-    } else {
-      // Simplification did not actually remove any coordinates.  We now know
-      // that any calls to getSimplifiedGeometry with a squaredTolerance less
-      // than or equal to the current squaredTolerance will also not have any
-      // effect.  This allows us to short circuit simplification (saving CPU
-      // cycles) and prevents the cache of simplified geometries from filling
-      // up with useless identical copies of this geometry (saving memory).
-      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
-      return this;
-    }
-  }
-};
-
-
-/**
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.SimpleGeometry} Simplified geometry.
- * @protected
- */
-ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
-  return this;
-};
-
-
-/**
- * @return {number} Stride.
- */
-ol.geom.SimpleGeometry.prototype.getStride = function() {
-  return this.stride;
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @protected
- */
-ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) {
-  this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
-  this.layout = layout;
-  this.flatCoordinates = flatCoordinates;
-};
-
-
-/**
- * @abstract
- * @param {Array} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- */
-ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {};
-
-
-/**
- * @param {ol.geom.GeometryLayout|undefined} layout Layout.
- * @param {Array} coordinates Coordinates.
- * @param {number} nesting Nesting.
- * @protected
- */
-ol.geom.SimpleGeometry.prototype.setLayout = function(layout, coordinates, nesting) {
-  /** @type {number} */
-  var stride;
-  if (layout) {
-    stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
-  } else {
-    var i;
-    for (i = 0; i < nesting; ++i) {
-      if (coordinates.length === 0) {
-        this.layout = ol.geom.GeometryLayout.XY;
-        this.stride = 2;
-        return;
-      } else {
-        coordinates = /** @type {Array} */ (coordinates[0]);
-      }
-    }
-    stride = coordinates.length;
-    layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride);
-  }
-  this.layout = layout;
-  this.stride = stride;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
-  if (this.flatCoordinates) {
-    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
-    this.changed();
-  }
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.geom.SimpleGeometry.prototype.rotate = function(angle, anchor) {
-  var flatCoordinates = this.getFlatCoordinates();
-  if (flatCoordinates) {
-    var stride = this.getStride();
-    ol.geom.flat.transform.rotate(
-        flatCoordinates, 0, flatCoordinates.length,
-        stride, angle, anchor, flatCoordinates);
-    this.changed();
-  }
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.geom.SimpleGeometry.prototype.scale = function(sx, opt_sy, opt_anchor) {
-  var sy = opt_sy;
-  if (sy === undefined) {
-    sy = sx;
-  }
-  var anchor = opt_anchor;
-  if (!anchor) {
-    anchor = ol.extent.getCenter(this.getExtent());
-  }
-  var flatCoordinates = this.getFlatCoordinates();
-  if (flatCoordinates) {
-    var stride = this.getStride();
-    ol.geom.flat.transform.scale(
-        flatCoordinates, 0, flatCoordinates.length,
-        stride, sx, sy, anchor, flatCoordinates);
-    this.changed();
-  }
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) {
-  var flatCoordinates = this.getFlatCoordinates();
-  if (flatCoordinates) {
-    var stride = this.getStride();
-    ol.geom.flat.transform.translate(
-        flatCoordinates, 0, flatCoordinates.length, stride,
-        deltaX, deltaY, flatCoordinates);
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
- * @param {ol.Transform} transform Transform.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Transformed flat coordinates.
- */
-ol.geom.SimpleGeometry.transform2D = function(simpleGeometry, transform, opt_dest) {
-  var flatCoordinates = simpleGeometry.getFlatCoordinates();
-  if (!flatCoordinates) {
-    return null;
-  } else {
-    var stride = simpleGeometry.getStride();
-    return ol.geom.flat.transform.transform2D(
-        flatCoordinates, 0, flatCoordinates.length, stride,
-        transform, opt_dest);
-  }
-};
-
-goog.provide('ol.geom.flat.area');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {number} Area.
- */
-ol.geom.flat.area.linearRing = function(flatCoordinates, offset, end, stride) {
-  var twiceArea = 0;
-  var x1 = flatCoordinates[end - stride];
-  var y1 = flatCoordinates[end - stride + 1];
-  for (; offset < end; offset += stride) {
-    var x2 = flatCoordinates[offset];
-    var y2 = flatCoordinates[offset + 1];
-    twiceArea += y1 * x2 - x1 * y2;
-    x1 = x2;
-    y1 = y2;
-  }
-  return twiceArea / 2;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @return {number} Area.
- */
-ol.geom.flat.area.linearRings = function(flatCoordinates, offset, ends, stride) {
-  var area = 0;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride);
-    offset = end;
-  }
-  return area;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @return {number} Area.
- */
-ol.geom.flat.area.linearRingss = function(flatCoordinates, offset, endss, stride) {
-  var area = 0;
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    area +=
-        ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride);
-    offset = ends[ends.length - 1];
-  }
-  return area;
-};
-
-goog.provide('ol.geom.flat.closest');
-
-goog.require('ol');
-goog.require('ol.math');
-
-
-/**
- * Returns the point on the 2D line segment flatCoordinates[offset1] to
- * flatCoordinates[offset2] that is closest to the point (x, y).  Extra
- * dimensions are linearly interpolated.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset1 Offset 1.
- * @param {number} offset2 Offset 2.
- * @param {number} stride Stride.
- * @param {number} x X.
- * @param {number} y Y.
- * @param {Array.<number>} closestPoint Closest point.
- */
-ol.geom.flat.closest.point = function(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) {
-  var x1 = flatCoordinates[offset1];
-  var y1 = flatCoordinates[offset1 + 1];
-  var dx = flatCoordinates[offset2] - x1;
-  var dy = flatCoordinates[offset2 + 1] - y1;
-  var i, offset;
-  if (dx === 0 && dy === 0) {
-    offset = offset1;
-  } else {
-    var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
-    if (t > 1) {
-      offset = offset2;
-    } else if (t > 0) {
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = ol.math.lerp(flatCoordinates[offset1 + i],
-            flatCoordinates[offset2 + i], t);
-      }
-      closestPoint.length = stride;
-      return;
-    } else {
-      offset = offset1;
-    }
-  }
-  for (i = 0; i < stride; ++i) {
-    closestPoint[i] = flatCoordinates[offset + i];
-  }
-  closestPoint.length = stride;
-};
-
-
-/**
- * Return the squared of the largest distance between any pair of consecutive
- * coordinates.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} maxSquaredDelta Max squared delta.
- * @return {number} Max squared delta.
- */
-ol.geom.flat.closest.getMaxSquaredDelta = function(flatCoordinates, offset, end, stride, maxSquaredDelta) {
-  var x1 = flatCoordinates[offset];
-  var y1 = flatCoordinates[offset + 1];
-  for (offset += stride; offset < end; offset += stride) {
-    var x2 = flatCoordinates[offset];
-    var y2 = flatCoordinates[offset + 1];
-    var squaredDelta = ol.math.squaredDistance(x1, y1, x2, y2);
-    if (squaredDelta > maxSquaredDelta) {
-      maxSquaredDelta = squaredDelta;
-    }
-    x1 = x2;
-    y1 = y2;
-  }
-  return maxSquaredDelta;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {number} maxSquaredDelta Max squared delta.
- * @return {number} Max squared delta.
- */
-ol.geom.flat.closest.getsMaxSquaredDelta = function(flatCoordinates, offset, ends, stride, maxSquaredDelta) {
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    maxSquaredDelta = ol.geom.flat.closest.getMaxSquaredDelta(
-        flatCoordinates, offset, end, stride, maxSquaredDelta);
-    offset = end;
-  }
-  return maxSquaredDelta;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {number} maxSquaredDelta Max squared delta.
- * @return {number} Max squared delta.
- */
-ol.geom.flat.closest.getssMaxSquaredDelta = function(flatCoordinates, offset, endss, stride, maxSquaredDelta) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    maxSquaredDelta = ol.geom.flat.closest.getsMaxSquaredDelta(
-        flatCoordinates, offset, ends, stride, maxSquaredDelta);
-    offset = ends[ends.length - 1];
-  }
-  return maxSquaredDelta;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} maxDelta Max delta.
- * @param {boolean} isRing Is ring.
- * @param {number} x X.
- * @param {number} y Y.
- * @param {Array.<number>} closestPoint Closest point.
- * @param {number} minSquaredDistance Minimum squared distance.
- * @param {Array.<number>=} opt_tmpPoint Temporary point object.
- * @return {number} Minimum squared distance.
- */
-ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end,
-    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
-    opt_tmpPoint) {
-  if (offset == end) {
-    return minSquaredDistance;
-  }
-  var i, squaredDistance;
-  if (maxDelta === 0) {
-    // All points are identical, so just test the first point.
-    squaredDistance = ol.math.squaredDistance(
-        x, y, flatCoordinates[offset], flatCoordinates[offset + 1]);
-    if (squaredDistance < minSquaredDistance) {
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = flatCoordinates[offset + i];
-      }
-      closestPoint.length = stride;
-      return squaredDistance;
-    } else {
-      return minSquaredDistance;
-    }
-  }
-  ol.DEBUG && console.assert(maxDelta > 0, 'maxDelta should be larger than 0');
-  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
-  var index = offset + stride;
-  while (index < end) {
-    ol.geom.flat.closest.point(
-        flatCoordinates, index - stride, index, stride, x, y, tmpPoint);
-    squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]);
-    if (squaredDistance < minSquaredDistance) {
-      minSquaredDistance = squaredDistance;
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = tmpPoint[i];
-      }
-      closestPoint.length = stride;
-      index += stride;
-    } else {
-      // Skip ahead multiple points, because we know that all the skipped
-      // points cannot be any closer than the closest point we have found so
-      // far.  We know this because we know how close the current point is, how
-      // close the closest point we have found so far is, and the maximum
-      // distance between consecutive points.  For example, if we're currently
-      // at distance 10, the best we've found so far is 3, and that the maximum
-      // distance between consecutive points is 2, then we'll need to skip at
-      // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of
-      // finding a closer point.  We use Math.max(..., 1) to ensure that we
-      // always advance at least one point, to avoid an infinite loop.
-      index += stride * Math.max(
-          ((Math.sqrt(squaredDistance) -
-            Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1);
-    }
-  }
-  if (isRing) {
-    // Check the closing segment.
-    ol.geom.flat.closest.point(
-        flatCoordinates, end - stride, offset, stride, x, y, tmpPoint);
-    squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]);
-    if (squaredDistance < minSquaredDistance) {
-      minSquaredDistance = squaredDistance;
-      for (i = 0; i < stride; ++i) {
-        closestPoint[i] = tmpPoint[i];
-      }
-      closestPoint.length = stride;
-    }
-  }
-  return minSquaredDistance;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {number} maxDelta Max delta.
- * @param {boolean} isRing Is ring.
- * @param {number} x X.
- * @param {number} y Y.
- * @param {Array.<number>} closestPoint Closest point.
- * @param {number} minSquaredDistance Minimum squared distance.
- * @param {Array.<number>=} opt_tmpPoint Temporary point object.
- * @return {number} Minimum squared distance.
- */
-ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends,
-    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
-    opt_tmpPoint) {
-  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    minSquaredDistance = ol.geom.flat.closest.getClosestPoint(
-        flatCoordinates, offset, end, stride,
-        maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint);
-    offset = end;
-  }
-  return minSquaredDistance;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {number} maxDelta Max delta.
- * @param {boolean} isRing Is ring.
- * @param {number} x X.
- * @param {number} y Y.
- * @param {Array.<number>} closestPoint Closest point.
- * @param {number} minSquaredDistance Minimum squared distance.
- * @param {Array.<number>=} opt_tmpPoint Temporary point object.
- * @return {number} Minimum squared distance.
- */
-ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset,
-    endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
-    opt_tmpPoint) {
-  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    minSquaredDistance = ol.geom.flat.closest.getsClosestPoint(
-        flatCoordinates, offset, ends, stride,
-        maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint);
-    offset = ends[ends.length - 1];
-  }
-  return minSquaredDistance;
-};
-
-goog.provide('ol.geom.flat.deflate');
-
-goog.require('ol');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} stride Stride.
- * @return {number} offset Offset.
- */
-ol.geom.flat.deflate.coordinate = function(flatCoordinates, offset, coordinate, stride) {
-  ol.DEBUG && console.assert(coordinate.length == stride,
-      'length of the coordinate array should match stride');
-  var i, ii;
-  for (i = 0, ii = coordinate.length; i < ii; ++i) {
-    flatCoordinates[offset++] = coordinate[i];
-  }
-  return offset;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {number} stride Stride.
- * @return {number} offset Offset.
- */
-ol.geom.flat.deflate.coordinates = function(flatCoordinates, offset, coordinates, stride) {
-  var i, ii;
-  for (i = 0, ii = coordinates.length; i < ii; ++i) {
-    var coordinate = coordinates[i];
-    ol.DEBUG && console.assert(coordinate.length == stride,
-        'length of coordinate array should match stride');
-    var j;
-    for (j = 0; j < stride; ++j) {
-      flatCoordinates[offset++] = coordinate[j];
-    }
-  }
-  return offset;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess.
- * @param {number} stride Stride.
- * @param {Array.<number>=} opt_ends Ends.
- * @return {Array.<number>} Ends.
- */
-ol.geom.flat.deflate.coordinatess = function(flatCoordinates, offset, coordinatess, stride, opt_ends) {
-  var ends = opt_ends ? opt_ends : [];
-  var i = 0;
-  var j, jj;
-  for (j = 0, jj = coordinatess.length; j < jj; ++j) {
-    var end = ol.geom.flat.deflate.coordinates(
-        flatCoordinates, offset, coordinatess[j], stride);
-    ends[i++] = end;
-    offset = end;
-  }
-  ends.length = i;
-  return ends;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinatesss Coordinatesss.
- * @param {number} stride Stride.
- * @param {Array.<Array.<number>>=} opt_endss Endss.
- * @return {Array.<Array.<number>>} Endss.
- */
-ol.geom.flat.deflate.coordinatesss = function(flatCoordinates, offset, coordinatesss, stride, opt_endss) {
-  var endss = opt_endss ? opt_endss : [];
-  var i = 0;
-  var j, jj;
-  for (j = 0, jj = coordinatesss.length; j < jj; ++j) {
-    var ends = ol.geom.flat.deflate.coordinatess(
-        flatCoordinates, offset, coordinatesss[j], stride, endss[i]);
-    endss[i++] = ends;
-    offset = ends[ends.length - 1];
-  }
-  endss.length = i;
-  return endss;
-};
-
-goog.provide('ol.geom.flat.inflate');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {Array.<ol.Coordinate>=} opt_coordinates Coordinates.
- * @return {Array.<ol.Coordinate>} Coordinates.
- */
-ol.geom.flat.inflate.coordinates = function(flatCoordinates, offset, end, stride, opt_coordinates) {
-  var coordinates = opt_coordinates !== undefined ? opt_coordinates : [];
-  var i = 0;
-  var j;
-  for (j = offset; j < end; j += stride) {
-    coordinates[i++] = flatCoordinates.slice(j, j + stride);
-  }
-  coordinates.length = i;
-  return coordinates;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess.
- * @return {Array.<Array.<ol.Coordinate>>} Coordinatess.
- */
-ol.geom.flat.inflate.coordinatess = function(flatCoordinates, offset, ends, stride, opt_coordinatess) {
-  var coordinatess = opt_coordinatess !== undefined ? opt_coordinatess : [];
-  var i = 0;
-  var j, jj;
-  for (j = 0, jj = ends.length; j < jj; ++j) {
-    var end = ends[j];
-    coordinatess[i++] = ol.geom.flat.inflate.coordinates(
-        flatCoordinates, offset, end, stride, coordinatess[i]);
-    offset = end;
-  }
-  coordinatess.length = i;
-  return coordinatess;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss
- *     Coordinatesss.
- * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss.
- */
-ol.geom.flat.inflate.coordinatesss = function(flatCoordinates, offset, endss, stride, opt_coordinatesss) {
-  var coordinatesss = opt_coordinatesss !== undefined ? opt_coordinatesss : [];
-  var i = 0;
-  var j, jj;
-  for (j = 0, jj = endss.length; j < jj; ++j) {
-    var ends = endss[j];
-    coordinatesss[i++] = ol.geom.flat.inflate.coordinatess(
-        flatCoordinates, offset, ends, stride, coordinatesss[i]);
-    offset = ends[ends.length - 1];
-  }
-  coordinatesss.length = i;
-  return coordinatesss;
-};
-
-// Based on simplify-js https://github.com/mourner/simplify-js
-// Copyright (c) 2012, Vladimir Agafonkin
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-//    1. Redistributions of source code must retain the above copyright notice,
-//       this list of conditions and the following disclaimer.
-//
-//    2. Redistributions in binary form must reproduce the above copyright
-//       notice, this list of conditions and the following disclaimer in the
-//       documentation and/or other materials provided with the distribution.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.geom.flat.simplify');
-
-goog.require('ol.math');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {boolean} highQuality Highest quality.
- * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @return {Array.<number>} Simplified line string.
- */
-ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end,
-    stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) {
-  var simplifiedFlatCoordinates = opt_simplifiedFlatCoordinates !== undefined ?
-      opt_simplifiedFlatCoordinates : [];
-  if (!highQuality) {
-    end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end,
-        stride, squaredTolerance,
-        simplifiedFlatCoordinates, 0);
-    flatCoordinates = simplifiedFlatCoordinates;
-    offset = 0;
-    stride = 2;
-  }
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
-      flatCoordinates, offset, end, stride, squaredTolerance,
-      simplifiedFlatCoordinates, 0);
-  return simplifiedFlatCoordinates;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @return {number} Simplified offset.
- */
-ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end,
-    stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
-  var n = (end - offset) / stride;
-  if (n < 3) {
-    for (; offset < end; offset += stride) {
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset];
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + 1];
-    }
-    return simplifiedOffset;
-  }
-  /** @type {Array.<number>} */
-  var markers = new Array(n);
-  markers[0] = 1;
-  markers[n - 1] = 1;
-  /** @type {Array.<number>} */
-  var stack = [offset, end - stride];
-  var index = 0;
-  var i;
-  while (stack.length > 0) {
-    var last = stack.pop();
-    var first = stack.pop();
-    var maxSquaredDistance = 0;
-    var x1 = flatCoordinates[first];
-    var y1 = flatCoordinates[first + 1];
-    var x2 = flatCoordinates[last];
-    var y2 = flatCoordinates[last + 1];
-    for (i = first + stride; i < last; i += stride) {
-      var x = flatCoordinates[i];
-      var y = flatCoordinates[i + 1];
-      var squaredDistance = ol.math.squaredSegmentDistance(
-          x, y, x1, y1, x2, y2);
-      if (squaredDistance > maxSquaredDistance) {
-        index = i;
-        maxSquaredDistance = squaredDistance;
-      }
-    }
-    if (maxSquaredDistance > squaredTolerance) {
-      markers[(index - offset) / stride] = 1;
-      if (first + stride < index) {
-        stack.push(first, index);
-      }
-      if (index + stride < last) {
-        stack.push(index, last);
-      }
-    }
-  }
-  for (i = 0; i < n; ++i) {
-    if (markers[i]) {
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + i * stride];
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + i * stride + 1];
-    }
-  }
-  return simplifiedOffset;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @param {Array.<number>} simplifiedEnds Simplified ends.
- * @return {number} Simplified offset.
- */
-ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset,
-    ends, stride, squaredTolerance, simplifiedFlatCoordinates,
-    simplifiedOffset, simplifiedEnds) {
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    simplifiedOffset = ol.geom.flat.simplify.douglasPeucker(
-        flatCoordinates, offset, end, stride, squaredTolerance,
-        simplifiedFlatCoordinates, simplifiedOffset);
-    simplifiedEnds.push(simplifiedOffset);
-    offset = end;
-  }
-  return simplifiedOffset;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
- * @return {number} Simplified offset.
- */
-ol.geom.flat.simplify.douglasPeuckerss = function(
-    flatCoordinates, offset, endss, stride, squaredTolerance,
-    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    var simplifiedEnds = [];
-    simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers(
-        flatCoordinates, offset, ends, stride, squaredTolerance,
-        simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
-    simplifiedEndss.push(simplifiedEnds);
-    offset = ends[ends.length - 1];
-  }
-  return simplifiedOffset;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @return {number} Simplified offset.
- */
-ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end,
-    stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
-  if (end <= offset + stride) {
-    // zero or one point, no simplification possible, so copy and return
-    for (; offset < end; offset += stride) {
-      simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset];
-      simplifiedFlatCoordinates[simplifiedOffset++] =
-          flatCoordinates[offset + 1];
-    }
-    return simplifiedOffset;
-  }
-  var x1 = flatCoordinates[offset];
-  var y1 = flatCoordinates[offset + 1];
-  // copy first point
-  simplifiedFlatCoordinates[simplifiedOffset++] = x1;
-  simplifiedFlatCoordinates[simplifiedOffset++] = y1;
-  var x2 = x1;
-  var y2 = y1;
-  for (offset += stride; offset < end; offset += stride) {
-    x2 = flatCoordinates[offset];
-    y2 = flatCoordinates[offset + 1];
-    if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) {
-      // copy point at offset
-      simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-      simplifiedFlatCoordinates[simplifiedOffset++] = y2;
-      x1 = x2;
-      y1 = y2;
-    }
-  }
-  if (x2 != x1 || y2 != y1) {
-    // copy last point
-    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
-  }
-  return simplifiedOffset;
-};
-
-
-/**
- * @param {number} value Value.
- * @param {number} tolerance Tolerance.
- * @return {number} Rounded value.
- */
-ol.geom.flat.simplify.snap = function(value, tolerance) {
-  return tolerance * Math.round(value / tolerance);
-};
-
-
-/**
- * Simplifies a line string using an algorithm designed by Tim Schaub.
- * Coordinates are snapped to the nearest value in a virtual grid and
- * consecutive duplicate coordinates are discarded.  This effectively preserves
- * topology as the simplification of any subsection of a line string is
- * independent of the rest of the line string.  This means that, for examples,
- * the common edge between two polygons will be simplified to the same line
- * string independently in both polygons.  This implementation uses a single
- * pass over the coordinates and eliminates intermediate collinear points.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} tolerance Tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @return {number} Simplified offset.
- */
-ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride,
-    tolerance, simplifiedFlatCoordinates, simplifiedOffset) {
-  // do nothing if the line is empty
-  if (offset == end) {
-    return simplifiedOffset;
-  }
-  // snap the first coordinate (P1)
-  var x1 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
-  var y1 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
-  offset += stride;
-  // add the first coordinate to the output
-  simplifiedFlatCoordinates[simplifiedOffset++] = x1;
-  simplifiedFlatCoordinates[simplifiedOffset++] = y1;
-  // find the next coordinate that does not snap to the same value as the first
-  // coordinate (P2)
-  var x2, y2;
-  do {
-    x2 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
-    y2 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
-    offset += stride;
-    if (offset == end) {
-      // all coordinates snap to the same value, the line collapses to a point
-      // push the last snapped value anyway to ensure that the output contains
-      // at least two points
-      // FIXME should we really return at least two points anyway?
-      simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-      simplifiedFlatCoordinates[simplifiedOffset++] = y2;
-      return simplifiedOffset;
-    }
-  } while (x2 == x1 && y2 == y1);
-  while (offset < end) {
-    var x3, y3;
-    // snap the next coordinate (P3)
-    x3 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
-    y3 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
-    offset += stride;
-    // skip P3 if it is equal to P2
-    if (x3 == x2 && y3 == y2) {
-      continue;
-    }
-    // calculate the delta between P1 and P2
-    var dx1 = x2 - x1;
-    var dy1 = y2 - y1;
-    // calculate the delta between P3 and P1
-    var dx2 = x3 - x1;
-    var dy2 = y3 - y1;
-    // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from
-    // P1 in the same direction then P2 is on the straight line between P1 and
-    // P3
-    if ((dx1 * dy2 == dy1 * dx2) &&
-        ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) &&
-        ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) {
-      // discard P2 and set P2 = P3
-      x2 = x3;
-      y2 = y3;
-      continue;
-    }
-    // either P1, P2, and P3 are not colinear, or they are colinear but P3 is
-    // between P3 and P1 or on the opposite half of the line to P2.  add P2,
-    // and continue with P1 = P2 and P2 = P3
-    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
-    x1 = x2;
-    y1 = y2;
-    x2 = x3;
-    y2 = y3;
-  }
-  // add the last point (P2)
-  simplifiedFlatCoordinates[simplifiedOffset++] = x2;
-  simplifiedFlatCoordinates[simplifiedOffset++] = y2;
-  return simplifiedOffset;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {number} tolerance Tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @param {Array.<number>} simplifiedEnds Simplified ends.
- * @return {number} Simplified offset.
- */
-ol.geom.flat.simplify.quantizes = function(
-    flatCoordinates, offset, ends, stride,
-    tolerance,
-    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) {
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    simplifiedOffset = ol.geom.flat.simplify.quantize(
-        flatCoordinates, offset, end, stride,
-        tolerance,
-        simplifiedFlatCoordinates, simplifiedOffset);
-    simplifiedEnds.push(simplifiedOffset);
-    offset = end;
-  }
-  return simplifiedOffset;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {number} tolerance Tolerance.
- * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
- *     coordinates.
- * @param {number} simplifiedOffset Simplified offset.
- * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
- * @return {number} Simplified offset.
- */
-ol.geom.flat.simplify.quantizess = function(
-    flatCoordinates, offset, endss, stride,
-    tolerance,
-    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    var simplifiedEnds = [];
-    simplifiedOffset = ol.geom.flat.simplify.quantizes(
-        flatCoordinates, offset, ends, stride,
-        tolerance,
-        simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
-    simplifiedEndss.push(simplifiedEnds);
-    offset = ends[ends.length - 1];
-  }
-  return simplifiedOffset;
-};
-
-goog.provide('ol.geom.LinearRing');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.area');
-goog.require('ol.geom.flat.closest');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.geom.flat.simplify');
-
-
-/**
- * @classdesc
- * Linear ring geometry. Only used as part of polygon; cannot be rendered
- * on its own.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.LinearRing = function(coordinates, opt_layout) {
-
-  ol.geom.SimpleGeometry.call(this);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
-
-  this.setCoordinates(coordinates, opt_layout);
-
-};
-ol.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry);
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.LinearRing} Clone.
- * @api stable
- */
-ol.geom.LinearRing.prototype.clone = function() {
-  var linearRing = new ol.geom.LinearRing(null);
-  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return linearRing;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.LinearRing.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  if (this.maxDeltaRevision_ != this.getRevision()) {
-    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta(
-        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0));
-    this.maxDeltaRevision_ = this.getRevision();
-  }
-  return ol.geom.flat.closest.getClosestPoint(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
-};
-
-
-/**
- * Return the area of the linear ring on projected plane.
- * @return {number} Area (on projected plane).
- * @api stable
- */
-ol.geom.LinearRing.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRing(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
-
-
-/**
- * Return the coordinates of the linear ring.
- * @return {Array.<ol.Coordinate>} Coordinates.
- * @api stable
- */
-ol.geom.LinearRing.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.LinearRing.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
-  var simplifiedFlatCoordinates = [];
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      squaredTolerance, simplifiedFlatCoordinates, 0);
-  var simplifiedLinearRing = new ol.geom.LinearRing(null);
-  simplifiedLinearRing.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates);
-  return simplifiedLinearRing;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.LinearRing.prototype.getType = function() {
-  return ol.geom.GeometryType.LINEAR_RING;
-};
-
-
-/**
- * Set the coordinates of the linear ring.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.LinearRing.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 1);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- */
-ol.geom.LinearRing.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
-};
-
-goog.provide('ol.geom.Point');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.math');
-
-
-/**
- * @classdesc
- * Point geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {ol.Coordinate} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.Point = function(coordinates, opt_layout) {
-  ol.geom.SimpleGeometry.call(this);
-  this.setCoordinates(coordinates, opt_layout);
-};
-ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Point} Clone.
- * @api stable
- */
-ol.geom.Point.prototype.clone = function() {
-  var point = new ol.geom.Point(null);
-  point.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return point;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Point.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  var flatCoordinates = this.flatCoordinates;
-  var squaredDistance = ol.math.squaredDistance(
-      x, y, flatCoordinates[0], flatCoordinates[1]);
-  if (squaredDistance < minSquaredDistance) {
-    var stride = this.stride;
-    var i;
-    for (i = 0; i < stride; ++i) {
-      closestPoint[i] = flatCoordinates[i];
-    }
-    closestPoint.length = stride;
-    return squaredDistance;
-  } else {
-    return minSquaredDistance;
-  }
-};
-
-
-/**
- * Return the coordinate of the point.
- * @return {ol.Coordinate} Coordinates.
- * @api stable
- */
-ol.geom.Point.prototype.getCoordinates = function() {
-  return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Point.prototype.computeExtent = function(extent) {
-  return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.Point.prototype.getType = function() {
-  return ol.geom.GeometryType.POINT;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.Point.prototype.intersectsExtent = function(extent) {
-  return ol.extent.containsXY(extent,
-      this.flatCoordinates[0], this.flatCoordinates[1]);
-};
-
-
-/**
- * Set the coordinate of the point.
- * @param {ol.Coordinate} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 0);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinate(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- */
-ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
-};
-
-goog.provide('ol.geom.flat.contains');
-
-goog.require('ol');
-goog.require('ol.extent');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} Contains extent.
- */
-ol.geom.flat.contains.linearRingContainsExtent = function(flatCoordinates, offset, end, stride, extent) {
-  var outside = ol.extent.forEachCorner(extent,
-      /**
-       * @param {ol.Coordinate} coordinate Coordinate.
-       * @return {boolean} Contains (x, y).
-       */
-      function(coordinate) {
-        return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates,
-            offset, end, stride, coordinate[0], coordinate[1]);
-      });
-  return !outside;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {boolean} Contains (x, y).
- */
-ol.geom.flat.contains.linearRingContainsXY = function(flatCoordinates, offset, end, stride, x, y) {
-  // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
-  var contains = false;
-  var x1 = flatCoordinates[end - stride];
-  var y1 = flatCoordinates[end - stride + 1];
-  for (; offset < end; offset += stride) {
-    var x2 = flatCoordinates[offset];
-    var y2 = flatCoordinates[offset + 1];
-    var intersect = ((y1 > y) != (y2 > y)) &&
-        (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1);
-    if (intersect) {
-      contains = !contains;
-    }
-    x1 = x2;
-    y1 = y2;
-  }
-  return contains;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {boolean} Contains (x, y).
- */
-ol.geom.flat.contains.linearRingsContainsXY = function(flatCoordinates, offset, ends, stride, x, y) {
-  ol.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array');
-  if (ends.length === 0) {
-    return false;
-  }
-  if (!ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, ends[0], stride, x, y)) {
-    return false;
-  }
-  var i, ii;
-  for (i = 1, ii = ends.length; i < ii; ++i) {
-    if (ol.geom.flat.contains.linearRingContainsXY(
-        flatCoordinates, ends[i - 1], ends[i], stride, x, y)) {
-      return false;
-    }
-  }
-  return true;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {boolean} Contains (x, y).
- */
-ol.geom.flat.contains.linearRingssContainsXY = function(flatCoordinates, offset, endss, stride, x, y) {
-  ol.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array');
-  if (endss.length === 0) {
-    return false;
-  }
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    if (ol.geom.flat.contains.linearRingsContainsXY(
-        flatCoordinates, offset, ends, stride, x, y)) {
-      return true;
-    }
-    offset = ends[ends.length - 1];
-  }
-  return false;
-};
-
-goog.provide('ol.geom.flat.interiorpoint');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.geom.flat.contains');
-
-
-/**
- * Calculates a point that is likely to lie in the interior of the linear rings.
- * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {Array.<number>} flatCenters Flat centers.
- * @param {number} flatCentersOffset Flat center offset.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Destination.
- */
-ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
-    ends, stride, flatCenters, flatCentersOffset, opt_dest) {
-  var i, ii, x, x1, x2, y1, y2;
-  var y = flatCenters[flatCentersOffset + 1];
-  /** @type {Array.<number>} */
-  var intersections = [];
-  // Calculate intersections with the horizontal line
-  var end = ends[0];
-  x1 = flatCoordinates[end - stride];
-  y1 = flatCoordinates[end - stride + 1];
-  for (i = offset; i < end; i += stride) {
-    x2 = flatCoordinates[i];
-    y2 = flatCoordinates[i + 1];
-    if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) {
-      x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
-      intersections.push(x);
-    }
-    x1 = x2;
-    y1 = y2;
-  }
-  // Find the longest segment of the horizontal line that has its center point
-  // inside the linear ring.
-  var pointX = NaN;
-  var maxSegmentLength = -Infinity;
-  intersections.sort(ol.array.numberSafeCompareFunction);
-  x1 = intersections[0];
-  for (i = 1, ii = intersections.length; i < ii; ++i) {
-    x2 = intersections[i];
-    var segmentLength = Math.abs(x2 - x1);
-    if (segmentLength > maxSegmentLength) {
-      x = (x1 + x2) / 2;
-      if (ol.geom.flat.contains.linearRingsContainsXY(
-          flatCoordinates, offset, ends, stride, x, y)) {
-        pointX = x;
-        maxSegmentLength = segmentLength;
-      }
-    }
-    x1 = x2;
-  }
-  if (isNaN(pointX)) {
-    // There is no horizontal line that has its center point inside the linear
-    // ring.  Use the center of the the linear ring's extent.
-    pointX = flatCenters[flatCentersOffset];
-  }
-  if (opt_dest) {
-    opt_dest.push(pointX, y);
-    return opt_dest;
-  } else {
-    return [pointX, y];
-  }
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {Array.<number>} flatCenters Flat centers.
- * @return {Array.<number>} Interior points.
- */
-ol.geom.flat.interiorpoint.linearRingss = function(flatCoordinates, offset, endss, stride, flatCenters) {
-  ol.DEBUG && console.assert(2 * endss.length == flatCenters.length,
-      'endss.length times 2 should be flatCenters.length');
-  var interiorPoints = [];
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    interiorPoints = ol.geom.flat.interiorpoint.linearRings(flatCoordinates,
-        offset, ends, stride, flatCenters, 2 * i, interiorPoints);
-    offset = ends[ends.length - 1];
-  }
-  return interiorPoints;
-};
-
-goog.provide('ol.geom.flat.segments');
-
-
-/**
- * This function calls `callback` for each segment of the flat coordinates
- * array. If the callback returns a truthy value the function returns that
- * value immediately. Otherwise the function returns `false`.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function
- *     called for each segment.
- * @param {S=} opt_this The object to be used as the value of 'this'
- *     within callback.
- * @return {T|boolean} Value.
- * @template T,S
- */
-ol.geom.flat.segments.forEach = function(flatCoordinates, offset, end, stride, callback, opt_this) {
-  var point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]];
-  var point2 = [];
-  var ret;
-  for (; (offset + stride) < end; offset += stride) {
-    point2[0] = flatCoordinates[offset + stride];
-    point2[1] = flatCoordinates[offset + stride + 1];
-    ret = callback.call(opt_this, point1, point2);
-    if (ret) {
-      return ret;
-    }
-    point1[0] = point2[0];
-    point1[1] = point2[1];
-  }
-  return false;
-};
-
-goog.provide('ol.geom.flat.intersectsextent');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.geom.flat.contains');
-goog.require('ol.geom.flat.segments');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} True if the geometry and the extent intersect.
- */
-ol.geom.flat.intersectsextent.lineString = function(flatCoordinates, offset, end, stride, extent) {
-  var coordinatesExtent = ol.extent.extendFlatCoordinates(
-      ol.extent.createEmpty(), flatCoordinates, offset, end, stride);
-  if (!ol.extent.intersects(extent, coordinatesExtent)) {
-    return false;
-  }
-  if (ol.extent.containsExtent(extent, coordinatesExtent)) {
-    return true;
-  }
-  if (coordinatesExtent[0] >= extent[0] &&
-      coordinatesExtent[2] <= extent[2]) {
-    return true;
-  }
-  if (coordinatesExtent[1] >= extent[1] &&
-      coordinatesExtent[3] <= extent[3]) {
-    return true;
-  }
-  return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride,
-      /**
-       * @param {ol.Coordinate} point1 Start point.
-       * @param {ol.Coordinate} point2 End point.
-       * @return {boolean} `true` if the segment and the extent intersect,
-       *     `false` otherwise.
-       */
-      function(point1, point2) {
-        return ol.extent.intersectsSegment(extent, point1, point2);
-      });
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} True if the geometry and the extent intersect.
- */
-ol.geom.flat.intersectsextent.lineStrings = function(flatCoordinates, offset, ends, stride, extent) {
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    if (ol.geom.flat.intersectsextent.lineString(
-        flatCoordinates, offset, ends[i], stride, extent)) {
-      return true;
-    }
-    offset = ends[i];
-  }
-  return false;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} True if the geometry and the extent intersect.
- */
-ol.geom.flat.intersectsextent.linearRing = function(flatCoordinates, offset, end, stride, extent) {
-  if (ol.geom.flat.intersectsextent.lineString(
-      flatCoordinates, offset, end, stride, extent)) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[0], extent[1])) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[0], extent[3])) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[2], extent[1])) {
-    return true;
-  }
-  if (ol.geom.flat.contains.linearRingContainsXY(
-      flatCoordinates, offset, end, stride, extent[2], extent[3])) {
-    return true;
-  }
-  return false;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} True if the geometry and the extent intersect.
- */
-ol.geom.flat.intersectsextent.linearRings = function(flatCoordinates, offset, ends, stride, extent) {
-  ol.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array');
-  if (!ol.geom.flat.intersectsextent.linearRing(
-      flatCoordinates, offset, ends[0], stride, extent)) {
-    return false;
-  }
-  if (ends.length === 1) {
-    return true;
-  }
-  var i, ii;
-  for (i = 1, ii = ends.length; i < ii; ++i) {
-    if (ol.geom.flat.contains.linearRingContainsExtent(
-        flatCoordinates, ends[i - 1], ends[i], stride, extent)) {
-      return false;
-    }
-  }
-  return true;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @param {ol.Extent} extent Extent.
- * @return {boolean} True if the geometry and the extent intersect.
- */
-ol.geom.flat.intersectsextent.linearRingss = function(flatCoordinates, offset, endss, stride, extent) {
-  ol.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array');
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    if (ol.geom.flat.intersectsextent.linearRings(
-        flatCoordinates, offset, ends, stride, extent)) {
-      return true;
-    }
-    offset = ends[ends.length - 1];
-  }
-  return false;
-};
-
-goog.provide('ol.geom.flat.reverse');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- */
-ol.geom.flat.reverse.coordinates = function(flatCoordinates, offset, end, stride) {
-  while (offset < end - stride) {
-    var i;
-    for (i = 0; i < stride; ++i) {
-      var tmp = flatCoordinates[offset + i];
-      flatCoordinates[offset + i] = flatCoordinates[end - stride + i];
-      flatCoordinates[end - stride + i] = tmp;
-    }
-    offset += stride;
-    end -= stride;
-  }
-};
-
-goog.provide('ol.geom.flat.orient');
-
-goog.require('ol');
-goog.require('ol.geom.flat.reverse');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {boolean} Is clockwise.
- */
-ol.geom.flat.orient.linearRingIsClockwise = function(flatCoordinates, offset, end, stride) {
-  // http://tinyurl.com/clockwise-method
-  // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp
-  var edge = 0;
-  var x1 = flatCoordinates[end - stride];
-  var y1 = flatCoordinates[end - stride + 1];
-  for (; offset < end; offset += stride) {
-    var x2 = flatCoordinates[offset];
-    var y2 = flatCoordinates[offset + 1];
-    edge += (x2 - x1) * (y2 + y1);
-    x1 = x2;
-    y1 = y2;
-  }
-  return edge > 0;
-};
-
-
-/**
- * Determines if linear rings are oriented.  By default, left-hand orientation
- * is tested (first ring must be clockwise, remaining rings counter-clockwise).
- * To test for right-hand orientation, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Array of end indexes.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Test for right-hand orientation
- *     (counter-clockwise exterior ring and clockwise interior rings).
- * @return {boolean} Rings are correctly oriented.
- */
-ol.geom.flat.orient.linearRingsAreOriented = function(flatCoordinates, offset, ends, stride, opt_right) {
-  var right = opt_right !== undefined ? opt_right : false;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
-        flatCoordinates, offset, end, stride);
-    if (i === 0) {
-      if ((right && isClockwise) || (!right && !isClockwise)) {
-        return false;
-      }
-    } else {
-      if ((right && !isClockwise) || (!right && isClockwise)) {
-        return false;
-      }
-    }
-    offset = end;
-  }
-  return true;
-};
-
-
-/**
- * Determines if linear rings are oriented.  By default, left-hand orientation
- * is tested (first ring must be clockwise, remaining rings counter-clockwise).
- * To test for right-hand orientation, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Array of array of end indexes.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Test for right-hand orientation
- *     (counter-clockwise exterior ring and clockwise interior rings).
- * @return {boolean} Rings are correctly oriented.
- */
-ol.geom.flat.orient.linearRingssAreOriented = function(flatCoordinates, offset, endss, stride, opt_right) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    if (!ol.geom.flat.orient.linearRingsAreOriented(
-        flatCoordinates, offset, endss[i], stride, opt_right)) {
-      return false;
-    }
-  }
-  return true;
-};
-
-
-/**
- * Orient coordinates in a flat array of linear rings.  By default, rings
- * are oriented following the left-hand rule (clockwise for exterior and
- * counter-clockwise for interior rings).  To orient according to the
- * right-hand rule, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Follow the right-hand rule for orientation.
- * @return {number} End.
- */
-ol.geom.flat.orient.orientLinearRings = function(flatCoordinates, offset, ends, stride, opt_right) {
-  var right = opt_right !== undefined ? opt_right : false;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
-        flatCoordinates, offset, end, stride);
-    var reverse = i === 0 ?
-        (right && isClockwise) || (!right && !isClockwise) :
-        (right && !isClockwise) || (!right && isClockwise);
-    if (reverse) {
-      ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride);
-    }
-    offset = end;
-  }
-  return offset;
-};
-
-
-/**
- * Orient coordinates in a flat array of linear rings.  By default, rings
- * are oriented following the left-hand rule (clockwise for exterior and
- * counter-clockwise for interior rings).  To orient according to the
- * right-hand rule, use the `opt_right` argument.
- *
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Array of array of end indexes.
- * @param {number} stride Stride.
- * @param {boolean=} opt_right Follow the right-hand rule for orientation.
- * @return {number} End.
- */
-ol.geom.flat.orient.orientLinearRingss = function(flatCoordinates, offset, endss, stride, opt_right) {
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    offset = ol.geom.flat.orient.orientLinearRings(
-        flatCoordinates, offset, endss[i], stride, opt_right);
-  }
-  return offset;
-};
-
-goog.provide('ol.geom.Polygon');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.area');
-goog.require('ol.geom.flat.closest');
-goog.require('ol.geom.flat.contains');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.geom.flat.interiorpoint');
-goog.require('ol.geom.flat.intersectsextent');
-goog.require('ol.geom.flat.orient');
-goog.require('ol.geom.flat.simplify');
-goog.require('ol.math');
-
-
-/**
- * @classdesc
- * Polygon geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.Polygon = function(coordinates, opt_layout) {
-
-  ol.geom.SimpleGeometry.call(this);
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.ends_ = [];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.flatInteriorPointRevision_ = -1;
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.flatInteriorPoint_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.orientedRevision_ = -1;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.orientedFlatCoordinates_ = null;
-
-  this.setCoordinates(coordinates, opt_layout);
-
-};
-ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
-
-
-/**
- * Append the passed linear ring to this polygon.
- * @param {ol.geom.LinearRing} linearRing Linear ring.
- * @api stable
- */
-ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
-  ol.DEBUG && console.assert(linearRing.getLayout() == this.layout,
-      'layout of linearRing should match layout');
-  if (!this.flatCoordinates) {
-    this.flatCoordinates = linearRing.getFlatCoordinates().slice();
-  } else {
-    ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
-  }
-  this.ends_.push(this.flatCoordinates.length);
-  this.changed();
-};
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Polygon} Clone.
- * @api stable
- */
-ol.geom.Polygon.prototype.clone = function() {
-  var polygon = new ol.geom.Polygon(null);
-  polygon.setFlatCoordinates(
-      this.layout, this.flatCoordinates.slice(), this.ends_.slice());
-  return polygon;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Polygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  if (this.maxDeltaRevision_ != this.getRevision()) {
-    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta(
-        this.flatCoordinates, 0, this.ends_, this.stride, 0));
-    this.maxDeltaRevision_ = this.getRevision();
-  }
-  return ol.geom.flat.closest.getsClosestPoint(
-      this.flatCoordinates, 0, this.ends_, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Polygon.prototype.containsXY = function(x, y) {
-  return ol.geom.flat.contains.linearRingsContainsXY(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
-};
-
-
-/**
- * Return the area of the polygon on projected plane.
- * @return {number} Area (on projected plane).
- * @api stable
- */
-ol.geom.Polygon.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRings(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
-};
-
-
-/**
- * Get the coordinate array for this geometry.  This array has the structure
- * of a GeoJSON coordinate array for polygons.
- *
- * @param {boolean=} opt_right Orient coordinates according to the right-hand
- *     rule (counter-clockwise for exterior and clockwise for interior rings).
- *     If `false`, coordinates will be oriented according to the left-hand rule
- *     (clockwise for exterior and counter-clockwise for interior rings).
- *     By default, coordinate orientation will depend on how the geometry was
- *     constructed.
- * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
- * @api stable
- */
-ol.geom.Polygon.prototype.getCoordinates = function(opt_right) {
-  var flatCoordinates;
-  if (opt_right !== undefined) {
-    flatCoordinates = this.getOrientedFlatCoordinates().slice();
-    ol.geom.flat.orient.orientLinearRings(
-        flatCoordinates, 0, this.ends_, this.stride, opt_right);
-  } else {
-    flatCoordinates = this.flatCoordinates;
-  }
-
-  return ol.geom.flat.inflate.coordinatess(
-      flatCoordinates, 0, this.ends_, this.stride);
-};
-
-
-/**
- * @return {Array.<number>} Ends.
- */
-ol.geom.Polygon.prototype.getEnds = function() {
-  return this.ends_;
-};
-
-
-/**
- * @return {Array.<number>} Interior point.
- */
-ol.geom.Polygon.prototype.getFlatInteriorPoint = function() {
-  if (this.flatInteriorPointRevision_ != this.getRevision()) {
-    var flatCenter = ol.extent.getCenter(this.getExtent());
-    this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings(
-        this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride,
-        flatCenter, 0);
-    this.flatInteriorPointRevision_ = this.getRevision();
-  }
-  return this.flatInteriorPoint_;
-};
-
-
-/**
- * Return an interior point of the polygon.
- * @return {ol.geom.Point} Interior point.
- * @api stable
- */
-ol.geom.Polygon.prototype.getInteriorPoint = function() {
-  return new ol.geom.Point(this.getFlatInteriorPoint());
-};
-
-
-/**
- * Return the number of rings of the polygon,  this includes the exterior
- * ring and any interior rings.
- *
- * @return {number} Number of rings.
- * @api
- */
-ol.geom.Polygon.prototype.getLinearRingCount = function() {
-  return this.ends_.length;
-};
-
-
-/**
- * Return the Nth linear ring of the polygon geometry. Return `null` if the
- * given index is out of range.
- * The exterior linear ring is available at index `0` and the interior rings
- * at index `1` and beyond.
- *
- * @param {number} index Index.
- * @return {ol.geom.LinearRing} Linear ring.
- * @api stable
- */
-ol.geom.Polygon.prototype.getLinearRing = function(index) {
-  ol.DEBUG && console.assert(0 <= index && index < this.ends_.length,
-      'index should be in between 0 and and length of this.ends_');
-  if (index < 0 || this.ends_.length <= index) {
-    return null;
-  }
-  var linearRing = new ol.geom.LinearRing(null);
-  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
-      index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]));
-  return linearRing;
-};
-
-
-/**
- * Return the linear rings of the polygon.
- * @return {Array.<ol.geom.LinearRing>} Linear rings.
- * @api stable
- */
-ol.geom.Polygon.prototype.getLinearRings = function() {
-  var layout = this.layout;
-  var flatCoordinates = this.flatCoordinates;
-  var ends = this.ends_;
-  var linearRings = [];
-  var offset = 0;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var linearRing = new ol.geom.LinearRing(null);
-    linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end));
-    linearRings.push(linearRing);
-    offset = end;
-  }
-  return linearRings;
-};
-
-
-/**
- * @return {Array.<number>} Oriented flat coordinates.
- */
-ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() {
-  if (this.orientedRevision_ != this.getRevision()) {
-    var flatCoordinates = this.flatCoordinates;
-    if (ol.geom.flat.orient.linearRingsAreOriented(
-        flatCoordinates, 0, this.ends_, this.stride)) {
-      this.orientedFlatCoordinates_ = flatCoordinates;
-    } else {
-      this.orientedFlatCoordinates_ = flatCoordinates.slice();
-      this.orientedFlatCoordinates_.length =
-          ol.geom.flat.orient.orientLinearRings(
-              this.orientedFlatCoordinates_, 0, this.ends_, this.stride);
-    }
-    this.orientedRevision_ = this.getRevision();
-  }
-  return this.orientedFlatCoordinates_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Polygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
-  var simplifiedFlatCoordinates = [];
-  var simplifiedEnds = [];
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes(
-      this.flatCoordinates, 0, this.ends_, this.stride,
-      Math.sqrt(squaredTolerance),
-      simplifiedFlatCoordinates, 0, simplifiedEnds);
-  var simplifiedPolygon = new ol.geom.Polygon(null);
-  simplifiedPolygon.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds);
-  return simplifiedPolygon;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.Polygon.prototype.getType = function() {
-  return ol.geom.GeometryType.POLYGON;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
-  return ol.geom.flat.intersectsextent.linearRings(
-      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
-};
-
-
-/**
- * Set the coordinates of the polygon.
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
-  } else {
-    this.setLayout(opt_layout, coordinates, 2);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    var ends = ol.geom.flat.deflate.coordinatess(
-        this.flatCoordinates, 0, coordinates, this.stride, this.ends_);
-    this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1];
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<number>} ends Ends.
- */
-ol.geom.Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
-  if (!flatCoordinates) {
-    ol.DEBUG && console.assert(ends && ends.length === 0,
-        'ends must be an empty array');
-  } else if (ends.length === 0) {
-    ol.DEBUG && console.assert(flatCoordinates.length === 0,
-        'flatCoordinates should be an empty array');
-  } else {
-    ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1],
-        'the length of flatCoordinates should be the last entry of ends');
-  }
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.ends_ = ends;
-  this.changed();
-};
-
-
-/**
- * Create an approximation of a circle on the surface of a sphere.
- * @param {ol.Sphere} sphere The sphere.
- * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees).
- * @param {number} radius The great-circle distance from the center to
- *     the polygon vertices.
- * @param {number=} opt_n Optional number of vertices for the resulting
- *     polygon. Default is `32`.
- * @return {ol.geom.Polygon} The "circular" polygon.
- * @api stable
- */
-ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) {
-  var n = opt_n ? opt_n : 32;
-  /** @type {Array.<number>} */
-  var flatCoordinates = [];
-  var i;
-  for (i = 0; i < n; ++i) {
-    ol.array.extend(
-        flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n));
-  }
-  flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
-  var polygon = new ol.geom.Polygon(null);
-  polygon.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
-  return polygon;
-};
-
-
-/**
- * Create a polygon from an extent. The layout used is `XY`.
- * @param {ol.Extent} extent The extent.
- * @return {ol.geom.Polygon} The polygon.
- * @api
- */
-ol.geom.Polygon.fromExtent = function(extent) {
-  var minX = extent[0];
-  var minY = extent[1];
-  var maxX = extent[2];
-  var maxY = extent[3];
-  var flatCoordinates =
-      [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY];
-  var polygon = new ol.geom.Polygon(null);
-  polygon.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
-  return polygon;
-};
-
-
-/**
- * Create a regular polygon from a circle.
- * @param {ol.geom.Circle} circle Circle geometry.
- * @param {number=} opt_sides Number of sides of the polygon. Default is 32.
- * @param {number=} opt_angle Start angle for the first vertex of the polygon in
- *     radians. Default is 0.
- * @return {ol.geom.Polygon} Polygon geometry.
- * @api
- */
-ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) {
-  var sides = opt_sides ? opt_sides : 32;
-  var stride = circle.getStride();
-  var layout = circle.getLayout();
-  var polygon = new ol.geom.Polygon(null, layout);
-  var arrayLength = stride * (sides + 1);
-  var flatCoordinates = new Array(arrayLength);
-  for (var i = 0; i < arrayLength; i++) {
-    flatCoordinates[i] = 0;
-  }
-  var ends = [flatCoordinates.length];
-  polygon.setFlatCoordinates(layout, flatCoordinates, ends);
-  ol.geom.Polygon.makeRegular(
-      polygon, circle.getCenter(), circle.getRadius(), opt_angle);
-  return polygon;
-};
-
-
-/**
- * Modify the coordinates of a polygon to make it a regular polygon.
- * @param {ol.geom.Polygon} polygon Polygon geometry.
- * @param {ol.Coordinate} center Center of the regular polygon.
- * @param {number} radius Radius of the regular polygon.
- * @param {number=} opt_angle Start angle for the first vertex of the polygon in
- *     radians. Default is 0.
- */
-ol.geom.Polygon.makeRegular = function(polygon, center, radius, opt_angle) {
-  var flatCoordinates = polygon.getFlatCoordinates();
-  var layout = polygon.getLayout();
-  var stride = polygon.getStride();
-  var ends = polygon.getEnds();
-  ol.DEBUG && console.assert(ends.length === 1, 'only 1 ring is supported');
-  var sides = flatCoordinates.length / stride - 1;
-  var startAngle = opt_angle ? opt_angle : 0;
-  var angle, offset;
-  for (var i = 0; i <= sides; ++i) {
-    offset = i * stride;
-    angle = startAngle + (ol.math.modulo(i, sides) * 2 * Math.PI / sides);
-    flatCoordinates[offset] = center[0] + (radius * Math.cos(angle));
-    flatCoordinates[offset + 1] = center[1] + (radius * Math.sin(angle));
-  }
-  polygon.setFlatCoordinates(layout, flatCoordinates, ends);
-};
-
-goog.provide('ol.View');
-
-goog.require('ol');
-goog.require('ol.CenterConstraint');
-goog.require('ol.Constraints');
-goog.require('ol.Object');
-goog.require('ol.ResolutionConstraint');
-goog.require('ol.RotationConstraint');
-goog.require('ol.array');
-goog.require('ol.asserts');
-goog.require('ol.coordinate');
-goog.require('ol.extent');
-goog.require('ol.geom.Polygon');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.proj');
-goog.require('ol.proj.METERS_PER_UNIT');
-goog.require('ol.proj.Units');
-
-
-/**
- * @classdesc
- * An ol.View object represents a simple 2D view of the map.
- *
- * This is the object to act upon to change the center, resolution,
- * and rotation of the map.
- *
- * ### The view states
- *
- * An `ol.View` is determined by three states: `center`, `resolution`,
- * and `rotation`. Each state has a corresponding getter and setter, e.g.
- * `getCenter` and `setCenter` for the `center` state.
- *
- * An `ol.View` has a `projection`. The projection determines the
- * coordinate system of the center, and its units determine the units of the
- * resolution (projection units per pixel). The default projection is
- * Spherical Mercator (EPSG:3857).
- *
- * ### The constraints
- *
- * `setCenter`, `setResolution` and `setRotation` can be used to change the
- * states of the view. Any value can be passed to the setters. And the value
- * that is passed to a setter will effectively be the value set in the view,
- * and returned by the corresponding getter.
- *
- * But an `ol.View` object also has a *resolution constraint*, a
- * *rotation constraint* and a *center constraint*.
- *
- * As said above, no constraints are applied when the setters are used to set
- * new states for the view. Applying constraints is done explicitly through
- * the use of the `constrain*` functions (`constrainResolution` and
- * `constrainRotation` and `constrainCenter`).
- *
- * The main users of the constraints are the interactions and the
- * controls. For example, double-clicking on the map changes the view to
- * the "next" resolution. And releasing the fingers after pinch-zooming
- * snaps to the closest resolution (with an animation).
- *
- * The *resolution constraint* snaps to specific resolutions. It is
- * determined by the following options: `resolutions`, `maxResolution`,
- * `maxZoom`, and `zoomFactor`. If `resolutions` is set, the other three
- * options are ignored. See documentation for each option for more
- * information.
- *
- * The *rotation constraint* snaps to specific angles. It is determined
- * by the following options: `enableRotation` and `constrainRotation`.
- * By default the rotation value is snapped to zero when approaching the
- * horizontal.
- *
- * The *center constraint* is determined by the `extent` option. By
- * default the center is not constrained at all.
- *
- * @constructor
- * @extends {ol.Object}
- * @param {olx.ViewOptions=} opt_options View options.
- * @api stable
- */
-ol.View = function(opt_options) {
-  ol.Object.call(this);
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.hints_ = [0, 0];
-
-  /**
-   * @type {Object.<string, *>}
-   */
-  var properties = {};
-  properties[ol.View.Property.CENTER] = options.center !== undefined ?
-      options.center : null;
-
-  /**
-   * @private
-   * @const
-   * @type {ol.proj.Projection}
-   */
-  this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857');
-
-  var resolutionConstraintInfo = ol.View.createResolutionConstraint_(
-      options);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxResolution_ = resolutionConstraintInfo.maxResolution;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minResolution_ = resolutionConstraintInfo.minResolution;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.zoomFactor_ = resolutionConstraintInfo.zoomFactor;
-
-  /**
-   * @private
-   * @type {Array.<number>|undefined}
-   */
-  this.resolutions_ = options.resolutions;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minZoom_ = resolutionConstraintInfo.minZoom;
-
-  var centerConstraint = ol.View.createCenterConstraint_(options);
-  var resolutionConstraint = resolutionConstraintInfo.constraint;
-  var rotationConstraint = ol.View.createRotationConstraint_(options);
-
-  /**
-   * @private
-   * @type {ol.Constraints}
-   */
-  this.constraints_ = new ol.Constraints(
-      centerConstraint, resolutionConstraint, rotationConstraint);
-
-  if (options.resolution !== undefined) {
-    properties[ol.View.Property.RESOLUTION] = options.resolution;
-  } else if (options.zoom !== undefined) {
-    properties[ol.View.Property.RESOLUTION] = this.constrainResolution(
-        this.maxResolution_, options.zoom - this.minZoom_);
-  }
-  properties[ol.View.Property.ROTATION] =
-      options.rotation !== undefined ? options.rotation : 0;
-  this.setProperties(properties);
-};
-ol.inherits(ol.View, ol.Object);
-
-
-/**
- * @param {number} rotation Target rotation.
- * @param {ol.Coordinate} anchor Rotation anchor.
- * @return {ol.Coordinate|undefined} Center for rotation and anchor.
- */
-ol.View.prototype.calculateCenterRotate = function(rotation, anchor) {
-  var center;
-  var currentCenter = this.getCenter();
-  if (currentCenter !== undefined) {
-    center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]];
-    ol.coordinate.rotate(center, rotation - this.getRotation());
-    ol.coordinate.add(center, anchor);
-  }
-  return center;
-};
-
-
-/**
- * @param {number} resolution Target resolution.
- * @param {ol.Coordinate} anchor Zoom anchor.
- * @return {ol.Coordinate|undefined} Center for resolution and anchor.
- */
-ol.View.prototype.calculateCenterZoom = function(resolution, anchor) {
-  var center;
-  var currentCenter = this.getCenter();
-  var currentResolution = this.getResolution();
-  if (currentCenter !== undefined && currentResolution !== undefined) {
-    var x = anchor[0] -
-        resolution * (anchor[0] - currentCenter[0]) / currentResolution;
-    var y = anchor[1] -
-        resolution * (anchor[1] - currentCenter[1]) / currentResolution;
-    center = [x, y];
-  }
-  return center;
-};
-
-
-/**
- * Get the constrained center of this view.
- * @param {ol.Coordinate|undefined} center Center.
- * @return {ol.Coordinate|undefined} Constrained center.
- * @api
- */
-ol.View.prototype.constrainCenter = function(center) {
-  return this.constraints_.center(center);
-};
-
-
-/**
- * Get the constrained resolution of this view.
- * @param {number|undefined} resolution Resolution.
- * @param {number=} opt_delta Delta. Default is `0`.
- * @param {number=} opt_direction Direction. Default is `0`.
- * @return {number|undefined} Constrained resolution.
- * @api
- */
-ol.View.prototype.constrainResolution = function(
-    resolution, opt_delta, opt_direction) {
-  var delta = opt_delta || 0;
-  var direction = opt_direction || 0;
-  return this.constraints_.resolution(resolution, delta, direction);
-};
-
-
-/**
- * Get the constrained rotation of this view.
- * @param {number|undefined} rotation Rotation.
- * @param {number=} opt_delta Delta. Default is `0`.
- * @return {number|undefined} Constrained rotation.
- * @api
- */
-ol.View.prototype.constrainRotation = function(rotation, opt_delta) {
-  var delta = opt_delta || 0;
-  return this.constraints_.rotation(rotation, delta);
-};
-
-
-/**
- * Get the view center.
- * @return {ol.Coordinate|undefined} The center of the view.
- * @observable
- * @api stable
- */
-ol.View.prototype.getCenter = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.View.Property.CENTER));
-};
-
-
-/**
- * @param {Array.<number>=} opt_hints Destination array.
- * @return {Array.<number>} Hint.
- */
-ol.View.prototype.getHints = function(opt_hints) {
-  if (opt_hints !== undefined) {
-    opt_hints[0] = this.hints_[0];
-    opt_hints[1] = this.hints_[1];
-    return opt_hints;
-  } else {
-    return this.hints_.slice();
-  }
-};
-
-
-/**
- * Calculate the extent for the current view state and the passed size.
- * The size is the pixel dimensions of the box into which the calculated extent
- * should fit. In most cases you want to get the extent of the entire map,
- * that is `map.getSize()`.
- * @param {ol.Size} size Box pixel size.
- * @return {ol.Extent} Extent.
- * @api stable
- */
-ol.View.prototype.calculateExtent = function(size) {
-  var center = /** @type {!ol.Coordinate} */ (this.getCenter());
-  ol.asserts.assert(center, 1); // The view center is not defined
-  var resolution = /** @type {!number} */ (this.getResolution());
-  ol.asserts.assert(resolution !== undefined, 2); // The view resolution is not defined
-  var rotation = /** @type {!number} */ (this.getRotation());
-  ol.asserts.assert(rotation !== undefined, 3); // The view rotation is not defined
-
-  return ol.extent.getForViewAndSize(center, resolution, rotation, size);
-};
-
-
-/**
- * Get the maximum resolution of the view.
- * @return {number} The maximum resolution of the view.
- * @api
- */
-ol.View.prototype.getMaxResolution = function() {
-  return this.maxResolution_;
-};
-
-
-/**
- * Get the minimum resolution of the view.
- * @return {number} The minimum resolution of the view.
- * @api
- */
-ol.View.prototype.getMinResolution = function() {
-  return this.minResolution_;
-};
-
-
-/**
- * Get the view projection.
- * @return {ol.proj.Projection} The projection of the view.
- * @api stable
- */
-ol.View.prototype.getProjection = function() {
-  return this.projection_;
-};
-
-
-/**
- * Get the view resolution.
- * @return {number|undefined} The resolution of the view.
- * @observable
- * @api stable
- */
-ol.View.prototype.getResolution = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.View.Property.RESOLUTION));
-};
-
-
-/**
- * Get the resolutions for the view. This returns the array of resolutions
- * passed to the constructor of the {ol.View}, or undefined if none were given.
- * @return {Array.<number>|undefined} The resolutions of the view.
- * @api stable
- */
-ol.View.prototype.getResolutions = function() {
-  return this.resolutions_;
-};
-
-
-/**
- * Get the resolution for a provided extent (in map units) and size (in pixels).
- * @param {ol.Extent} extent Extent.
- * @param {ol.Size} size Box pixel size.
- * @return {number} The resolution at which the provided extent will render at
- *     the given size.
- */
-ol.View.prototype.getResolutionForExtent = function(extent, size) {
-  var xResolution = ol.extent.getWidth(extent) / size[0];
-  var yResolution = ol.extent.getHeight(extent) / size[1];
-  return Math.max(xResolution, yResolution);
-};
-
-
-/**
- * Return a function that returns a value between 0 and 1 for a
- * resolution. Exponential scaling is assumed.
- * @param {number=} opt_power Power.
- * @return {function(number): number} Resolution for value function.
- */
-ol.View.prototype.getResolutionForValueFunction = function(opt_power) {
-  var power = opt_power || 2;
-  var maxResolution = this.maxResolution_;
-  var minResolution = this.minResolution_;
-  var max = Math.log(maxResolution / minResolution) / Math.log(power);
-  return (
-      /**
-       * @param {number} value Value.
-       * @return {number} Resolution.
-       */
-      function(value) {
-        var resolution = maxResolution / Math.pow(power, value * max);
-        ol.DEBUG && console.assert(resolution >= minResolution &&
-            resolution <= maxResolution,
-            'calculated resolution outside allowed bounds (%s <= %s <= %s)',
-            minResolution, resolution, maxResolution);
-        return resolution;
-      });
-};
-
-
-/**
- * Get the view rotation.
- * @return {number} The rotation of the view in radians.
- * @observable
- * @api stable
- */
-ol.View.prototype.getRotation = function() {
-  return /** @type {number} */ (this.get(ol.View.Property.ROTATION));
-};
-
-
-/**
- * Return a function that returns a resolution for a value between
- * 0 and 1. Exponential scaling is assumed.
- * @param {number=} opt_power Power.
- * @return {function(number): number} Value for resolution function.
- */
-ol.View.prototype.getValueForResolutionFunction = function(opt_power) {
-  var power = opt_power || 2;
-  var maxResolution = this.maxResolution_;
-  var minResolution = this.minResolution_;
-  var max = Math.log(maxResolution / minResolution) / Math.log(power);
-  return (
-      /**
-       * @param {number} resolution Resolution.
-       * @return {number} Value.
-       */
-      function(resolution) {
-        var value =
-            (Math.log(maxResolution / resolution) / Math.log(power)) / max;
-        ol.DEBUG && console.assert(value >= 0 && value <= 1,
-            'calculated value (%s) ouside allowed range (0-1)', value);
-        return value;
-      });
-};
-
-
-/**
- * @return {olx.ViewState} View state.
- */
-ol.View.prototype.getState = function() {
-  ol.DEBUG && console.assert(this.isDef(),
-      'the view was not defined (had no center and/or resolution)');
-  var center = /** @type {ol.Coordinate} */ (this.getCenter());
-  var projection = this.getProjection();
-  var resolution = /** @type {number} */ (this.getResolution());
-  var rotation = this.getRotation();
-  return /** @type {olx.ViewState} */ ({
-    center: center.slice(),
-    projection: projection !== undefined ? projection : null,
-    resolution: resolution,
-    rotation: rotation
-  });
-};
-
-
-/**
- * Get the current zoom level. Return undefined if the current
- * resolution is undefined or not within the "resolution constraints".
- * @return {number|undefined} Zoom.
- * @api stable
- */
-ol.View.prototype.getZoom = function() {
-  var zoom;
-  var resolution = this.getResolution();
-  if (resolution !== undefined &&
-      resolution >= this.minResolution_ && resolution <= this.maxResolution_) {
-    var offset = this.minZoom_ || 0;
-    var max, zoomFactor;
-    if (this.resolutions_) {
-      var nearest = ol.array.linearFindNearest(this.resolutions_, resolution, 1);
-      offset += nearest;
-      if (nearest == this.resolutions_.length - 1) {
-        return offset;
-      }
-      max = this.resolutions_[nearest];
-      zoomFactor = max / this.resolutions_[nearest + 1];
-    } else {
-      max = this.maxResolution_;
-      zoomFactor = this.zoomFactor_;
-    }
-    zoom = offset + Math.log(max / resolution) / Math.log(zoomFactor);
-  }
-  return zoom;
-};
-
-
-/**
- * Fit the given geometry or extent based on the given map size and border.
- * The size is pixel dimensions of the box to fit the extent into.
- * In most cases you will want to use the map size, that is `map.getSize()`.
- * Takes care of the map angle.
- * @param {ol.geom.SimpleGeometry|ol.Extent} geometry Geometry.
- * @param {ol.Size} size Box pixel size.
- * @param {olx.view.FitOptions=} opt_options Options.
- * @api
- */
-ol.View.prototype.fit = function(geometry, size, opt_options) {
-  if (!(geometry instanceof ol.geom.SimpleGeometry)) {
-    ol.asserts.assert(Array.isArray(geometry),
-        24); // Invalid extent or geometry provided as `geometry`
-    ol.asserts.assert(!ol.extent.isEmpty(geometry),
-        25); // Cannot fit empty extent provided as `geometry`
-    geometry = ol.geom.Polygon.fromExtent(geometry);
-  }
-
-  var options = opt_options || {};
-
-  var padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0];
-  var constrainResolution = options.constrainResolution !== undefined ?
-      options.constrainResolution : true;
-  var nearest = options.nearest !== undefined ? options.nearest : false;
-  var minResolution;
-  if (options.minResolution !== undefined) {
-    minResolution = options.minResolution;
-  } else if (options.maxZoom !== undefined) {
-    minResolution = this.constrainResolution(
-        this.maxResolution_, options.maxZoom - this.minZoom_, 0);
-  } else {
-    minResolution = 0;
-  }
-  var coords = geometry.getFlatCoordinates();
-
-  // calculate rotated extent
-  var rotation = this.getRotation();
-  ol.DEBUG && console.assert(rotation !== undefined, 'rotation was not defined');
-  var cosAngle = Math.cos(-rotation);
-  var sinAngle = Math.sin(-rotation);
-  var minRotX = +Infinity;
-  var minRotY = +Infinity;
-  var maxRotX = -Infinity;
-  var maxRotY = -Infinity;
-  var stride = geometry.getStride();
-  for (var i = 0, ii = coords.length; i < ii; i += stride) {
-    var rotX = coords[i] * cosAngle - coords[i + 1] * sinAngle;
-    var rotY = coords[i] * sinAngle + coords[i + 1] * cosAngle;
-    minRotX = Math.min(minRotX, rotX);
-    minRotY = Math.min(minRotY, rotY);
-    maxRotX = Math.max(maxRotX, rotX);
-    maxRotY = Math.max(maxRotY, rotY);
-  }
-
-  // calculate resolution
-  var resolution = this.getResolutionForExtent(
-      [minRotX, minRotY, maxRotX, maxRotY],
-      [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]);
-  resolution = isNaN(resolution) ? minResolution :
-      Math.max(resolution, minResolution);
-  if (constrainResolution) {
-    var constrainedResolution = this.constrainResolution(resolution, 0, 0);
-    if (!nearest && constrainedResolution < resolution) {
-      constrainedResolution = this.constrainResolution(
-          constrainedResolution, -1, 0);
-    }
-    resolution = constrainedResolution;
-  }
-  this.setResolution(resolution);
-
-  // calculate center
-  sinAngle = -sinAngle; // go back to original rotation
-  var centerRotX = (minRotX + maxRotX) / 2;
-  var centerRotY = (minRotY + maxRotY) / 2;
-  centerRotX += (padding[1] - padding[3]) / 2 * resolution;
-  centerRotY += (padding[0] - padding[2]) / 2 * resolution;
-  var centerX = centerRotX * cosAngle - centerRotY * sinAngle;
-  var centerY = centerRotY * cosAngle + centerRotX * sinAngle;
-
-  this.setCenter([centerX, centerY]);
-};
-
-
-/**
- * Center on coordinate and view position.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.Size} size Box pixel size.
- * @param {ol.Pixel} position Position on the view to center on.
- * @api
- */
-ol.View.prototype.centerOn = function(coordinate, size, position) {
-  // calculate rotated position
-  var rotation = this.getRotation();
-  var cosAngle = Math.cos(-rotation);
-  var sinAngle = Math.sin(-rotation);
-  var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
-  var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
-  var resolution = this.getResolution();
-  rotX += (size[0] / 2 - position[0]) * resolution;
-  rotY += (position[1] - size[1] / 2) * resolution;
-
-  // go back to original angle
-  sinAngle = -sinAngle; // go back to original rotation
-  var centerX = rotX * cosAngle - rotY * sinAngle;
-  var centerY = rotY * cosAngle + rotX * sinAngle;
-
-  this.setCenter([centerX, centerY]);
-};
-
-
-/**
- * @return {boolean} Is defined.
- */
-ol.View.prototype.isDef = function() {
-  return !!this.getCenter() && this.getResolution() !== undefined;
-};
-
-
-/**
- * Rotate the view around a given coordinate.
- * @param {number} rotation New rotation value for the view.
- * @param {ol.Coordinate=} opt_anchor The rotation center.
- * @api stable
- */
-ol.View.prototype.rotate = function(rotation, opt_anchor) {
-  if (opt_anchor !== undefined) {
-    var center = this.calculateCenterRotate(rotation, opt_anchor);
-    this.setCenter(center);
-  }
-  this.setRotation(rotation);
-};
-
-
-/**
- * Set the center of the current view.
- * @param {ol.Coordinate|undefined} center The center of the view.
- * @observable
- * @api stable
- */
-ol.View.prototype.setCenter = function(center) {
-  this.set(ol.View.Property.CENTER, center);
-};
-
-
-/**
- * @param {ol.View.Hint} hint Hint.
- * @param {number} delta Delta.
- * @return {number} New value.
- */
-ol.View.prototype.setHint = function(hint, delta) {
-  ol.DEBUG && console.assert(0 <= hint && hint < this.hints_.length,
-      'illegal hint (%s), must be between 0 and %s', hint, this.hints_.length);
-  this.hints_[hint] += delta;
-  ol.DEBUG && console.assert(this.hints_[hint] >= 0,
-      'Hint at %s must be positive, was %s', hint, this.hints_[hint]);
-  return this.hints_[hint];
-};
-
-
-/**
- * Set the resolution for this view.
- * @param {number|undefined} resolution The resolution of the view.
- * @observable
- * @api stable
- */
-ol.View.prototype.setResolution = function(resolution) {
-  this.set(ol.View.Property.RESOLUTION, resolution);
-};
-
-
-/**
- * Set the rotation for this view.
- * @param {number} rotation The rotation of the view in radians.
- * @observable
- * @api stable
- */
-ol.View.prototype.setRotation = function(rotation) {
-  this.set(ol.View.Property.ROTATION, rotation);
-};
-
-
-/**
- * Zoom to a specific zoom level.
- * @param {number} zoom Zoom level.
- * @api stable
- */
-ol.View.prototype.setZoom = function(zoom) {
-  var resolution = this.constrainResolution(
-      this.maxResolution_, zoom - this.minZoom_, 0);
-  this.setResolution(resolution);
-};
-
-
-/**
- * @param {olx.ViewOptions} options View options.
- * @private
- * @return {ol.CenterConstraintType} The constraint.
- */
-ol.View.createCenterConstraint_ = function(options) {
-  if (options.extent !== undefined) {
-    return ol.CenterConstraint.createExtent(options.extent);
-  } else {
-    return ol.CenterConstraint.none;
-  }
-};
-
-
-/**
- * @private
- * @param {olx.ViewOptions} options View options.
- * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number,
- *     minResolution: number, zoomFactor: number}} The constraint.
- */
-ol.View.createResolutionConstraint_ = function(options) {
-  var resolutionConstraint;
-  var maxResolution;
-  var minResolution;
-
-  // TODO: move these to be ol constants
-  // see https://github.com/openlayers/ol3/issues/2076
-  var defaultMaxZoom = 28;
-  var defaultZoomFactor = 2;
-
-  var minZoom = options.minZoom !== undefined ?
-      options.minZoom : ol.DEFAULT_MIN_ZOOM;
-
-  var maxZoom = options.maxZoom !== undefined ?
-      options.maxZoom : defaultMaxZoom;
-
-  var zoomFactor = options.zoomFactor !== undefined ?
-      options.zoomFactor : defaultZoomFactor;
-
-  if (options.resolutions !== undefined) {
-    var resolutions = options.resolutions;
-    maxResolution = resolutions[0];
-    minResolution = resolutions[resolutions.length - 1];
-    resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions(
-        resolutions);
-  } else {
-    // calculate the default min and max resolution
-    var projection = ol.proj.createProjection(options.projection, 'EPSG:3857');
-    var extent = projection.getExtent();
-    var size = !extent ?
-        // use an extent that can fit the whole world if need be
-        360 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] /
-            projection.getMetersPerUnit() :
-        Math.max(ol.extent.getWidth(extent), ol.extent.getHeight(extent));
-
-    var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow(
-        defaultZoomFactor, ol.DEFAULT_MIN_ZOOM);
-
-    var defaultMinResolution = defaultMaxResolution / Math.pow(
-        defaultZoomFactor, defaultMaxZoom - ol.DEFAULT_MIN_ZOOM);
-
-    // user provided maxResolution takes precedence
-    maxResolution = options.maxResolution;
-    if (maxResolution !== undefined) {
-      minZoom = 0;
-    } else {
-      maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom);
-    }
-
-    // user provided minResolution takes precedence
-    minResolution = options.minResolution;
-    if (minResolution === undefined) {
-      if (options.maxZoom !== undefined) {
-        if (options.maxResolution !== undefined) {
-          minResolution = maxResolution / Math.pow(zoomFactor, maxZoom);
-        } else {
-          minResolution = defaultMaxResolution / Math.pow(zoomFactor, maxZoom);
-        }
-      } else {
-        minResolution = defaultMinResolution;
-      }
-    }
-
-    // given discrete zoom levels, minResolution may be different than provided
-    maxZoom = minZoom + Math.floor(
-        Math.log(maxResolution / minResolution) / Math.log(zoomFactor));
-    minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom);
-
-    resolutionConstraint = ol.ResolutionConstraint.createSnapToPower(
-        zoomFactor, maxResolution, maxZoom - minZoom);
-  }
-  return {constraint: resolutionConstraint, maxResolution: maxResolution,
-    minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor};
-};
-
-
-/**
- * @private
- * @param {olx.ViewOptions} options View options.
- * @return {ol.RotationConstraintType} Rotation constraint.
- */
-ol.View.createRotationConstraint_ = function(options) {
-  var enableRotation = options.enableRotation !== undefined ?
-      options.enableRotation : true;
-  if (enableRotation) {
-    var constrainRotation = options.constrainRotation;
-    if (constrainRotation === undefined || constrainRotation === true) {
-      return ol.RotationConstraint.createSnapToZero();
-    } else if (constrainRotation === false) {
-      return ol.RotationConstraint.none;
-    } else if (typeof constrainRotation === 'number') {
-      return ol.RotationConstraint.createSnapToN(constrainRotation);
-    } else {
-      ol.DEBUG && console.assert(false,
-          'illegal option for constrainRotation (%s)', constrainRotation);
-      return ol.RotationConstraint.none;
-    }
-  } else {
-    return ol.RotationConstraint.disable;
-  }
-};
-
-
-/**
- * @enum {string}
- */
-ol.View.Property = {
-  CENTER: 'center',
-  RESOLUTION: 'resolution',
-  ROTATION: 'rotation'
-};
-
-
-/**
- * @enum {number}
- */
-ol.View.Hint = {
-  ANIMATING: 0,
-  INTERACTING: 1
-};
-
-goog.provide('ol.easing');
-
-
-/**
- * Start slow and speed up.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
- */
-ol.easing.easeIn = function(t) {
-  return Math.pow(t, 3);
-};
-
-
-/**
- * Start fast and slow down.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
- */
-ol.easing.easeOut = function(t) {
-  return 1 - ol.easing.easeIn(1 - t);
-};
-
-
-/**
- * Start slow, speed up, and then slow down again.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
- */
-ol.easing.inAndOut = function(t) {
-  return 3 * t * t - 2 * t * t * t;
-};
-
-
-/**
- * Maintain a constant speed over time.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
- */
-ol.easing.linear = function(t) {
-  return t;
-};
-
-
-/**
- * Start slow, speed up, and at the very end slow down again.  This has the
- * same general behavior as {@link ol.easing.inAndOut}, but the final slowdown
- * is delayed.
- * @param {number} t Input between 0 and 1.
- * @return {number} Output between 0 and 1.
- * @api
- */
-ol.easing.upAndDown = function(t) {
-  if (t < 0.5) {
-    return ol.easing.inAndOut(2 * t);
-  } else {
-    return 1 - ol.easing.inAndOut(2 * (t - 0.5));
-  }
-};
-
-goog.provide('ol.animation');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.coordinate');
-goog.require('ol.easing');
-
-
-/**
- * Generate an animated transition that will "bounce" the resolution as it
- * approaches the final value.
- * @param {olx.animation.BounceOptions} options Bounce options.
- * @return {ol.PreRenderFunction} Pre-render function.
- * @api
- */
-ol.animation.bounce = function(options) {
-  var resolution = options.resolution;
-  var start = options.start ? options.start : Date.now();
-  var duration = options.duration !== undefined ? options.duration : 1000;
-  var easing = options.easing ?
-      options.easing : ol.easing.upAndDown;
-  return (
-      /**
-       * @param {ol.Map} map Map.
-       * @param {?olx.FrameState} frameState Frame state.
-       * @return {boolean} Run this function in the next frame.
-       */
-      function(map, frameState) {
-        if (frameState.time < start) {
-          frameState.animate = true;
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else if (frameState.time < start + duration) {
-          var delta = easing((frameState.time - start) / duration);
-          var deltaResolution = resolution - frameState.viewState.resolution;
-          frameState.animate = true;
-          frameState.viewState.resolution += delta * deltaResolution;
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else {
-          return false;
-        }
-      });
-};
-
-
-/**
- * Generate an animated transition while updating the view center.
- * @param {olx.animation.PanOptions} options Pan options.
- * @return {ol.PreRenderFunction} Pre-render function.
- * @api
- */
-ol.animation.pan = function(options) {
-  var source = options.source;
-  var start = options.start ? options.start : Date.now();
-  var sourceX = source[0];
-  var sourceY = source[1];
-  var duration = options.duration !== undefined ? options.duration : 1000;
-  var easing = options.easing ?
-      options.easing : ol.easing.inAndOut;
-  return (
-      /**
-       * @param {ol.Map} map Map.
-       * @param {?olx.FrameState} frameState Frame state.
-       * @return {boolean} Run this function in the next frame.
-       */
-      function(map, frameState) {
-        if (frameState.time < start) {
-          frameState.animate = true;
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else if (frameState.time < start + duration) {
-          var delta = 1 - easing((frameState.time - start) / duration);
-          var deltaX = sourceX - frameState.viewState.center[0];
-          var deltaY = sourceY - frameState.viewState.center[1];
-          frameState.animate = true;
-          frameState.viewState.center[0] += delta * deltaX;
-          frameState.viewState.center[1] += delta * deltaY;
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else {
-          return false;
-        }
-      });
-};
-
-
-/**
- * Generate an animated transition while updating the view rotation.
- * @param {olx.animation.RotateOptions} options Rotate options.
- * @return {ol.PreRenderFunction} Pre-render function.
- * @api
- */
-ol.animation.rotate = function(options) {
-  var sourceRotation = options.rotation ? options.rotation : 0;
-  var start = options.start ? options.start : Date.now();
-  var duration = options.duration !== undefined ? options.duration : 1000;
-  var easing = options.easing ?
-      options.easing : ol.easing.inAndOut;
-  var anchor = options.anchor ?
-      options.anchor : null;
-
-  return (
-      /**
-       * @param {ol.Map} map Map.
-       * @param {?olx.FrameState} frameState Frame state.
-       * @return {boolean} Run this function in the next frame.
-       */
-      function(map, frameState) {
-        if (frameState.time < start) {
-          frameState.animate = true;
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else if (frameState.time < start + duration) {
-          var delta = 1 - easing((frameState.time - start) / duration);
-          var deltaRotation =
-              (sourceRotation - frameState.viewState.rotation) * delta;
-          frameState.animate = true;
-          frameState.viewState.rotation += deltaRotation;
-          if (anchor) {
-            var center = frameState.viewState.center;
-            ol.coordinate.sub(center, anchor);
-            ol.coordinate.rotate(center, deltaRotation);
-            ol.coordinate.add(center, anchor);
-          }
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else {
-          return false;
-        }
-      });
-};
-
-
-/**
- * Generate an animated transition while updating the view resolution.
- * @param {olx.animation.ZoomOptions} options Zoom options.
- * @return {ol.PreRenderFunction} Pre-render function.
- * @api
- */
-ol.animation.zoom = function(options) {
-  var sourceResolution = options.resolution;
-  var start = options.start ? options.start : Date.now();
-  var duration = options.duration !== undefined ? options.duration : 1000;
-  var easing = options.easing ?
-      options.easing : ol.easing.inAndOut;
-  return (
-      /**
-       * @param {ol.Map} map Map.
-       * @param {?olx.FrameState} frameState Frame state.
-       * @return {boolean} Run this function in the next frame.
-       */
-      function(map, frameState) {
-        if (frameState.time < start) {
-          frameState.animate = true;
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else if (frameState.time < start + duration) {
-          var delta = 1 - easing((frameState.time - start) / duration);
-          var deltaResolution =
-              sourceResolution - frameState.viewState.resolution;
-          frameState.animate = true;
-          frameState.viewState.resolution += delta * deltaResolution;
-          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
-          return true;
-        } else {
-          return false;
-        }
-      });
-};
-
-goog.provide('ol.TileRange');
-
-
-/**
- * A representation of a contiguous block of tiles.  A tile range is specified
- * by its min/max tile coordinates and is inclusive of coordinates.
- *
- * @constructor
- * @param {number} minX Minimum X.
- * @param {number} maxX Maximum X.
- * @param {number} minY Minimum Y.
- * @param {number} maxY Maximum Y.
- * @struct
- */
-ol.TileRange = function(minX, maxX, minY, maxY) {
-
-  /**
-   * @type {number}
-   */
-  this.minX = minX;
-
-  /**
-   * @type {number}
-   */
-  this.maxX = maxX;
-
-  /**
-   * @type {number}
-   */
-  this.minY = minY;
-
-  /**
-   * @type {number}
-   */
-  this.maxY = maxY;
-
-};
-
-
-/**
- * @param {number} minX Minimum X.
- * @param {number} maxX Maximum X.
- * @param {number} minY Minimum Y.
- * @param {number} maxY Maximum Y.
- * @param {ol.TileRange|undefined} tileRange TileRange.
- * @return {ol.TileRange} Tile range.
- */
-ol.TileRange.createOrUpdate = function(minX, maxX, minY, maxY, tileRange) {
-  if (tileRange !== undefined) {
-    tileRange.minX = minX;
-    tileRange.maxX = maxX;
-    tileRange.minY = minY;
-    tileRange.maxY = maxY;
-    return tileRange;
-  } else {
-    return new ol.TileRange(minX, maxX, minY, maxY);
-  }
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @return {boolean} Contains tile coordinate.
- */
-ol.TileRange.prototype.contains = function(tileCoord) {
-  return this.containsXY(tileCoord[1], tileCoord[2]);
-};
-
-
-/**
- * @param {ol.TileRange} tileRange Tile range.
- * @return {boolean} Contains.
- */
-ol.TileRange.prototype.containsTileRange = function(tileRange) {
-  return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX &&
-      this.minY <= tileRange.minY && tileRange.maxY <= this.maxY;
-};
-
-
-/**
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @return {boolean} Contains coordinate.
- */
-ol.TileRange.prototype.containsXY = function(x, y) {
-  return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY;
-};
-
-
-/**
- * @param {ol.TileRange} tileRange Tile range.
- * @return {boolean} Equals.
- */
-ol.TileRange.prototype.equals = function(tileRange) {
-  return this.minX == tileRange.minX && this.minY == tileRange.minY &&
-      this.maxX == tileRange.maxX && this.maxY == tileRange.maxY;
-};
-
-
-/**
- * @param {ol.TileRange} tileRange Tile range.
- */
-ol.TileRange.prototype.extend = function(tileRange) {
-  if (tileRange.minX < this.minX) {
-    this.minX = tileRange.minX;
-  }
-  if (tileRange.maxX > this.maxX) {
-    this.maxX = tileRange.maxX;
-  }
-  if (tileRange.minY < this.minY) {
-    this.minY = tileRange.minY;
-  }
-  if (tileRange.maxY > this.maxY) {
-    this.maxY = tileRange.maxY;
-  }
-};
-
-
-/**
- * @return {number} Height.
- */
-ol.TileRange.prototype.getHeight = function() {
-  return this.maxY - this.minY + 1;
-};
-
-
-/**
- * @return {ol.Size} Size.
- */
-ol.TileRange.prototype.getSize = function() {
-  return [this.getWidth(), this.getHeight()];
-};
-
-
-/**
- * @return {number} Width.
- */
-ol.TileRange.prototype.getWidth = function() {
-  return this.maxX - this.minX + 1;
-};
-
-
-/**
- * @param {ol.TileRange} tileRange Tile range.
- * @return {boolean} Intersects.
- */
-ol.TileRange.prototype.intersects = function(tileRange) {
-  return this.minX <= tileRange.maxX &&
-      this.maxX >= tileRange.minX &&
-      this.minY <= tileRange.maxY &&
-      this.maxY >= tileRange.minY;
-};
-
-goog.provide('ol.size');
-
-
-/**
- * Returns a buffered size.
- * @param {ol.Size} size Size.
- * @param {number} buffer Buffer.
- * @param {ol.Size=} opt_size Optional reusable size array.
- * @return {ol.Size} The buffered size.
- */
-ol.size.buffer = function(size, buffer, opt_size) {
-  if (opt_size === undefined) {
-    opt_size = [0, 0];
-  }
-  opt_size[0] = size[0] + 2 * buffer;
-  opt_size[1] = size[1] + 2 * buffer;
-  return opt_size;
-};
-
-
-/**
- * Determines if a size has a positive area.
- * @param {ol.Size} size The size to test.
- * @return {boolean} The size has a positive area.
- */
-ol.size.hasArea = function(size) {
-  return size[0] > 0 && size[1] > 0;
-};
-
-
-/**
- * Returns a size scaled by a ratio. The result will be an array of integers.
- * @param {ol.Size} size Size.
- * @param {number} ratio Ratio.
- * @param {ol.Size=} opt_size Optional reusable size array.
- * @return {ol.Size} The scaled size.
- */
-ol.size.scale = function(size, ratio, opt_size) {
-  if (opt_size === undefined) {
-    opt_size = [0, 0];
-  }
-  opt_size[0] = (size[0] * ratio + 0.5) | 0;
-  opt_size[1] = (size[1] * ratio + 0.5) | 0;
-  return opt_size;
-};
-
-
-/**
- * Returns an `ol.Size` array for the passed in number (meaning: square) or
- * `ol.Size` array.
- * (meaning: non-square),
- * @param {number|ol.Size} size Width and height.
- * @param {ol.Size=} opt_size Optional reusable size array.
- * @return {ol.Size} Size.
- * @api stable
- */
-ol.size.toSize = function(size, opt_size) {
-  if (Array.isArray(size)) {
-    return size;
-  } else {
-    if (opt_size === undefined) {
-      opt_size = [size, size];
-    } else {
-      opt_size[0] = opt_size[1] = /** @type {number} */ (size);
-    }
-    return opt_size;
-  }
-};
-
-goog.provide('ol.tilecoord');
-
-
-/**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @param {ol.TileCoord=} opt_tileCoord Tile coordinate.
- * @return {ol.TileCoord} Tile coordinate.
- */
-ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) {
-  if (opt_tileCoord !== undefined) {
-    opt_tileCoord[0] = z;
-    opt_tileCoord[1] = x;
-    opt_tileCoord[2] = y;
-    return opt_tileCoord;
-  } else {
-    return [z, x, y];
-  }
-};
-
-
-/**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {string} Key.
- */
-ol.tilecoord.getKeyZXY = function(z, x, y) {
-  return z + '/' + x + '/' + y;
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coord.
- * @return {number} Hash.
- */
-ol.tilecoord.hash = function(tileCoord) {
-  return (tileCoord[1] << tileCoord[0]) + tileCoord[2];
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coord.
- * @return {string} Quad key.
- */
-ol.tilecoord.quadKey = function(tileCoord) {
-  var z = tileCoord[0];
-  var digits = new Array(z);
-  var mask = 1 << (z - 1);
-  var i, charCode;
-  for (i = 0; i < z; ++i) {
-    // 48 is charCode for 0 - '0'.charCodeAt(0)
-    charCode = 48;
-    if (tileCoord[1] & mask) {
-      charCode += 1;
-    }
-    if (tileCoord[2] & mask) {
-      charCode += 2;
-    }
-    digits[i] = String.fromCharCode(charCode);
-    mask >>= 1;
-  }
-  return digits.join('');
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @return {boolean} Tile coordinate is within extent and zoom level range.
- */
-ol.tilecoord.withinExtentAndZ = function(tileCoord, tileGrid) {
-  var z = tileCoord[0];
-  var x = tileCoord[1];
-  var y = tileCoord[2];
-
-  if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) {
-    return false;
-  }
-  var extent = tileGrid.getExtent();
-  var tileRange;
-  if (!extent) {
-    tileRange = tileGrid.getFullTileRange(z);
-  } else {
-    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
-  }
-  if (!tileRange) {
-    return true;
-  } else {
-    return tileRange.containsXY(x, y);
-  }
-};
-
-goog.provide('ol.tilegrid.TileGrid');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.TileRange');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.size');
-goog.require('ol.tilecoord');
-
-
-/**
- * @classdesc
- * Base class for setting the grid pattern for sources accessing tiled-image
- * servers.
- *
- * @constructor
- * @param {olx.tilegrid.TileGridOptions} options Tile grid options.
- * @struct
- * @api stable
- */
-ol.tilegrid.TileGrid = function(options) {
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.minZoom = options.minZoom !== undefined ? options.minZoom : 0;
-
-  /**
-   * @private
-   * @type {!Array.<number>}
-   */
-  this.resolutions_ = options.resolutions;
-  ol.asserts.assert(ol.array.isSorted(this.resolutions_, function(a, b) {
-    return b - a;
-  }, true), 17); // `resolutions` must be sorted in descending order
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.maxZoom = this.resolutions_.length - 1;
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.origin_ = options.origin !== undefined ? options.origin : null;
-
-  /**
-   * @private
-   * @type {Array.<ol.Coordinate>}
-   */
-  this.origins_ = null;
-  if (options.origins !== undefined) {
-    this.origins_ = options.origins;
-    ol.asserts.assert(this.origins_.length == this.resolutions_.length,
-        20); // Number of `origins` and `resolutions` must be equal
-  }
-
-  var extent = options.extent;
-
-  if (extent !== undefined &&
-      !this.origin_ && !this.origins_) {
-    this.origin_ = ol.extent.getTopLeft(extent);
-  }
-
-  ol.asserts.assert(
-      (!this.origin_ && this.origins_) || (this.origin_ && !this.origins_),
-      18); // Either `origin` or `origins` must be configured, never both
-
-  /**
-   * @private
-   * @type {Array.<number|ol.Size>}
-   */
-  this.tileSizes_ = null;
-  if (options.tileSizes !== undefined) {
-    this.tileSizes_ = options.tileSizes;
-    ol.asserts.assert(this.tileSizes_.length == this.resolutions_.length,
-        19); // Number of `tileSizes` and `resolutions` must be equal
-  }
-
-  /**
-   * @private
-   * @type {number|ol.Size}
-   */
-  this.tileSize_ = options.tileSize !== undefined ?
-      options.tileSize :
-      !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null;
-  ol.asserts.assert(
-      (!this.tileSize_ && this.tileSizes_) ||
-      (this.tileSize_ && !this.tileSizes_),
-      22); // Either `tileSize` or `tileSizes` must be configured, never both
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = extent !== undefined ? extent : null;
-
-
-  /**
-   * @private
-   * @type {Array.<ol.TileRange>}
-   */
-  this.fullTileRanges_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.tmpSize_ = [0, 0];
-
-  if (options.sizes !== undefined) {
-    ol.DEBUG && console.assert(options.sizes.length == this.resolutions_.length,
-        'number of sizes and resolutions must be equal');
-    this.fullTileRanges_ = options.sizes.map(function(size, z) {
-      ol.DEBUG && console.assert(size[0] !== 0, 'width must not be 0');
-      ol.DEBUG && console.assert(size[1] !== 0, 'height must not be 0');
-      var tileRange = new ol.TileRange(
-          Math.min(0, size[0]), Math.max(size[0] - 1, -1),
-          Math.min(0, size[1]), Math.max(size[1] - 1, -1));
-      return tileRange;
-    }, this);
-  } else if (extent) {
-    this.calculateTileRanges_(extent);
-  }
-
-};
-
-
-/**
- * @private
- * @type {ol.TileCoord}
- */
-ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];
-
-
-/**
- * Call a function with each tile coordinate for a given extent and zoom level.
- *
- * @param {ol.Extent} extent Extent.
- * @param {number} zoom Zoom level.
- * @param {function(ol.TileCoord)} callback Function called with each tile coordinate.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.forEachTileCoord = function(extent, zoom, callback) {
-  var tileRange = this.getTileRangeForExtentAndZ(extent, zoom);
-  for (var i = tileRange.minX, ii = tileRange.maxX; i <= ii; ++i) {
-    for (var j = tileRange.minY, jj = tileRange.maxY; j <= jj; ++j) {
-      callback([zoom, i, j]);
-    }
-  }
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {function(this: T, number, ol.TileRange): boolean} callback Callback.
- * @param {T=} opt_this The object to use as `this` in `callback`.
- * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
- * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
- * @return {boolean} Callback succeeded.
- * @template T
- */
-ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) {
-  var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
-  var z = tileCoord[0] - 1;
-  while (z >= this.minZoom) {
-    if (callback.call(opt_this, z,
-        this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) {
-      return true;
-    }
-    --z;
-  }
-  return false;
-};
-
-
-/**
- * Get the extent for this tile grid, if it was configured.
- * @return {ol.Extent} Extent.
- */
-ol.tilegrid.TileGrid.prototype.getExtent = function() {
-  return this.extent_;
-};
-
-
-/**
- * Get the maximum zoom level for the grid.
- * @return {number} Max zoom.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
-  return this.maxZoom;
-};
-
-
-/**
- * Get the minimum zoom level for the grid.
- * @return {number} Min zoom.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
-  return this.minZoom;
-};
-
-
-/**
- * Get the origin for the grid at the given zoom level.
- * @param {number} z Z.
- * @return {ol.Coordinate} Origin.
- * @api stable
- */
-ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
-  if (this.origin_) {
-    return this.origin_;
-  } else {
-    ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
-        'given z is not in allowed range (%s <= %s <= %s)',
-        this.minZoom, z, this.maxZoom);
-    return this.origins_[z];
-  }
-};
-
-
-/**
- * Get the resolution for the given zoom level.
- * @param {number} z Z.
- * @return {number} Resolution.
- * @api stable
- */
-ol.tilegrid.TileGrid.prototype.getResolution = function(z) {
-  ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
-      'given z is not in allowed range (%s <= %s <= %s)',
-      this.minZoom, z, this.maxZoom);
-  return this.resolutions_[z];
-};
-
-
-/**
- * Get the list of resolutions for the tile grid.
- * @return {Array.<number>} Resolutions.
- * @api stable
- */
-ol.tilegrid.TileGrid.prototype.getResolutions = function() {
-  return this.resolutions_;
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
- * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
- * @return {ol.TileRange} Tile range.
- */
-ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = function(tileCoord, opt_tileRange, opt_extent) {
-  if (tileCoord[0] < this.maxZoom) {
-    var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
-    return this.getTileRangeForExtentAndZ(
-        tileCoordExtent, tileCoord[0] + 1, opt_tileRange);
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @param {number} z Z.
- * @param {ol.TileRange} tileRange Tile range.
- * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
- * @return {ol.Extent} Extent.
- */
-ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_extent) {
-  var origin = this.getOrigin(z);
-  var resolution = this.getResolution(z);
-  var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_);
-  var minX = origin[0] + tileRange.minX * tileSize[0] * resolution;
-  var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution;
-  var minY = origin[1] + tileRange.minY * tileSize[1] * resolution;
-  var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution;
-  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
- * @return {ol.TileRange} Tile range.
- */
-ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(extent, resolution, opt_tileRange) {
-  var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_;
-  this.getTileCoordForXYAndResolution_(
-      extent[0], extent[1], resolution, false, tileCoord);
-  var minX = tileCoord[1];
-  var minY = tileCoord[2];
-  this.getTileCoordForXYAndResolution_(
-      extent[2], extent[3], resolution, true, tileCoord);
-  return ol.TileRange.createOrUpdate(
-      minX, tileCoord[1], minY, tileCoord[2], opt_tileRange);
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} z Z.
- * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
- * @return {ol.TileRange} Tile range.
- */
-ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) {
-  var resolution = this.getResolution(z);
-  return this.getTileRangeForExtentAndResolution(
-      extent, resolution, opt_tileRange);
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @return {ol.Coordinate} Tile center.
- */
-ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) {
-  var origin = this.getOrigin(tileCoord[0]);
-  var resolution = this.getResolution(tileCoord[0]);
-  var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_);
-  return [
-    origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution,
-    origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution
-  ];
-};
-
-
-/**
- * Get the extent of a tile coordinate.
- *
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Extent=} opt_extent Temporary extent object.
- * @return {ol.Extent} Extent.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.getTileCoordExtent = function(tileCoord, opt_extent) {
-  var origin = this.getOrigin(tileCoord[0]);
-  var resolution = this.getResolution(tileCoord[0]);
-  var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_);
-  var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution;
-  var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution;
-  var maxX = minX + tileSize[0] * resolution;
-  var maxY = minY + tileSize[1] * resolution;
-  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
-};
-
-
-/**
- * Get the tile coordinate for the given map coordinate and resolution.  This
- * method considers that coordinates that intersect tile boundaries should be
- * assigned the higher tile coordinate.
- *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
- * @return {ol.TileCoord} Tile coordinate.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coordinate, resolution, opt_tileCoord) {
-  return this.getTileCoordForXYAndResolution_(
-      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
-};
-
-
-/**
- * @param {number} x X.
- * @param {number} y Y.
- * @param {number} resolution Resolution.
- * @param {boolean} reverseIntersectionPolicy Instead of letting edge
- *     intersections go to the higher tile coordinate, let edge intersections
- *     go to the lower tile coordinate.
- * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object.
- * @return {ol.TileCoord} Tile coordinate.
- * @private
- */
-ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
-    x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) {
-  var z = this.getZForResolution(resolution);
-  var scale = resolution / this.getResolution(z);
-  var origin = this.getOrigin(z);
-  var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_);
-
-  var adjustX = reverseIntersectionPolicy ? 0.5 : 0;
-  var adjustY = reverseIntersectionPolicy ? 0 : 0.5;
-  var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX);
-  var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY);
-  var tileCoordX = scale * xFromOrigin / tileSize[0];
-  var tileCoordY = scale * yFromOrigin / tileSize[1];
-
-  if (reverseIntersectionPolicy) {
-    tileCoordX = Math.ceil(tileCoordX) - 1;
-    tileCoordY = Math.ceil(tileCoordY) - 1;
-  } else {
-    tileCoordX = Math.floor(tileCoordX);
-    tileCoordY = Math.floor(tileCoordY);
-  }
-
-  return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord);
-};
-
-
-/**
- * Get a tile coordinate given a map coordinate and zoom level.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} z Zoom level.
- * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
- * @return {ol.TileCoord} Tile coordinate.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = function(coordinate, z, opt_tileCoord) {
-  var resolution = this.getResolution(z);
-  return this.getTileCoordForXYAndResolution_(
-      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @return {number} Tile resolution.
- */
-ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) {
-  ol.DEBUG && console.assert(
-      this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom,
-      'z of given tilecoord is not in allowed range (%s <= %s <= %s',
-      this.minZoom, tileCoord[0], this.maxZoom);
-  return this.resolutions_[tileCoord[0]];
-};
-
-
-/**
- * Get the tile size for a zoom level. The type of the return value matches the
- * `tileSize` or `tileSizes` that the tile grid was configured with. To always
- * get an `ol.Size`, run the result through `ol.size.toSize()`.
- * @param {number} z Z.
- * @return {number|ol.Size} Tile size.
- * @api stable
- */
-ol.tilegrid.TileGrid.prototype.getTileSize = function(z) {
-  if (this.tileSize_) {
-    return this.tileSize_;
-  } else {
-    ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
-        'z is not in allowed range (%s <= %s <= %s',
-        this.minZoom, z, this.maxZoom);
-    return this.tileSizes_[z];
-  }
-};
-
-
-/**
- * @param {number} z Zoom level.
- * @return {ol.TileRange} Extent tile range for the specified zoom level.
- */
-ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) {
-  if (!this.fullTileRanges_) {
-    return null;
-  } else {
-    ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
-        'z is not in allowed range (%s <= %s <= %s',
-        this.minZoom, z, this.maxZoom);
-    return this.fullTileRanges_[z];
-  }
-};
-
-
-/**
- * @param {number} resolution Resolution.
- * @param {number=} opt_direction If 0, the nearest resolution will be used.
- *     If 1, the nearest lower resolution will be used. If -1, the nearest
- *     higher resolution will be used. Default is 0.
- * @return {number} Z.
- * @api
- */
-ol.tilegrid.TileGrid.prototype.getZForResolution = function(
-    resolution, opt_direction) {
-  var z = ol.array.linearFindNearest(this.resolutions_, resolution,
-      opt_direction || 0);
-  return ol.math.clamp(z, this.minZoom, this.maxZoom);
-};
-
-
-/**
- * @param {!ol.Extent} extent Extent for this tile grid.
- * @private
- */
-ol.tilegrid.TileGrid.prototype.calculateTileRanges_ = function(extent) {
-  var length = this.resolutions_.length;
-  var fullTileRanges = new Array(length);
-  for (var z = this.minZoom; z < length; ++z) {
-    fullTileRanges[z] = this.getTileRangeForExtentAndZ(extent, z);
-  }
-  this.fullTileRanges_ = fullTileRanges;
-};
-
-goog.provide('ol.tilegrid');
-
-goog.require('ol');
-goog.require('ol.size');
-goog.require('ol.extent');
-goog.require('ol.extent.Corner');
-goog.require('ol.obj');
-goog.require('ol.proj');
-goog.require('ol.proj.METERS_PER_UNIT');
-goog.require('ol.proj.Units');
-goog.require('ol.tilegrid.TileGrid');
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @return {!ol.tilegrid.TileGrid} Default tile grid for the passed projection.
- */
-ol.tilegrid.getForProjection = function(projection) {
-  var tileGrid = projection.getDefaultTileGrid();
-  if (!tileGrid) {
-    tileGrid = ol.tilegrid.createForProjection(projection);
-    projection.setDefaultTileGrid(tileGrid);
-  }
-  return tileGrid;
-};
-
-
-/**
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.TileCoord} Tile coordinate.
- */
-ol.tilegrid.wrapX = function(tileGrid, tileCoord, projection) {
-  var z = tileCoord[0];
-  var center = tileGrid.getTileCoordCenter(tileCoord);
-  var projectionExtent = ol.tilegrid.extentFromProjection(projection);
-  if (!ol.extent.containsCoordinate(projectionExtent, center)) {
-    var worldWidth = ol.extent.getWidth(projectionExtent);
-    var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth);
-    center[0] += worldWidth * worldsAway;
-    return tileGrid.getTileCoordForCoordAndZ(center, z);
-  } else {
-    return tileCoord;
-  }
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number=} opt_maxZoom Maximum zoom level (default is
- *     ol.DEFAULT_MAX_ZOOM).
- * @param {number|ol.Size=} opt_tileSize Tile size (default uses
- *     ol.DEFAULT_TILE_SIZE).
- * @param {ol.extent.Corner=} opt_corner Extent corner (default is
- *     ol.extent.Corner.TOP_LEFT).
- * @return {!ol.tilegrid.TileGrid} TileGrid instance.
- */
-ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) {
-  var corner = opt_corner !== undefined ?
-      opt_corner : ol.extent.Corner.TOP_LEFT;
-
-  var resolutions = ol.tilegrid.resolutionsFromExtent(
-      extent, opt_maxZoom, opt_tileSize);
-
-  return new ol.tilegrid.TileGrid({
-    extent: extent,
-    origin: ol.extent.getCorner(extent, corner),
-    resolutions: resolutions,
-    tileSize: opt_tileSize
-  });
-};
-
-
-/**
- * Creates a tile grid with a standard XYZ tiling scheme.
- * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options.
- * @return {ol.tilegrid.TileGrid} Tile grid instance.
- * @api
- */
-ol.tilegrid.createXYZ = function(opt_options) {
-  var options = /** @type {olx.tilegrid.TileGridOptions} */ ({});
-  ol.obj.assign(options, opt_options !== undefined ?
-      opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({}));
-  if (options.extent === undefined) {
-    options.extent = ol.proj.get('EPSG:3857').getExtent();
-  }
-  options.resolutions = ol.tilegrid.resolutionsFromExtent(
-      options.extent, options.maxZoom, options.tileSize);
-  delete options.maxZoom;
-
-  return new ol.tilegrid.TileGrid(options);
-};
-
-
-/**
- * Create a resolutions array from an extent.  A zoom factor of 2 is assumed.
- * @param {ol.Extent} extent Extent.
- * @param {number=} opt_maxZoom Maximum zoom level (default is
- *     ol.DEFAULT_MAX_ZOOM).
- * @param {number|ol.Size=} opt_tileSize Tile size (default uses
- *     ol.DEFAULT_TILE_SIZE).
- * @return {!Array.<number>} Resolutions array.
- */
-ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) {
-  var maxZoom = opt_maxZoom !== undefined ?
-      opt_maxZoom : ol.DEFAULT_MAX_ZOOM;
-
-  var height = ol.extent.getHeight(extent);
-  var width = ol.extent.getWidth(extent);
-
-  var tileSize = ol.size.toSize(opt_tileSize !== undefined ?
-      opt_tileSize : ol.DEFAULT_TILE_SIZE);
-  var maxResolution = Math.max(
-      width / tileSize[0], height / tileSize[1]);
-
-  var length = maxZoom + 1;
-  var resolutions = new Array(length);
-  for (var z = 0; z < length; ++z) {
-    resolutions[z] = maxResolution / Math.pow(2, z);
-  }
-  return resolutions;
-};
-
-
-/**
- * @param {ol.ProjectionLike} projection Projection.
- * @param {number=} opt_maxZoom Maximum zoom level (default is
- *     ol.DEFAULT_MAX_ZOOM).
- * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE).
- * @param {ol.extent.Corner=} opt_corner Extent corner (default is
- *     ol.extent.Corner.BOTTOM_LEFT).
- * @return {!ol.tilegrid.TileGrid} TileGrid instance.
- */
-ol.tilegrid.createForProjection = function(projection, opt_maxZoom, opt_tileSize, opt_corner) {
-  var extent = ol.tilegrid.extentFromProjection(projection);
-  return ol.tilegrid.createForExtent(
-      extent, opt_maxZoom, opt_tileSize, opt_corner);
-};
-
-
-/**
- * Generate a tile grid extent from a projection.  If the projection has an
- * extent, it is used.  If not, a global extent is assumed.
- * @param {ol.ProjectionLike} projection Projection.
- * @return {ol.Extent} Extent.
- */
-ol.tilegrid.extentFromProjection = function(projection) {
-  projection = ol.proj.get(projection);
-  var extent = projection.getExtent();
-  if (!extent) {
-    var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] /
-        projection.getMetersPerUnit();
-    extent = ol.extent.createOrUpdate(-half, -half, half, half);
-  }
-  return extent;
-};
-
-goog.provide('ol.Attribution');
-
-goog.require('ol.TileRange');
-goog.require('ol.math');
-goog.require('ol.tilegrid');
-
-
-/**
- * @classdesc
- * An attribution for a layer source.
- *
- * Example:
- *
- *     source: new ol.source.OSM({
- *       attributions: [
- *         new ol.Attribution({
- *           html: 'All maps © ' +
- *               '<a href="https://www.opencyclemap.org/">OpenCycleMap</a>'
- *         }),
- *         ol.source.OSM.ATTRIBUTION
- *       ],
- *     ..
- *
- * @constructor
- * @param {olx.AttributionOptions} options Attribution options.
- * @struct
- * @api stable
- */
-ol.Attribution = function(options) {
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.html_ = options.html;
-
-  /**
-   * @private
-   * @type {Object.<string, Array.<ol.TileRange>>}
-   */
-  this.tileRanges_ = options.tileRanges ? options.tileRanges : null;
-
-};
-
-
-/**
- * Get the attribution markup.
- * @return {string} The attribution HTML.
- * @api stable
- */
-ol.Attribution.prototype.getHTML = function() {
-  return this.html_;
-};
-
-
-/**
- * @param {Object.<string, ol.TileRange>} tileRanges Tile ranges.
- * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @param {!ol.proj.Projection} projection Projection.
- * @return {boolean} Intersects any tile range.
- */
-ol.Attribution.prototype.intersectsAnyTileRange = function(tileRanges, tileGrid, projection) {
-  if (!this.tileRanges_) {
-    return true;
-  }
-  var i, ii, tileRange, zKey;
-  for (zKey in tileRanges) {
-    if (!(zKey in this.tileRanges_)) {
-      continue;
-    }
-    tileRange = tileRanges[zKey];
-    var testTileRange;
-    for (i = 0, ii = this.tileRanges_[zKey].length; i < ii; ++i) {
-      testTileRange = this.tileRanges_[zKey][i];
-      if (testTileRange.intersects(tileRange)) {
-        return true;
-      }
-      var extentTileRange = tileGrid.getTileRangeForExtentAndZ(
-          ol.tilegrid.extentFromProjection(projection), parseInt(zKey, 10));
-      var width = extentTileRange.getWidth();
-      if (tileRange.minX < extentTileRange.minX ||
-          tileRange.maxX > extentTileRange.maxX) {
-        if (testTileRange.intersects(new ol.TileRange(
-            ol.math.modulo(tileRange.minX, width),
-            ol.math.modulo(tileRange.maxX, width),
-            tileRange.minY, tileRange.maxY))) {
-          return true;
-        }
-        if (tileRange.getWidth() > width &&
-            testTileRange.intersects(extentTileRange)) {
-          return true;
-        }
-      }
-    }
-  }
-  return false;
-};
-
-/**
- * An implementation of Google Maps' MVCArray.
- * @see https://developers.google.com/maps/documentation/javascript/reference
- */
-
-goog.provide('ol.Collection');
-
-goog.require('ol');
-goog.require('ol.events.Event');
-goog.require('ol.Object');
-
-
-/**
- * @classdesc
- * An expanded version of standard JS Array, adding convenience methods for
- * manipulation. Add and remove changes to the Collection trigger a Collection
- * event. Note that this does not cover changes to the objects _within_ the
- * Collection; they trigger events on the appropriate object, not on the
- * Collection as a whole.
- *
- * @constructor
- * @extends {ol.Object}
- * @fires ol.Collection.Event
- * @param {!Array.<T>=} opt_array Array.
- * @template T
- * @api stable
- */
-ol.Collection = function(opt_array) {
-
-  ol.Object.call(this);
-
-  /**
-   * @private
-   * @type {!Array.<T>}
-   */
-  this.array_ = opt_array ? opt_array : [];
-
-  this.updateLength_();
-
-};
-ol.inherits(ol.Collection, ol.Object);
-
-
-/**
- * Remove all elements from the collection.
- * @api stable
- */
-ol.Collection.prototype.clear = function() {
-  while (this.getLength() > 0) {
-    this.pop();
-  }
-};
-
-
-/**
- * Add elements to the collection.  This pushes each item in the provided array
- * to the end of the collection.
- * @param {!Array.<T>} arr Array.
- * @return {ol.Collection.<T>} This collection.
- * @api stable
- */
-ol.Collection.prototype.extend = function(arr) {
-  var i, ii;
-  for (i = 0, ii = arr.length; i < ii; ++i) {
-    this.push(arr[i]);
-  }
-  return this;
-};
-
-
-/**
- * Iterate over each element, calling the provided callback.
- * @param {function(this: S, T, number, Array.<T>): *} f The function to call
- *     for every element. This function takes 3 arguments (the element, the
- *     index and the array). The return value is ignored.
- * @param {S=} opt_this The object to use as `this` in `f`.
- * @template S
- * @api stable
- */
-ol.Collection.prototype.forEach = function(f, opt_this) {
-  this.array_.forEach(f, opt_this);
-};
-
-
-/**
- * Get a reference to the underlying Array object. Warning: if the array
- * is mutated, no events will be dispatched by the collection, and the
- * collection's "length" property won't be in sync with the actual length
- * of the array.
- * @return {!Array.<T>} Array.
- * @api stable
- */
-ol.Collection.prototype.getArray = function() {
-  return this.array_;
-};
-
-
-/**
- * Get the element at the provided index.
- * @param {number} index Index.
- * @return {T} Element.
- * @api stable
- */
-ol.Collection.prototype.item = function(index) {
-  return this.array_[index];
-};
-
-
-/**
- * Get the length of this collection.
- * @return {number} The length of the array.
- * @observable
- * @api stable
- */
-ol.Collection.prototype.getLength = function() {
-  return /** @type {number} */ (this.get(ol.Collection.Property.LENGTH));
-};
-
-
-/**
- * Insert an element at the provided index.
- * @param {number} index Index.
- * @param {T} elem Element.
- * @api stable
- */
-ol.Collection.prototype.insertAt = function(index, elem) {
-  this.array_.splice(index, 0, elem);
-  this.updateLength_();
-  this.dispatchEvent(
-      new ol.Collection.Event(ol.Collection.EventType.ADD, elem));
-};
-
-
-/**
- * Remove the last element of the collection and return it.
- * Return `undefined` if the collection is empty.
- * @return {T|undefined} Element.
- * @api stable
- */
-ol.Collection.prototype.pop = function() {
-  return this.removeAt(this.getLength() - 1);
-};
-
-
-/**
- * Insert the provided element at the end of the collection.
- * @param {T} elem Element.
- * @return {number} Length.
- * @api stable
- */
-ol.Collection.prototype.push = function(elem) {
-  var n = this.array_.length;
-  this.insertAt(n, elem);
-  return n;
-};
-
-
-/**
- * Remove the first occurrence of an element from the collection.
- * @param {T} elem Element.
- * @return {T|undefined} The removed element or undefined if none found.
- * @api stable
- */
-ol.Collection.prototype.remove = function(elem) {
-  var arr = this.array_;
-  var i, ii;
-  for (i = 0, ii = arr.length; i < ii; ++i) {
-    if (arr[i] === elem) {
-      return this.removeAt(i);
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * Remove the element at the provided index and return it.
- * Return `undefined` if the collection does not contain this index.
- * @param {number} index Index.
- * @return {T|undefined} Value.
- * @api stable
- */
-ol.Collection.prototype.removeAt = function(index) {
-  var prev = this.array_[index];
-  this.array_.splice(index, 1);
-  this.updateLength_();
-  this.dispatchEvent(
-      new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev));
-  return prev;
-};
-
-
-/**
- * Set the element at the provided index.
- * @param {number} index Index.
- * @param {T} elem Element.
- * @api stable
- */
-ol.Collection.prototype.setAt = function(index, elem) {
-  var n = this.getLength();
-  if (index < n) {
-    var prev = this.array_[index];
-    this.array_[index] = elem;
-    this.dispatchEvent(
-        new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev));
-    this.dispatchEvent(
-        new ol.Collection.Event(ol.Collection.EventType.ADD, elem));
-  } else {
-    var j;
-    for (j = n; j < index; ++j) {
-      this.insertAt(j, undefined);
-    }
-    this.insertAt(index, elem);
-  }
-};
-
-
-/**
- * @private
- */
-ol.Collection.prototype.updateLength_ = function() {
-  this.set(ol.Collection.Property.LENGTH, this.array_.length);
-};
-
-
-/**
- * @enum {string}
- */
-ol.Collection.Property = {
-  LENGTH: 'length'
-};
-
-
-/**
- * @enum {string}
- */
-ol.Collection.EventType = {
-  /**
-   * Triggered when an item is added to the collection.
-   * @event ol.Collection.Event#add
-   * @api stable
-   */
-  ADD: 'add',
-  /**
-   * Triggered when an item is removed from the collection.
-   * @event ol.Collection.Event#remove
-   * @api stable
-   */
-  REMOVE: 'remove'
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.Collection} instances are instances of this
- * type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.Collection.Event}
- * @param {ol.Collection.EventType} type Type.
- * @param {*=} opt_element Element.
- */
-ol.Collection.Event = function(type, opt_element) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The element that is added to or removed from the collection.
-   * @type {*}
-   * @api stable
-   */
-  this.element = opt_element;
-
-};
-ol.inherits(ol.Collection.Event, ol.events.Event);
-
-goog.provide('ol.color');
-
-goog.require('ol.asserts');
-goog.require('ol.math');
-
-
-/**
- * This RegExp matches # followed by 3 or 6 hex digits.
- * @const
- * @type {RegExp}
- * @private
- */
-ol.color.HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
-
-
-/**
- * Regular expression for matching and capturing RGB style strings.
- * @const
- * @type {RegExp}
- * @private
- */
-ol.color.RGB_COLOR_RE_ =
-    /^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;
-
-
-/**
- * Regular expression for matching and capturing RGBA style strings.
- * @const
- * @type {RegExp}
- * @private
- */
-ol.color.RGBA_COLOR_RE_ =
-    /^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;
-
-
-/**
- * Regular expression for matching potential named color style strings.
- * @const
- * @type {RegExp}
- * @private
- */
-ol.color.NAMED_COLOR_RE_ =
-    /^([a-z]*)$/i;
-
-
-/**
- * Return the color as an array. This function maintains a cache of calculated
- * arrays which means the result should not be modified.
- * @param {ol.Color|string} color Color.
- * @return {ol.Color} Color.
- * @api
- */
-ol.color.asArray = function(color) {
-  if (Array.isArray(color)) {
-    return color;
-  } else {
-    return ol.color.fromString(/** @type {string} */ (color));
-  }
-};
-
-
-/**
- * Return the color as an rgba string.
- * @param {ol.Color|string} color Color.
- * @return {string} Rgba string.
- * @api
- */
-ol.color.asString = function(color) {
-  if (typeof color === 'string') {
-    return color;
-  } else {
-    return ol.color.toString(color);
-  }
-};
-
-/**
- * Return named color as an rgba string.
- * @param {string} color Named color.
- * @return {string} Rgb string.
- */
-ol.color.fromNamed = function(color) {
-  var el = document.createElement('div');
-  el.style.color = color;
-  document.body.appendChild(el);
-  var rgb = getComputedStyle(el).color;
-  document.body.removeChild(el);
-  return rgb;
-};
-
-
-/**
- * @param {string} s String.
- * @return {ol.Color} Color.
- */
-ol.color.fromString = (
-    function() {
-
-      // We maintain a small cache of parsed strings.  To provide cheap LRU-like
-      // semantics, whenever the cache grows too large we simply delete an
-      // arbitrary 25% of the entries.
-
-      /**
-       * @const
-       * @type {number}
-       */
-      var MAX_CACHE_SIZE = 1024;
-
-      /**
-       * @type {Object.<string, ol.Color>}
-       */
-      var cache = {};
-
-      /**
-       * @type {number}
-       */
-      var cacheSize = 0;
-
-      return (
-          /**
-           * @param {string} s String.
-           * @return {ol.Color} Color.
-           */
-          function(s) {
-            var color;
-            if (cache.hasOwnProperty(s)) {
-              color = cache[s];
-            } else {
-              if (cacheSize >= MAX_CACHE_SIZE) {
-                var i = 0;
-                var key;
-                for (key in cache) {
-                  if ((i++ & 3) === 0) {
-                    delete cache[key];
-                    --cacheSize;
-                  }
-                }
-              }
-              color = ol.color.fromStringInternal_(s);
-              cache[s] = color;
-              ++cacheSize;
-            }
-            return color;
-          });
-
-    })();
-
-
-/**
- * @param {string} s String.
- * @private
- * @return {ol.Color} Color.
- */
-ol.color.fromStringInternal_ = function(s) {
-  var r, g, b, a, color, match;
-
-  if (ol.color.NAMED_COLOR_RE_.exec(s)) {
-    s = ol.color.fromNamed(s);
-  }
-
-  if (ol.color.HEX_COLOR_RE_.exec(s)) { // hex
-    var n = s.length - 1; // number of hex digits
-    ol.asserts.assert(n == 3 || n == 6, 54); // Hex color should have 3 or 6 digits
-    var d = n == 3 ? 1 : 2; // number of digits per channel
-    r = parseInt(s.substr(1 + 0 * d, d), 16);
-    g = parseInt(s.substr(1 + 1 * d, d), 16);
-    b = parseInt(s.substr(1 + 2 * d, d), 16);
-    if (d == 1) {
-      r = (r << 4) + r;
-      g = (g << 4) + g;
-      b = (b << 4) + b;
-    }
-    a = 1;
-    color = [r, g, b, a];
-  } else if ((match = ol.color.RGBA_COLOR_RE_.exec(s))) { // rgba()
-    r = Number(match[1]);
-    g = Number(match[2]);
-    b = Number(match[3]);
-    a = Number(match[4]);
-    color = ol.color.normalize([r, g, b, a]);
-  } else if ((match = ol.color.RGB_COLOR_RE_.exec(s))) { // rgb()
-    r = Number(match[1]);
-    g = Number(match[2]);
-    b = Number(match[3]);
-    color = ol.color.normalize([r, g, b, 1]);
-  } else {
-    ol.asserts.assert(false, 14); // Invalid color
-  }
-  return /** @type {ol.Color} */ (color);
-};
-
-
-/**
- * @param {ol.Color} color Color.
- * @param {ol.Color=} opt_color Color.
- * @return {ol.Color} Clamped color.
- */
-ol.color.normalize = function(color, opt_color) {
-  var result = opt_color || [];
-  result[0] = ol.math.clamp((color[0] + 0.5) | 0, 0, 255);
-  result[1] = ol.math.clamp((color[1] + 0.5) | 0, 0, 255);
-  result[2] = ol.math.clamp((color[2] + 0.5) | 0, 0, 255);
-  result[3] = ol.math.clamp(color[3], 0, 1);
-  return result;
-};
-
-
-/**
- * @param {ol.Color} color Color.
- * @return {string} String.
- */
-ol.color.toString = function(color) {
-  var r = color[0];
-  if (r != (r | 0)) {
-    r = (r + 0.5) | 0;
-  }
-  var g = color[1];
-  if (g != (g | 0)) {
-    g = (g + 0.5) | 0;
-  }
-  var b = color[2];
-  if (b != (b | 0)) {
-    b = (b + 0.5) | 0;
-  }
-  var a = color[3] === undefined ? 1 : color[3];
-  return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
-};
-
-goog.provide('ol.colorlike');
-
-goog.require('ol.color');
-
-
-/**
- * @param {ol.Color|ol.ColorLike} color Color.
- * @return {ol.ColorLike} The color as an ol.ColorLike
- * @api
- */
-ol.colorlike.asColorLike = function(color) {
-  if (ol.colorlike.isColorLike(color)) {
-    return /** @type {string|CanvasPattern|CanvasGradient} */ (color);
-  } else {
-    return ol.color.asString(/** @type {ol.Color} */ (color));
-  }
-};
-
-
-/**
- * @param {?} color The value that is potentially an ol.ColorLike
- * @return {boolean} Whether the color is an ol.ColorLike
- */
-ol.colorlike.isColorLike = function(color) {
-  return (
-      typeof color === 'string' ||
-      color instanceof CanvasPattern ||
-      color instanceof CanvasGradient
-  );
-};
-
-goog.provide('ol.dom');
-
-
-/**
- * Create an html canvas element and returns its 2d context.
- * @param {number=} opt_width Canvas width.
- * @param {number=} opt_height Canvas height.
- * @return {CanvasRenderingContext2D} The context.
- */
-ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
-  var canvas = document.createElement('CANVAS');
-  if (opt_width) {
-    canvas.width = opt_width;
-  }
-  if (opt_height) {
-    canvas.height = opt_height;
-  }
-  return canvas.getContext('2d');
-};
-
-
-/**
- * Get the current computed width for the given element including margin,
- * padding and border.
- * Equivalent to jQuery's `$(el).outerWidth(true)`.
- * @param {!Element} element Element.
- * @return {number} The width.
- */
-ol.dom.outerWidth = function(element) {
-  var width = element.offsetWidth;
-  var style = element.currentStyle || getComputedStyle(element);
-  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
-
-  return width;
-};
-
-
-/**
- * Get the current computed height for the given element including margin,
- * padding and border.
- * Equivalent to jQuery's `$(el).outerHeight(true)`.
- * @param {!Element} element Element.
- * @return {number} The height.
- */
-ol.dom.outerHeight = function(element) {
-  var height = element.offsetHeight;
-  var style = element.currentStyle || getComputedStyle(element);
-  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
-
-  return height;
-};
-
-/**
- * @param {Node} newNode Node to replace old node
- * @param {Node} oldNode The node to be replaced
- */
-ol.dom.replaceNode = function(newNode, oldNode) {
-  var parent = oldNode.parentNode;
-  if (parent) {
-    parent.replaceChild(newNode, oldNode);
-  }
-};
-
-/**
- * @param {Node} node The node to remove.
- * @returns {Node} The node that was removed or null.
- */
-ol.dom.removeNode = function(node) {
-  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
-};
-
-/**
- * @param {Node} node The node to remove the children from.
- */
-ol.dom.removeChildren = function(node) {
-  while (node.lastChild) {
-    node.removeChild(node.lastChild);
-  }
-};
-
-goog.provide('ol.MapEvent');
-
-goog.require('ol');
-goog.require('ol.events.Event');
-
-
-/**
- * @classdesc
- * Events emitted as map events are instances of this type.
- * See {@link ol.Map} for which events trigger a map event.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.MapEvent}
- * @param {string} type Event type.
- * @param {ol.Map} map Map.
- * @param {?olx.FrameState=} opt_frameState Frame state.
- */
-ol.MapEvent = function(type, map, opt_frameState) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The map where the event occurred.
-   * @type {ol.Map}
-   * @api stable
-   */
-  this.map = map;
-
-  /**
-   * The frame state at the time of the event.
-   * @type {?olx.FrameState}
-   * @api
-   */
-  this.frameState = opt_frameState !== undefined ? opt_frameState : null;
-
-};
-ol.inherits(ol.MapEvent, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.MapEvent.Type = {
-
-  /**
-   * Triggered after a map frame is rendered.
-   * @event ol.MapEvent#postrender
-   * @api
-   */
-  POSTRENDER: 'postrender',
-
-  /**
-   * Triggered after the map is moved.
-   * @event ol.MapEvent#moveend
-   * @api stable
-   */
-  MOVEEND: 'moveend'
-
-};
-
-goog.provide('ol.control.Control');
-
-goog.require('ol.events');
-goog.require('ol');
-goog.require('ol.MapEvent');
-goog.require('ol.Object');
-goog.require('ol.dom');
-
-
-/**
- * @classdesc
- * A control is a visible widget with a DOM element in a fixed position on the
- * screen. They can involve user input (buttons), or be informational only;
- * the position is determined using CSS. By default these are placed in the
- * container with CSS class name `ol-overlaycontainer-stopevent`, but can use
- * any outside DOM element.
- *
- * This is the base class for controls. You can use it for simple custom
- * controls by creating the element with listeners, creating an instance:
- * ```js
- * var myControl = new ol.control.Control({element: myElement});
- * ```
- * and then adding this to the map.
- *
- * The main advantage of having this as a control rather than a simple separate
- * DOM element is that preventing propagation is handled for you. Controls
- * will also be `ol.Object`s in a `ol.Collection`, so you can use their
- * methods.
- *
- * You can also extend this base for your own control class. See
- * examples/custom-controls for an example of how to do this.
- *
- * @constructor
- * @extends {ol.Object}
- * @implements {oli.control.Control}
- * @param {olx.control.ControlOptions} options Control options.
- * @api stable
- */
-ol.control.Control = function(options) {
-
-  ol.Object.call(this);
-
-  /**
-   * @protected
-   * @type {Element}
-   */
-  this.element = options.element ? options.element : null;
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.target_ = null;
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = null;
-
-  /**
-   * @protected
-   * @type {!Array.<ol.EventsKey>}
-   */
-  this.listenerKeys = [];
-
-  /**
-   * @type {function(ol.MapEvent)}
-   */
-  this.render = options.render ? options.render : ol.nullFunction;
-
-  if (options.target) {
-    this.setTarget(options.target);
-  }
-
-};
-ol.inherits(ol.control.Control, ol.Object);
-
-
-/**
- * @inheritDoc
- */
-ol.control.Control.prototype.disposeInternal = function() {
-  ol.dom.removeNode(this.element);
-  ol.Object.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * Get the map associated with this control.
- * @return {ol.Map} Map.
- * @api stable
- */
-ol.control.Control.prototype.getMap = function() {
-  return this.map_;
-};
-
-
-/**
- * Remove the control from its current map and attach it to the new map.
- * Subclasses may set up event handlers to get notified about changes to
- * the map here.
- * @param {ol.Map} map Map.
- * @api stable
- */
-ol.control.Control.prototype.setMap = function(map) {
-  if (this.map_) {
-    ol.dom.removeNode(this.element);
-  }
-  for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) {
-    ol.events.unlistenByKey(this.listenerKeys[i]);
-  }
-  this.listenerKeys.length = 0;
-  this.map_ = map;
-  if (this.map_) {
-    var target = this.target_ ?
-        this.target_ : map.getOverlayContainerStopEvent();
-    target.appendChild(this.element);
-    if (this.render !== ol.nullFunction) {
-      this.listenerKeys.push(ol.events.listen(map,
-          ol.MapEvent.Type.POSTRENDER, this.render, this));
-    }
-    map.render();
-  }
-};
-
-
-/**
- * This function is used to set a target element for the control. It has no
- * effect if it is called after the control has been added to the map (i.e.
- * after `setMap` is called on the control). If no `target` is set in the
- * options passed to the control constructor and if `setTarget` is not called
- * then the control is added to the map's overlay container.
- * @param {Element|string} target Target.
- * @api
- */
-ol.control.Control.prototype.setTarget = function(target) {
-  this.target_ = typeof target === 'string' ?
-    document.getElementById(target) :
-    target;
-};
-
-goog.provide('ol.css');
-
-
-/**
- * The CSS class for hidden feature.
- *
- * @const
- * @type {string}
- */
-ol.css.CLASS_HIDDEN = 'ol-hidden';
-
-
-/**
- * The CSS class that we'll give the DOM elements to have them unselectable.
- *
- * @const
- * @type {string}
- */
-ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
-
-
-/**
- * The CSS class for unsupported feature.
- *
- * @const
- * @type {string}
- */
-ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
-
-
-/**
- * The CSS class for controls.
- *
- * @const
- * @type {string}
- */
-ol.css.CLASS_CONTROL = 'ol-control';
-
-// FIXME handle date line wrap
-
-goog.provide('ol.control.Attribution');
-
-goog.require('ol');
-goog.require('ol.dom');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.obj');
-
-
-/**
- * @classdesc
- * Control to show all the attributions associated with the layer sources
- * in the map. This control is one of the default controls included in maps.
- * By default it will show in the bottom right portion of the map, but this can
- * be changed by using a css selector for `.ol-attribution`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.AttributionOptions=} opt_options Attribution options.
- * @api stable
- */
-ol.control.Attribution = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.ulElement_ = document.createElement('UL');
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.logoLi_ = document.createElement('LI');
-
-  this.ulElement_.appendChild(this.logoLi_);
-  this.logoLi_.style.display = 'none';
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.collapsible_ = options.collapsible !== undefined ?
-      options.collapsible : true;
-
-  if (!this.collapsible_) {
-    this.collapsed_ = false;
-  }
-
-  var className = options.className !== undefined ? options.className : 'ol-attribution';
-
-  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions';
-
-  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB';
-
-  if (typeof collapseLabel === 'string') {
-    /**
-     * @private
-     * @type {Node}
-     */
-    this.collapseLabel_ = document.createElement('span');
-    this.collapseLabel_.textContent = collapseLabel;
-  } else {
-    this.collapseLabel_ = collapseLabel;
-  }
-
-  var label = options.label !== undefined ? options.label : 'i';
-
-  if (typeof label === 'string') {
-    /**
-     * @private
-     * @type {Node}
-     */
-    this.label_ = document.createElement('span');
-    this.label_.textContent = label;
-  } else {
-    this.label_ = label;
-  }
-
-
-  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
-      this.collapseLabel_ : this.label_;
-  var button = document.createElement('button');
-  button.setAttribute('type', 'button');
-  button.title = tipLabel;
-  button.appendChild(activeLabel);
-
-  ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this);
-
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL +
-      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
-      (this.collapsible_ ? '' : ' ol-uncollapsible');
-  var element = document.createElement('div');
-  element.className = cssClasses;
-  element.appendChild(this.ulElement_);
-  element.appendChild(button);
-
-  var render = options.render ? options.render : ol.control.Attribution.render;
-
-  ol.control.Control.call(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
-
-  /**
-   * @private
-   * @type {Object.<string, Element>}
-   */
-  this.attributionElements_ = {};
-
-  /**
-   * @private
-   * @type {Object.<string, boolean>}
-   */
-  this.attributionElementRenderedVisible_ = {};
-
-  /**
-   * @private
-   * @type {Object.<string, Element>}
-   */
-  this.logoElements_ = {};
-
-};
-ol.inherits(ol.control.Attribution, ol.control.Control);
-
-
-/**
- * @param {?olx.FrameState} frameState Frame state.
- * @return {Array.<Object.<string, ol.Attribution>>} Attributions.
- */
-ol.control.Attribution.prototype.getSourceAttributions = function(frameState) {
-  var i, ii, j, jj, tileRanges, source, sourceAttribution,
-      sourceAttributionKey, sourceAttributions, sourceKey;
-  var intersectsTileRange;
-  var layerStatesArray = frameState.layerStatesArray;
-  /** @type {Object.<string, ol.Attribution>} */
-  var attributions = ol.obj.assign({}, frameState.attributions);
-  /** @type {Object.<string, ol.Attribution>} */
-  var hiddenAttributions = {};
-  var projection = /** @type {!ol.proj.Projection} */ (frameState.viewState.projection);
-  for (i = 0, ii = layerStatesArray.length; i < ii; i++) {
-    source = layerStatesArray[i].layer.getSource();
-    if (!source) {
-      continue;
-    }
-    sourceKey = ol.getUid(source).toString();
-    sourceAttributions = source.getAttributions();
-    if (!sourceAttributions) {
-      continue;
-    }
-    for (j = 0, jj = sourceAttributions.length; j < jj; j++) {
-      sourceAttribution = sourceAttributions[j];
-      sourceAttributionKey = ol.getUid(sourceAttribution).toString();
-      if (sourceAttributionKey in attributions) {
-        continue;
-      }
-      tileRanges = frameState.usedTiles[sourceKey];
-      if (tileRanges) {
-        var tileGrid = /** @type {ol.source.Tile} */ (source).getTileGridForProjection(projection);
-        intersectsTileRange = sourceAttribution.intersectsAnyTileRange(
-            tileRanges, tileGrid, projection);
-      } else {
-        intersectsTileRange = false;
-      }
-      if (intersectsTileRange) {
-        if (sourceAttributionKey in hiddenAttributions) {
-          delete hiddenAttributions[sourceAttributionKey];
-        }
-        attributions[sourceAttributionKey] = sourceAttribution;
-      } else {
-        hiddenAttributions[sourceAttributionKey] = sourceAttribution;
-      }
-    }
-  }
-  return [attributions, hiddenAttributions];
-};
-
-
-/**
- * Update the attribution element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.Attribution}
- * @api
- */
-ol.control.Attribution.render = function(mapEvent) {
-  this.updateElement_(mapEvent.frameState);
-};
-
-
-/**
- * @private
- * @param {?olx.FrameState} frameState Frame state.
- */
-ol.control.Attribution.prototype.updateElement_ = function(frameState) {
-
-  if (!frameState) {
-    if (this.renderedVisible_) {
-      this.element.style.display = 'none';
-      this.renderedVisible_ = false;
-    }
-    return;
-  }
-
-  var attributions = this.getSourceAttributions(frameState);
-  /** @type {Object.<string, ol.Attribution>} */
-  var visibleAttributions = attributions[0];
-  /** @type {Object.<string, ol.Attribution>} */
-  var hiddenAttributions = attributions[1];
-
-  var attributionElement, attributionKey;
-  for (attributionKey in this.attributionElements_) {
-    if (attributionKey in visibleAttributions) {
-      if (!this.attributionElementRenderedVisible_[attributionKey]) {
-        this.attributionElements_[attributionKey].style.display = '';
-        this.attributionElementRenderedVisible_[attributionKey] = true;
-      }
-      delete visibleAttributions[attributionKey];
-    } else if (attributionKey in hiddenAttributions) {
-      if (this.attributionElementRenderedVisible_[attributionKey]) {
-        this.attributionElements_[attributionKey].style.display = 'none';
-        delete this.attributionElementRenderedVisible_[attributionKey];
-      }
-      delete hiddenAttributions[attributionKey];
-    } else {
-      ol.dom.removeNode(this.attributionElements_[attributionKey]);
-      delete this.attributionElements_[attributionKey];
-      delete this.attributionElementRenderedVisible_[attributionKey];
-    }
-  }
-  for (attributionKey in visibleAttributions) {
-    attributionElement = document.createElement('LI');
-    attributionElement.innerHTML =
-        visibleAttributions[attributionKey].getHTML();
-    this.ulElement_.appendChild(attributionElement);
-    this.attributionElements_[attributionKey] = attributionElement;
-    this.attributionElementRenderedVisible_[attributionKey] = true;
-  }
-  for (attributionKey in hiddenAttributions) {
-    attributionElement = document.createElement('LI');
-    attributionElement.innerHTML =
-        hiddenAttributions[attributionKey].getHTML();
-    attributionElement.style.display = 'none';
-    this.ulElement_.appendChild(attributionElement);
-    this.attributionElements_[attributionKey] = attributionElement;
-  }
-
-  var renderVisible =
-      !ol.obj.isEmpty(this.attributionElementRenderedVisible_) ||
-      !ol.obj.isEmpty(frameState.logos);
-  if (this.renderedVisible_ != renderVisible) {
-    this.element.style.display = renderVisible ? '' : 'none';
-    this.renderedVisible_ = renderVisible;
-  }
-  if (renderVisible &&
-      ol.obj.isEmpty(this.attributionElementRenderedVisible_)) {
-    this.element.classList.add('ol-logo-only');
-  } else {
-    this.element.classList.remove('ol-logo-only');
-  }
-
-  this.insertLogos_(frameState);
-
-};
-
-
-/**
- * @param {?olx.FrameState} frameState Frame state.
- * @private
- */
-ol.control.Attribution.prototype.insertLogos_ = function(frameState) {
-
-  var logo;
-  var logos = frameState.logos;
-  var logoElements = this.logoElements_;
-
-  for (logo in logoElements) {
-    if (!(logo in logos)) {
-      ol.dom.removeNode(logoElements[logo]);
-      delete logoElements[logo];
-    }
-  }
-
-  var image, logoElement, logoKey;
-  for (logoKey in logos) {
-    var logoValue = logos[logoKey];
-    if (logoValue instanceof HTMLElement) {
-      this.logoLi_.appendChild(logoValue);
-      logoElements[logoKey] = logoValue;
-    }
-    if (!(logoKey in logoElements)) {
-      image = new Image();
-      image.src = logoKey;
-      if (logoValue === '') {
-        logoElement = image;
-      } else {
-        logoElement = document.createElement('a');
-        logoElement.href = logoValue;
-        logoElement.appendChild(image);
-      }
-      this.logoLi_.appendChild(logoElement);
-      logoElements[logoKey] = logoElement;
-    }
-  }
-
-  this.logoLi_.style.display = !ol.obj.isEmpty(logos) ? '' : 'none';
-
-};
-
-
-/**
- * @param {Event} event The event to handle
- * @private
- */
-ol.control.Attribution.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleToggle_();
-};
-
-
-/**
- * @private
- */
-ol.control.Attribution.prototype.handleToggle_ = function() {
-  this.element.classList.toggle('ol-collapsed');
-  if (this.collapsed_) {
-    ol.dom.replaceNode(this.collapseLabel_, this.label_);
-  } else {
-    ol.dom.replaceNode(this.label_, this.collapseLabel_);
-  }
-  this.collapsed_ = !this.collapsed_;
-};
-
-
-/**
- * Return `true` if the attribution is collapsible, `false` otherwise.
- * @return {boolean} True if the widget is collapsible.
- * @api stable
- */
-ol.control.Attribution.prototype.getCollapsible = function() {
-  return this.collapsible_;
-};
-
-
-/**
- * Set whether the attribution should be collapsible.
- * @param {boolean} collapsible True if the widget is collapsible.
- * @api stable
- */
-ol.control.Attribution.prototype.setCollapsible = function(collapsible) {
-  if (this.collapsible_ === collapsible) {
-    return;
-  }
-  this.collapsible_ = collapsible;
-  this.element.classList.toggle('ol-uncollapsible');
-  if (!collapsible && this.collapsed_) {
-    this.handleToggle_();
-  }
-};
-
-
-/**
- * Collapse or expand the attribution according to the passed parameter. Will
- * not do anything if the attribution isn't collapsible or if the current
- * collapsed state is already the one requested.
- * @param {boolean} collapsed True if the widget is collapsed.
- * @api stable
- */
-ol.control.Attribution.prototype.setCollapsed = function(collapsed) {
-  if (!this.collapsible_ || this.collapsed_ === collapsed) {
-    return;
-  }
-  this.handleToggle_();
-};
-
-
-/**
- * Return `true` when the attribution is currently collapsed or `false`
- * otherwise.
- * @return {boolean} True if the widget is collapsed.
- * @api stable
- */
-ol.control.Attribution.prototype.getCollapsed = function() {
-  return this.collapsed_;
-};
-
-goog.provide('ol.control.FullScreen');
-
-goog.require('ol');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-
-
-/**
- * @classdesc
- * Provides a button that when clicked fills up the full screen with the map.
- * The full screen source element is by default the element containing the map viewport unless
- * overriden by providing the `source` option. In which case, the dom
- * element introduced using this parameter will be displayed in full screen.
- *
- * When in full screen mode, a close button is shown to exit full screen mode.
- * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to
- * toggle the map in full screen mode.
- *
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.FullScreenOptions=} opt_options Options.
- * @api stable
- */
-ol.control.FullScreen = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.cssClassName_ = options.className !== undefined ? options.className :
-      'ol-full-screen';
-
-  var label = options.label !== undefined ? options.label : '\u2922';
-
-  /**
-   * @private
-   * @type {Node}
-   */
-  this.labelNode_ = typeof label === 'string' ?
-      document.createTextNode(label) : label;
-
-  var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7';
-
-  /**
-   * @private
-   * @type {Node}
-   */
-  this.labelActiveNode_ = typeof labelActive === 'string' ?
-      document.createTextNode(labelActive) : labelActive;
-
-  var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
-  var button = document.createElement('button');
-  button.className = this.cssClassName_ + '-' + ol.control.FullScreen.isFullScreen();
-  button.setAttribute('type', 'button');
-  button.title = tipLabel;
-  button.appendChild(this.labelNode_);
-
-  ol.events.listen(button, ol.events.EventType.CLICK,
-      this.handleClick_, this);
-
-  var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE +
-      ' ' + ol.css.CLASS_CONTROL + ' ' +
-      (!ol.control.FullScreen.isFullScreenSupported() ? ol.css.CLASS_UNSUPPORTED : '');
-  var element = document.createElement('div');
-  element.className = cssClasses;
-  element.appendChild(button);
-
-  ol.control.Control.call(this, {
-    element: element,
-    target: options.target
-  });
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.keys_ = options.keys !== undefined ? options.keys : false;
-
-  /**
-   * @private
-   * @type {Element|string|undefined}
-   */
-  this.source_ = options.source;
-
-};
-ol.inherits(ol.control.FullScreen, ol.control.Control);
-
-
-/**
- * @param {Event} event The event to handle
- * @private
- */
-ol.control.FullScreen.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleFullScreen_();
-};
-
-
-/**
- * @private
- */
-ol.control.FullScreen.prototype.handleFullScreen_ = function() {
-  if (!ol.control.FullScreen.isFullScreenSupported()) {
-    return;
-  }
-  var map = this.getMap();
-  if (!map) {
-    return;
-  }
-  if (ol.control.FullScreen.isFullScreen()) {
-    ol.control.FullScreen.exitFullScreen();
-  } else {
-    var element;
-    if (this.source_) {
-      element = typeof this.source_ === 'string' ?
-        document.getElementById(this.source_) :
-        this.source_;
-    } else {
-      element = map.getTargetElement();
-    }
-    if (this.keys_) {
-      ol.control.FullScreen.requestFullScreenWithKeys(element);
-
-    } else {
-      ol.control.FullScreen.requestFullScreen(element);
-    }
-  }
-};
-
-
-/**
- * @private
- */
-ol.control.FullScreen.prototype.handleFullScreenChange_ = function() {
-  var button = this.element.firstElementChild;
-  var map = this.getMap();
-  if (ol.control.FullScreen.isFullScreen()) {
-    button.className = this.cssClassName_ + '-true';
-    ol.dom.replaceNode(this.labelActiveNode_, this.labelNode_);
-  } else {
-    button.className = this.cssClassName_ + '-false';
-    ol.dom.replaceNode(this.labelNode_, this.labelActiveNode_);
-  }
-  if (map) {
-    map.updateSize();
-  }
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.control.FullScreen.prototype.setMap = function(map) {
-  ol.control.Control.prototype.setMap.call(this, map);
-  if (map) {
-    this.listenerKeys.push(ol.events.listen(document,
-        ol.control.FullScreen.getChangeType_(),
-        this.handleFullScreenChange_, this)
-    );
-  }
-};
-
-/**
- * @return {boolean} Fullscreen is supported by the current platform.
- */
-ol.control.FullScreen.isFullScreenSupported = function() {
-  var body = document.body;
-  return !!(
-    body.webkitRequestFullscreen ||
-    (body.mozRequestFullScreen && document.mozFullScreenEnabled) ||
-    (body.msRequestFullscreen && document.msFullscreenEnabled) ||
-    (body.requestFullscreen && document.fullscreenEnabled)
-  );
-};
-
-/**
- * @return {boolean} Element is currently in fullscreen.
- */
-ol.control.FullScreen.isFullScreen = function() {
-  return !!(
-    document.webkitIsFullScreen || document.mozFullScreen ||
-    document.msFullscreenElement || document.fullscreenElement
-  );
-};
-
-/**
- * Request to fullscreen an element.
- * @param {Node} element Element to request fullscreen
- */
-ol.control.FullScreen.requestFullScreen = function(element) {
-  if (element.requestFullscreen) {
-    element.requestFullscreen();
-  } else if (element.msRequestFullscreen) {
-    element.msRequestFullscreen();
-  } else if (element.mozRequestFullScreen) {
-    element.mozRequestFullScreen();
-  } else if (element.webkitRequestFullscreen) {
-    element.webkitRequestFullscreen();
-  }
-};
-
-/**
- * Request to fullscreen an element with keyboard input.
- * @param {Node} element Element to request fullscreen
- */
-ol.control.FullScreen.requestFullScreenWithKeys = function(element) {
-  if (element.mozRequestFullScreenWithKeys) {
-    element.mozRequestFullScreenWithKeys();
-  } else if (element.webkitRequestFullscreen) {
-    element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
-  } else {
-    ol.control.FullScreen.requestFullScreen(element);
-  }
-};
-
-/**
- * Exit fullscreen.
- */
-ol.control.FullScreen.exitFullScreen = function() {
-  if (document.exitFullscreen) {
-    document.exitFullscreen();
-  } else if (document.msExitFullscreen) {
-    document.msExitFullscreen();
-  } else if (document.mozCancelFullScreen) {
-    document.mozCancelFullScreen();
-  } else if (document.webkitExitFullscreen) {
-    document.webkitExitFullscreen();
-  }
-};
-
-/**
- * @return {string} Change type.
- * @private
- */
-ol.control.FullScreen.getChangeType_ = (function() {
-  var changeType;
-  return function() {
-    if (!changeType) {
-      var body = document.body;
-      if (body.webkitRequestFullscreen) {
-        changeType = 'webkitfullscreenchange';
-      } else if (body.mozRequestFullScreen) {
-        changeType = 'mozfullscreenchange';
-      } else if (body.msRequestFullscreen) {
-        changeType = 'MSFullscreenChange';
-      } else if (body.requestFullscreen) {
-        changeType = 'fullscreenchange';
-      }
-    }
-    return changeType;
-  };
-})();
-
-goog.provide('ol.control.Rotate');
-
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
-
-
-/**
- * @classdesc
- * A button control to reset rotation to 0.
- * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css
- * selector is added to the button when the rotation is 0.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.RotateOptions=} opt_options Rotate options.
- * @api stable
- */
-ol.control.Rotate = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var className = options.className !== undefined ? options.className : 'ol-rotate';
-
-  var label = options.label !== undefined ? options.label : '\u21E7';
-
-  /**
-   * @type {Element}
-   * @private
-   */
-  this.label_ = null;
-
-  if (typeof label === 'string') {
-    this.label_ = document.createElement('span');
-    this.label_.className = 'ol-compass';
-    this.label_.textContent = label;
-  } else {
-    this.label_ = label;
-    this.label_.classList.add('ol-compass');
-  }
-
-  var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation';
-
-  var button = document.createElement('button');
-  button.className = className + '-reset';
-  button.setAttribute('type', 'button');
-  button.title = tipLabel;
-  button.appendChild(this.label_);
-
-  ol.events.listen(button, ol.events.EventType.CLICK,
-      ol.control.Rotate.prototype.handleClick_, this);
-
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = document.createElement('div');
-  element.className = cssClasses;
-  element.appendChild(button);
-
-  var render = options.render ? options.render : ol.control.Rotate.render;
-
-  this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
-
-  ol.control.Control.call(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.rotation_ = undefined;
-
-  if (this.autoHide_) {
-    this.element.classList.add(ol.css.CLASS_HIDDEN);
-  }
-
-};
-ol.inherits(ol.control.Rotate, ol.control.Control);
-
-
-/**
- * @param {Event} event The event to handle
- * @private
- */
-ol.control.Rotate.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  if (this.callResetNorth_ !== undefined) {
-    this.callResetNorth_();
-  } else {
-    this.resetNorth_();
-  }
-};
-
-
-/**
- * @private
- */
-ol.control.Rotate.prototype.resetNorth_ = function() {
-  var map = this.getMap();
-  var view = map.getView();
-  if (!view) {
-    // the map does not have a view, so we can't act
-    // upon it
-    return;
-  }
-  var currentRotation = view.getRotation();
-  if (currentRotation !== undefined) {
-    if (this.duration_ > 0) {
-      currentRotation = currentRotation % (2 * Math.PI);
-      if (currentRotation < -Math.PI) {
-        currentRotation += 2 * Math.PI;
-      }
-      if (currentRotation > Math.PI) {
-        currentRotation -= 2 * Math.PI;
-      }
-      map.beforeRender(ol.animation.rotate({
-        rotation: currentRotation,
-        duration: this.duration_,
-        easing: ol.easing.easeOut
-      }));
-    }
-    view.setRotation(0);
-  }
-};
-
-
-/**
- * Update the rotate control element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.Rotate}
- * @api
- */
-ol.control.Rotate.render = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (!frameState) {
-    return;
-  }
-  var rotation = frameState.viewState.rotation;
-  if (rotation != this.rotation_) {
-    var transform = 'rotate(' + rotation + 'rad)';
-    if (this.autoHide_) {
-      var contains = this.element.classList.contains(ol.css.CLASS_HIDDEN);
-      if (!contains && rotation === 0) {
-        this.element.classList.add(ol.css.CLASS_HIDDEN);
-      } else if (contains && rotation !== 0) {
-        this.element.classList.remove(ol.css.CLASS_HIDDEN);
-      }
-    }
-    this.label_.style.msTransform = transform;
-    this.label_.style.webkitTransform = transform;
-    this.label_.style.transform = transform;
-  }
-  this.rotation_ = rotation;
-};
-
-goog.provide('ol.control.Zoom');
-
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
-
-
-/**
- * @classdesc
- * A control with 2 buttons, one for zoom in and one for zoom out.
- * This control is one of the default controls of a map. To style this control
- * use css selectors `.ol-zoom-in` and `.ol-zoom-out`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomOptions=} opt_options Zoom options.
- * @api stable
- */
-ol.control.Zoom = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var className = options.className !== undefined ? options.className : 'ol-zoom';
-
-  var delta = options.delta !== undefined ? options.delta : 1;
-
-  var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+';
-  var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212';
-
-  var zoomInTipLabel = options.zoomInTipLabel !== undefined ?
-      options.zoomInTipLabel : 'Zoom in';
-  var zoomOutTipLabel = options.zoomOutTipLabel !== undefined ?
-      options.zoomOutTipLabel : 'Zoom out';
-
-  var inElement = document.createElement('button');
-  inElement.className = className + '-in';
-  inElement.setAttribute('type', 'button');
-  inElement.title = zoomInTipLabel;
-  inElement.appendChild(
-    typeof zoomInLabel === 'string' ? document.createTextNode(zoomInLabel) : zoomInLabel
-  );
-
-  ol.events.listen(inElement, ol.events.EventType.CLICK,
-      ol.control.Zoom.prototype.handleClick_.bind(this, delta));
-
-  var outElement = document.createElement('button');
-  outElement.className = className + '-out';
-  outElement.setAttribute('type', 'button');
-  outElement.title = zoomOutTipLabel;
-  outElement.appendChild(
-    typeof zoomOutLabel === 'string' ? document.createTextNode(zoomOutLabel) : zoomOutLabel
-  );
-
-  ol.events.listen(outElement, ol.events.EventType.CLICK,
-      ol.control.Zoom.prototype.handleClick_.bind(this, -delta));
-
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = document.createElement('div');
-  element.className = cssClasses;
-  element.appendChild(inElement);
-  element.appendChild(outElement);
-
-  ol.control.Control.call(this, {
-    element: element,
-    target: options.target
-  });
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-
-};
-ol.inherits(ol.control.Zoom, ol.control.Control);
-
-
-/**
- * @param {number} delta Zoom delta.
- * @param {Event} event The event to handle
- * @private
- */
-ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
-  event.preventDefault();
-  this.zoomByDelta_(delta);
-};
-
-
-/**
- * @param {number} delta Zoom delta.
- * @private
- */
-ol.control.Zoom.prototype.zoomByDelta_ = function(delta) {
-  var map = this.getMap();
-  var view = map.getView();
-  if (!view) {
-    // the map does not have a view, so we can't act
-    // upon it
-    return;
-  }
-  var currentResolution = view.getResolution();
-  if (currentResolution) {
-    if (this.duration_ > 0) {
-      map.beforeRender(ol.animation.zoom({
-        resolution: currentResolution,
-        duration: this.duration_,
-        easing: ol.easing.easeOut
-      }));
-    }
-    var newResolution = view.constrainResolution(currentResolution, delta);
-    view.setResolution(newResolution);
-  }
-};
-
-goog.provide('ol.control');
-
-goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.control.Attribution');
-goog.require('ol.control.Rotate');
-goog.require('ol.control.Zoom');
-
-
-/**
- * Set of controls included in maps by default. Unless configured otherwise,
- * this returns a collection containing an instance of each of the following
- * controls:
- * * {@link ol.control.Zoom}
- * * {@link ol.control.Rotate}
- * * {@link ol.control.Attribution}
- *
- * @param {olx.control.DefaultsOptions=} opt_options Defaults options.
- * @return {ol.Collection.<ol.control.Control>} Controls.
- * @api stable
- */
-ol.control.defaults = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var controls = new ol.Collection();
-
-  var zoomControl = options.zoom !== undefined ? options.zoom : true;
-  if (zoomControl) {
-    controls.push(new ol.control.Zoom(options.zoomOptions));
-  }
-
-  var rotateControl = options.rotate !== undefined ? options.rotate : true;
-  if (rotateControl) {
-    controls.push(new ol.control.Rotate(options.rotateOptions));
-  }
-
-  var attributionControl = options.attribution !== undefined ?
-      options.attribution : true;
-  if (attributionControl) {
-    controls.push(new ol.control.Attribution(options.attributionOptions));
-  }
-
-  return controls;
-
-};
-
-// FIXME should listen on appropriate pane, once it is defined
-
-goog.provide('ol.control.MousePosition');
-
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.Object');
-goog.require('ol.control.Control');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * A control to show the 2D coordinates of the mouse cursor. By default, these
- * are in the view projection, but can be in any supported projection.
- * By default the control is shown in the top right corner of the map, but this
- * can be changed by using the css selector `.ol-mouse-position`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.MousePositionOptions=} opt_options Mouse position
- *     options.
- * @api stable
- */
-ol.control.MousePosition = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var element = document.createElement('DIV');
-  element.className = options.className !== undefined ? options.className : 'ol-mouse-position';
-
-  var render = options.render ?
-      options.render : ol.control.MousePosition.render;
-
-  ol.control.Control.call(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.control.MousePosition.Property.PROJECTION),
-      this.handleProjectionChanged_, this);
-
-  if (options.coordinateFormat) {
-    this.setCoordinateFormat(options.coordinateFormat);
-  }
-  if (options.projection) {
-    this.setProjection(ol.proj.get(options.projection));
-  }
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : '';
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.renderedHTML_ = element.innerHTML;
-
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.mapProjection_ = null;
-
-  /**
-   * @private
-   * @type {?ol.TransformFunction}
-   */
-  this.transform_ = null;
-
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.lastMouseMovePixel_ = null;
-
-};
-ol.inherits(ol.control.MousePosition, ol.control.Control);
-
-
-/**
- * Update the mouseposition element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.MousePosition}
- * @api
- */
-ol.control.MousePosition.render = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (!frameState) {
-    this.mapProjection_ = null;
-  } else {
-    if (this.mapProjection_ != frameState.viewState.projection) {
-      this.mapProjection_ = frameState.viewState.projection;
-      this.transform_ = null;
-    }
-  }
-  this.updateHTML_(this.lastMouseMovePixel_);
-};
-
-
-/**
- * @private
- */
-ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
-  this.transform_ = null;
-};
-
-
-/**
- * Return the coordinate format type used to render the current position or
- * undefined.
- * @return {ol.CoordinateFormatType|undefined} The format to render the current
- *     position in.
- * @observable
- * @api stable
- */
-ol.control.MousePosition.prototype.getCoordinateFormat = function() {
-  return /** @type {ol.CoordinateFormatType|undefined} */ (
-      this.get(ol.control.MousePosition.Property.COORDINATE_FORMAT));
-};
-
-
-/**
- * Return the projection that is used to report the mouse position.
- * @return {ol.proj.Projection|undefined} The projection to report mouse
- *     position in.
- * @observable
- * @api stable
- */
-ol.control.MousePosition.prototype.getProjection = function() {
-  return /** @type {ol.proj.Projection|undefined} */ (
-      this.get(ol.control.MousePosition.Property.PROJECTION));
-};
-
-
-/**
- * @param {Event} event Browser event.
- * @protected
- */
-ol.control.MousePosition.prototype.handleMouseMove = function(event) {
-  var map = this.getMap();
-  this.lastMouseMovePixel_ = map.getEventPixel(event);
-  this.updateHTML_(this.lastMouseMovePixel_);
-};
-
-
-/**
- * @param {Event} event Browser event.
- * @protected
- */
-ol.control.MousePosition.prototype.handleMouseOut = function(event) {
-  this.updateHTML_(null);
-  this.lastMouseMovePixel_ = null;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.control.MousePosition.prototype.setMap = function(map) {
-  ol.control.Control.prototype.setMap.call(this, map);
-  if (map) {
-    var viewport = map.getViewport();
-    this.listenerKeys.push(
-        ol.events.listen(viewport, ol.events.EventType.MOUSEMOVE,
-            this.handleMouseMove, this),
-        ol.events.listen(viewport, ol.events.EventType.MOUSEOUT,
-            this.handleMouseOut, this)
-    );
-  }
-};
-
-
-/**
- * Set the coordinate format type used to render the current position.
- * @param {ol.CoordinateFormatType} format The format to render the current
- *     position in.
- * @observable
- * @api stable
- */
-ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
-  this.set(ol.control.MousePosition.Property.COORDINATE_FORMAT, format);
-};
-
-
-/**
- * Set the projection that is used to report the mouse position.
- * @param {ol.proj.Projection} projection The projection to report mouse
- *     position in.
- * @observable
- * @api stable
- */
-ol.control.MousePosition.prototype.setProjection = function(projection) {
-  this.set(ol.control.MousePosition.Property.PROJECTION, projection);
-};
-
-
-/**
- * @param {?ol.Pixel} pixel Pixel.
- * @private
- */
-ol.control.MousePosition.prototype.updateHTML_ = function(pixel) {
-  var html = this.undefinedHTML_;
-  if (pixel && this.mapProjection_) {
-    if (!this.transform_) {
-      var projection = this.getProjection();
-      if (projection) {
-        this.transform_ = ol.proj.getTransformFromProjections(
-            this.mapProjection_, projection);
-      } else {
-        this.transform_ = ol.proj.identityTransform;
-      }
-    }
-    var map = this.getMap();
-    var coordinate = map.getCoordinateFromPixel(pixel);
-    if (coordinate) {
-      this.transform_(coordinate, coordinate);
-      var coordinateFormat = this.getCoordinateFormat();
-      if (coordinateFormat) {
-        html = coordinateFormat(coordinate);
-      } else {
-        html = coordinate.toString();
-      }
-    }
-  }
-  if (!this.renderedHTML_ || html != this.renderedHTML_) {
-    this.element.innerHTML = html;
-    this.renderedHTML_ = html;
-  }
-};
-
-
-/**
- * @enum {string}
- */
-ol.control.MousePosition.Property = {
-  PROJECTION: 'projection',
-  COORDINATE_FORMAT: 'coordinateFormat'
-};
-
-goog.provide('ol.pointer.EventType');
-
-
-/**
- * Constants for event names.
- * @enum {string}
- */
-ol.pointer.EventType = {
-  POINTERMOVE: 'pointermove',
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
-};
-
-goog.provide('ol.webgl');
-
-
-/** Constants taken from goog.webgl
- */
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.ONE = 1;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.SRC_ALPHA = 0x0302;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.COLOR_BUFFER_BIT = 0x00004000;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TRIANGLES = 0x0004;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TRIANGLE_STRIP = 0x0005;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.ARRAY_BUFFER = 0x8892;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.STREAM_DRAW = 0x88E0;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.STATIC_DRAW = 0x88E4;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.DYNAMIC_DRAW = 0x88E8;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.CULL_FACE = 0x0B44;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.BLEND = 0x0BE2;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.STENCIL_TEST = 0x0B90;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.DEPTH_TEST = 0x0B71;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.SCISSOR_TEST = 0x0C11;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.UNSIGNED_BYTE = 0x1401;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.UNSIGNED_SHORT = 0x1403;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.UNSIGNED_INT = 0x1405;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.FLOAT = 0x1406;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.RGBA = 0x1908;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.FRAGMENT_SHADER = 0x8B30;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.VERTEX_SHADER = 0x8B31;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.LINK_STATUS = 0x8B82;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.LINEAR = 0x2601;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TEXTURE_MAG_FILTER = 0x2800;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TEXTURE_MIN_FILTER = 0x2801;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TEXTURE_WRAP_S = 0x2802;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TEXTURE_WRAP_T = 0x2803;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TEXTURE_2D = 0x0DE1;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.TEXTURE0 = 0x84C0;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.CLAMP_TO_EDGE = 0x812F;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.COMPILE_STATUS = 0x8B81;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.webgl.FRAMEBUFFER = 0x8D40;
-
-
-/** end of goog.webgl constants
- */
-
-
-/**
- * @const
- * @private
- * @type {Array.<string>}
- */
-ol.webgl.CONTEXT_IDS_ = [
-  'experimental-webgl',
-  'webgl',
-  'webkit-3d',
-  'moz-webgl'
-];
-
-
-/**
- * @param {HTMLCanvasElement} canvas Canvas.
- * @param {Object=} opt_attributes Attributes.
- * @return {WebGLRenderingContext} WebGL rendering context.
- */
-ol.webgl.getContext = function(canvas, opt_attributes) {
-  var context, i, ii = ol.webgl.CONTEXT_IDS_.length;
-  for (i = 0; i < ii; ++i) {
-    try {
-      context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes);
-      if (context) {
-        return /** @type {!WebGLRenderingContext} */ (context);
-      }
-    } catch (e) {
-      // pass
-    }
-  }
-  return null;
-};
-
-goog.provide('ol.has');
-
-goog.require('ol');
-goog.require('ol.webgl');
-
-var ua = typeof navigator !== 'undefined' ?
-    navigator.userAgent.toLowerCase() : '';
-
-/**
- * User agent string says we are dealing with Firefox as browser.
- * @type {boolean}
- */
-ol.has.FIREFOX = ua.indexOf('firefox') !== -1;
-
-/**
- * User agent string says we are dealing with Safari as browser.
- * @type {boolean}
- */
-ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1;
-
-/**
- * User agent string says we are dealing with a WebKit engine.
- * @type {boolean}
- */
-ol.has.WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1;
-
-/**
- * User agent string says we are dealing with a Mac as platform.
- * @type {boolean}
- */
-ol.has.MAC = ua.indexOf('macintosh') !== -1;
-
-
-/**
- * The ratio between physical pixels and device-independent pixels
- * (dips) on the device (`window.devicePixelRatio`).
- * @const
- * @type {number}
- * @api stable
- */
-ol.has.DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
-
-
-/**
- * True if the browser's Canvas implementation implements {get,set}LineDash.
- * @type {boolean}
- */
-ol.has.CANVAS_LINE_DASH = false;
-
-
-/**
- * True if both the library and browser support Canvas.  Always `false`
- * if `ol.ENABLE_CANVAS` is set to `false` at compile time.
- * @const
- * @type {boolean}
- * @api stable
- */
-ol.has.CANVAS = ol.ENABLE_CANVAS && (
-    /**
-     * @return {boolean} Canvas supported.
-     */
-    function() {
-      if (!('HTMLCanvasElement' in window)) {
-        return false;
-      }
-      try {
-        var context = document.createElement('CANVAS').getContext('2d');
-        if (!context) {
-          return false;
-        } else {
-          if (context.setLineDash !== undefined) {
-            ol.has.CANVAS_LINE_DASH = true;
-          }
-          return true;
-        }
-      } catch (e) {
-        return false;
-      }
-    })();
-
-
-/**
- * Indicates if DeviceOrientation is supported in the user's browser.
- * @const
- * @type {boolean}
- * @api stable
- */
-ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in window;
-
-
-/**
- * Is HTML5 geolocation supported in the current browser?
- * @const
- * @type {boolean}
- * @api stable
- */
-ol.has.GEOLOCATION = 'geolocation' in navigator;
-
-
-/**
- * True if browser supports touch events.
- * @const
- * @type {boolean}
- * @api stable
- */
-ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in window;
-
-
-/**
- * True if browser supports pointer events.
- * @const
- * @type {boolean}
- */
-ol.has.POINTER = 'PointerEvent' in window;
-
-
-/**
- * True if browser supports ms pointer events (IE 10).
- * @const
- * @type {boolean}
- */
-ol.has.MSPOINTER = !!(navigator.msPointerEnabled);
-
-
-/**
- * True if both OpenLayers and browser support WebGL.  Always `false`
- * if `ol.ENABLE_WEBGL` is set to `false` at compile time.
- * @const
- * @type {boolean}
- * @api stable
- */
-ol.has.WEBGL;
-
-
-(function() {
-  if (ol.ENABLE_WEBGL) {
-    var hasWebGL = false;
-    var textureSize;
-    var /** @type {Array.<string>} */ extensions = [];
-
-    if ('WebGLRenderingContext' in window) {
-      try {
-        var canvas = /** @type {HTMLCanvasElement} */
-            (document.createElement('CANVAS'));
-        var gl = ol.webgl.getContext(canvas, {
-          failIfMajorPerformanceCaveat: true
-        });
-        if (gl) {
-          hasWebGL = true;
-          textureSize = /** @type {number} */
-              (gl.getParameter(gl.MAX_TEXTURE_SIZE));
-          extensions = gl.getSupportedExtensions();
-        }
-      } catch (e) {
-        // pass
-      }
-    }
-    ol.has.WEBGL = hasWebGL;
-    ol.WEBGL_EXTENSIONS = extensions;
-    ol.WEBGL_MAX_TEXTURE_SIZE = textureSize;
-  }
-})();
-
-goog.provide('ol.pointer.EventSource');
-
-
-/**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @param {!Object.<string, function(Event)>} mapping Event
- *     mapping.
- * @constructor
- */
-ol.pointer.EventSource = function(dispatcher, mapping) {
-  /**
-   * @type {ol.pointer.PointerEventHandler}
-   */
-  this.dispatcher = dispatcher;
-
-  /**
-   * @private
-   * @const
-   * @type {!Object.<string, function(Event)>}
-   */
-  this.mapping_ = mapping;
-};
-
-
-/**
- * List of events supported by this source.
- * @return {Array.<string>} Event names
- */
-ol.pointer.EventSource.prototype.getEvents = function() {
-  return Object.keys(this.mapping_);
-};
-
-
-/**
- * Returns a mapping between the supported event types and
- * the handlers that should handle an event.
- * @return {Object.<string, function(Event)>}
- *         Event/Handler mapping
- */
-ol.pointer.EventSource.prototype.getMapping = function() {
-  return this.mapping_;
-};
-
-
-/**
- * Returns the handler that should handle a given event type.
- * @param {string} eventType The event type.
- * @return {function(Event)} Handler
- */
-ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
-  return this.mapping_[eventType];
-};
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.MouseSource');
-
-goog.require('ol');
-goog.require('ol.pointer.EventSource');
-
-
-/**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @constructor
- * @extends {ol.pointer.EventSource}
- */
-ol.pointer.MouseSource = function(dispatcher) {
-  var mapping = {
-    'mousedown': this.mousedown,
-    'mousemove': this.mousemove,
-    'mouseup': this.mouseup,
-    'mouseover': this.mouseover,
-    'mouseout': this.mouseout
-  };
-  ol.pointer.EventSource.call(this, dispatcher, mapping);
-
-  /**
-   * @const
-   * @type {!Object.<string, Event|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
-
-  /**
-   * @const
-   * @type {Array.<ol.Pixel>}
-   */
-  this.lastTouches = [];
-};
-ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
-
-
-/**
- * @const
- * @type {number}
- */
-ol.pointer.MouseSource.POINTER_ID = 1;
-
-
-/**
- * @const
- * @type {string}
- */
-ol.pointer.MouseSource.POINTER_TYPE = 'mouse';
-
-
-/**
- * Radius around touchend that swallows mouse events.
- *
- * @const
- * @type {number}
- */
-ol.pointer.MouseSource.DEDUP_DIST = 25;
-
-
-/**
- * Detect if a mouse event was simulated from a touch by
- * checking if previously there was a touch event at the
- * same position.
- *
- * FIXME - Known problem with the native Android browser on
- * Samsung GT-I9100 (Android 4.1.2):
- * In case the page is scrolled, this function does not work
- * correctly when a canvas is used (WebGL or canvas renderer).
- * Mouse listeners on canvas elements (for this browser), create
- * two mouse events: One 'good' and one 'bad' one (on other browsers or
- * when a div is used, there is only one event). For the 'bad' one,
- * clientX/clientY and also pageX/pageY are wrong when the page
- * is scrolled. Because of that, this function can not detect if
- * the events were simulated from a touch event. As result, a
- * pointer event at a wrong position is dispatched, which confuses
- * the map interactions.
- * It is unclear, how one can get the correct position for the event
- * or detect that the positions are invalid.
- *
- * @private
- * @param {Event} inEvent The in event.
- * @return {boolean} True, if the event was generated by a touch.
- */
-ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ = function(inEvent) {
-  var lts = this.lastTouches;
-  var x = inEvent.clientX, y = inEvent.clientY;
-  for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
-    // simulated mouse events will be swallowed near a primary touchend
-    var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]);
-    if (dx <= ol.pointer.MouseSource.DEDUP_DIST &&
-        dy <= ol.pointer.MouseSource.DEDUP_DIST) {
-      return true;
-    }
-  }
-  return false;
-};
-
-
-/**
- * Creates a copy of the original event that will be used
- * for the fake pointer event.
- *
- * @param {Event} inEvent The in event.
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @return {Object} The copied event.
- */
-ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
-  var e = dispatcher.cloneEvent(inEvent, inEvent);
-
-  // forward mouse preventDefault
-  var pd = e.preventDefault;
-  e.preventDefault = function() {
-    inEvent.preventDefault();
-    pd();
-  };
-
-  e.pointerId = ol.pointer.MouseSource.POINTER_ID;
-  e.isPrimary = true;
-  e.pointerType = ol.pointer.MouseSource.POINTER_TYPE;
-
-  return e;
-};
-
-
-/**
- * Handler for `mousedown`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MouseSource.prototype.mousedown = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    // TODO(dfreedman) workaround for some elements not sending mouseup
-    // http://crbug/149091
-    if (ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap) {
-      this.cancel(inEvent);
-    }
-    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-    this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent;
-    this.dispatcher.down(e, inEvent);
-  }
-};
-
-
-/**
- * Handler for `mousemove`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MouseSource.prototype.mousemove = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-    this.dispatcher.move(e, inEvent);
-  }
-};
-
-
-/**
- * Handler for `mouseup`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
-
-    if (p && p.button === inEvent.button) {
-      var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-      this.dispatcher.up(e, inEvent);
-      this.cleanupMouse();
-    }
-  }
-};
-
-
-/**
- * Handler for `mouseover`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MouseSource.prototype.mouseover = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-    this.dispatcher.enterOver(e, inEvent);
-  }
-};
-
-
-/**
- * Handler for `mouseout`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MouseSource.prototype.mouseout = function(inEvent) {
-  if (!this.isEventSimulatedFromTouch_(inEvent)) {
-    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-    this.dispatcher.leaveOut(e, inEvent);
-  }
-};
-
-
-/**
- * Dispatches a `pointercancel` event.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
-  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanupMouse();
-};
-
-
-/**
- * Remove the mouse from the list of active pointers.
- */
-ol.pointer.MouseSource.prototype.cleanupMouse = function() {
-  delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
-};
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.MsSource');
-
-goog.require('ol');
-goog.require('ol.pointer.EventSource');
-
-
-/**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @constructor
- * @extends {ol.pointer.EventSource}
- */
-ol.pointer.MsSource = function(dispatcher) {
-  var mapping = {
-    'MSPointerDown': this.msPointerDown,
-    'MSPointerMove': this.msPointerMove,
-    'MSPointerUp': this.msPointerUp,
-    'MSPointerOut': this.msPointerOut,
-    'MSPointerOver': this.msPointerOver,
-    'MSPointerCancel': this.msPointerCancel,
-    'MSGotPointerCapture': this.msGotPointerCapture,
-    'MSLostPointerCapture': this.msLostPointerCapture
-  };
-  ol.pointer.EventSource.call(this, dispatcher, mapping);
-
-  /**
-   * @const
-   * @type {!Object.<string, Event|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
-
-  /**
-   * @const
-   * @type {Array.<string>}
-   */
-  this.POINTER_TYPES = [
-    '',
-    'unavailable',
-    'touch',
-    'pen',
-    'mouse'
-  ];
-};
-ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource);
-
-
-/**
- * Creates a copy of the original event that will be used
- * for the fake pointer event.
- *
- * @private
- * @param {Event} inEvent The in event.
- * @return {Object} The copied event.
- */
-ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) {
-  var e = inEvent;
-  if (typeof inEvent.pointerType === 'number') {
-    e = this.dispatcher.cloneEvent(inEvent, inEvent);
-    e.pointerType = this.POINTER_TYPES[inEvent.pointerType];
-  }
-
-  return e;
-};
-
-
-/**
- * Remove this pointer from the list of active pointers.
- * @param {number} pointerId Pointer identifier.
- */
-ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
-  delete this.pointerMap[pointerId.toString()];
-};
-
-
-/**
- * Handler for `msPointerDown`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
-  this.pointerMap[inEvent.pointerId.toString()] = inEvent;
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.down(e, inEvent);
-};
-
-
-/**
- * Handler for `msPointerMove`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.move(e, inEvent);
-};
-
-
-/**
- * Handler for `msPointerUp`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.up(e, inEvent);
-  this.cleanup(inEvent.pointerId);
-};
-
-
-/**
- * Handler for `msPointerOut`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.leaveOut(e, inEvent);
-};
-
-
-/**
- * Handler for `msPointerOver`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.enterOver(e, inEvent);
-};
-
-
-/**
- * Handler for `msPointerCancel`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
-  var e = this.prepareEvent_(inEvent);
-  this.dispatcher.cancel(e, inEvent);
-  this.cleanup(inEvent.pointerId);
-};
-
-
-/**
- * Handler for `msLostPointerCapture`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('lostpointercapture',
-      inEvent, inEvent);
-  this.dispatcher.dispatchEvent(e);
-};
-
-
-/**
- * Handler for `msGotPointerCapture`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
-  var e = this.dispatcher.makeEvent('gotpointercapture',
-      inEvent, inEvent);
-  this.dispatcher.dispatchEvent(e);
-};
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.NativeSource');
-
-goog.require('ol');
-goog.require('ol.pointer.EventSource');
-
-
-/**
- * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
- * @constructor
- * @extends {ol.pointer.EventSource}
- */
-ol.pointer.NativeSource = function(dispatcher) {
-  var mapping = {
-    'pointerdown': this.pointerDown,
-    'pointermove': this.pointerMove,
-    'pointerup': this.pointerUp,
-    'pointerout': this.pointerOut,
-    'pointerover': this.pointerOver,
-    'pointercancel': this.pointerCancel,
-    'gotpointercapture': this.gotPointerCapture,
-    'lostpointercapture': this.lostPointerCapture
-  };
-  ol.pointer.EventSource.call(this, dispatcher, mapping);
-};
-ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);
-
-
-/**
- * Handler for `pointerdown`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-
-/**
- * Handler for `pointermove`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-
-/**
- * Handler for `pointerup`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-
-/**
- * Handler for `pointerout`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-
-/**
- * Handler for `pointerover`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-
-/**
- * Handler for `pointercancel`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-
-/**
- * Handler for `lostpointercapture`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-
-/**
- * Handler for `gotpointercapture`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
-  this.dispatcher.fireNativeEvent(inEvent);
-};
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.PointerEvent');
-
-
-goog.require('ol');
-goog.require('ol.events.Event');
-
-
-/**
- * A class for pointer events.
- *
- * This class is used as an abstraction for mouse events,
- * touch events and even native pointer events.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @param {string} type The type of the event to create.
- * @param {Event} originalEvent The event.
- * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of
- *    initial event properties.
- */
-ol.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) {
-  ol.events.Event.call(this, type);
-
-  /**
-   * @const
-   * @type {Event}
-   */
-  this.originalEvent = originalEvent;
-
-  var eventDict = opt_eventDict ? opt_eventDict : {};
-
-  /**
-   * @type {number}
-   */
-  this.buttons = this.getButtons_(eventDict);
-
-  /**
-   * @type {number}
-   */
-  this.pressure = this.getPressure_(eventDict, this.buttons);
-
-  // MouseEvent related properties
-
-  /**
-   * @type {boolean}
-   */
-  this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;
-
-  /**
-   * @type {boolean}
-   */
-  this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;
-
-  /**
-   * @type {Object}
-   */
-  this.view = 'view' in eventDict ? eventDict['view'] : null;
-
-  /**
-   * @type {number}
-   */
-  this.detail = 'detail' in eventDict ? eventDict['detail'] : null;
-
-  /**
-   * @type {number}
-   */
-  this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;
-
-  /**
-   * @type {number}
-   */
-  this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;
-
-  /**
-   * @type {number}
-   */
-  this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;
-
-  /**
-   * @type {number}
-   */
-  this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;
-
-  /**
-   * @type {boolean}
-   */
-  this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;
-
-  /**
-   * @type {boolean}
-   */
-  this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;
-
-  /**
-   * @type {boolean}
-   */
-  this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;
-
-  /**
-   * @type {boolean}
-   */
-  this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;
-
-  /**
-   * @type {number}
-   */
-  this.button = 'button' in eventDict ? eventDict['button'] : 0;
-
-  /**
-   * @type {Node}
-   */
-  this.relatedTarget = 'relatedTarget' in eventDict ?
-      eventDict['relatedTarget'] : null;
-
-  // PointerEvent related properties
-
-  /**
-   * @const
-   * @type {number}
-   */
-  this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;
-
-  /**
-   * @type {number}
-   */
-  this.width = 'width' in eventDict ? eventDict['width'] : 0;
-
-  /**
-   * @type {number}
-   */
-  this.height = 'height' in eventDict ? eventDict['height'] : 0;
-
-  /**
-   * @type {number}
-   */
-  this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;
-
-  /**
-   * @type {number}
-   */
-  this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;
-
-  /**
-   * @type {string}
-   */
-  this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';
-
-  /**
-   * @type {number}
-   */
-  this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;
-
-  /**
-   * @type {boolean}
-   */
-  this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;
-
-  // keep the semantics of preventDefault
-  if (originalEvent.preventDefault) {
-    this.preventDefault = function() {
-      originalEvent.preventDefault();
-    };
-  }
-};
-ol.inherits(ol.pointer.PointerEvent, ol.events.Event);
-
-
-/**
- * @private
- * @param {Object.<string, ?>} eventDict The event dictionary.
- * @return {number} Button indicator.
- */
-ol.pointer.PointerEvent.prototype.getButtons_ = function(eventDict) {
-  // According to the w3c spec,
-  // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button
-  // MouseEvent.button == 0 can mean either no mouse button depressed, or the
-  // left mouse button depressed.
-  //
-  // As of now, the only way to distinguish between the two states of
-  // MouseEvent.button is by using the deprecated MouseEvent.which property, as
-  // this maps mouse buttons to positive integers > 0, and uses 0 to mean that
-  // no mouse button is held.
-  //
-  // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation,
-  // but initMouseEvent does not expose an argument with which to set
-  // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set
-  // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations
-  // of app developers.
-  //
-  // The only way to propagate the correct state of MouseEvent.which and
-  // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0
-  // is to call initMouseEvent with a buttonArg value of -1.
-  //
-  // This is fixed with DOM Level 4's use of buttons
-  var buttons;
-  if (eventDict.buttons || ol.pointer.PointerEvent.HAS_BUTTONS) {
-    buttons = eventDict.buttons;
-  } else {
-    switch (eventDict.which) {
-      case 1: buttons = 1; break;
-      case 2: buttons = 4; break;
-      case 3: buttons = 2; break;
-      default: buttons = 0;
-    }
-  }
-  return buttons;
-};
-
-
-/**
- * @private
- * @param {Object.<string, ?>} eventDict The event dictionary.
- * @param {number} buttons Button indicator.
- * @return {number} The pressure.
- */
-ol.pointer.PointerEvent.prototype.getPressure_ = function(eventDict, buttons) {
-  // Spec requires that pointers without pressure specified use 0.5 for down
-  // state and 0 for up state.
-  var pressure = 0;
-  if (eventDict.pressure) {
-    pressure = eventDict.pressure;
-  } else {
-    pressure = buttons ? 0.5 : 0;
-  }
-  return pressure;
-};
-
-
-/**
- * Is the `buttons` property supported?
- * @type {boolean}
- */
-ol.pointer.PointerEvent.HAS_BUTTONS = false;
-
-
-/**
- * Checks if the `buttons` property is supported.
- */
-(function() {
-  try {
-    var ev = new MouseEvent('click', {buttons: 1});
-    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
-  } catch (e) {
-    // pass
-  }
-})();
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.TouchSource');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.pointer.EventSource');
-goog.require('ol.pointer.MouseSource');
-
-
-/**
- * @constructor
- * @param {ol.pointer.PointerEventHandler} dispatcher The event handler.
- * @param {ol.pointer.MouseSource} mouseSource Mouse source.
- * @extends {ol.pointer.EventSource}
- */
-ol.pointer.TouchSource = function(dispatcher, mouseSource) {
-  var mapping = {
-    'touchstart': this.touchstart,
-    'touchmove': this.touchmove,
-    'touchend': this.touchend,
-    'touchcancel': this.touchcancel
-  };
-  ol.pointer.EventSource.call(this, dispatcher, mapping);
-
-  /**
-   * @const
-   * @type {!Object.<string, Event|Object>}
-   */
-  this.pointerMap = dispatcher.pointerMap;
-
-  /**
-   * @const
-   * @type {ol.pointer.MouseSource}
-   */
-  this.mouseSource = mouseSource;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.firstTouchId_ = undefined;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.clickCount_ = 0;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.resetId_ = undefined;
-};
-ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);
-
-
-/**
- * Mouse event timeout: This should be long enough to
- * ignore compat mouse events made by touch.
- * @const
- * @type {number}
- */
-ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;
-
-
-/**
- * @const
- * @type {string}
- */
-ol.pointer.TouchSource.POINTER_TYPE = 'touch';
-
-
-/**
- * @private
- * @param {Touch} inTouch The in touch.
- * @return {boolean} True, if this is the primary touch.
- */
-ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
-  return this.firstTouchId_ === inTouch.identifier;
-};
-
-
-/**
- * Set primary touch if there are no pointers, or the only pointer is the mouse.
- * @param {Touch} inTouch The in touch.
- * @private
- */
-ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) {
-  var count = Object.keys(this.pointerMap).length;
-  if (count === 0 || (count === 1 &&
-      ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap)) {
-    this.firstTouchId_ = inTouch.identifier;
-    this.cancelResetClickCount_();
-  }
-};
-
-
-/**
- * @private
- * @param {Object} inPointer The in pointer object.
- */
-ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
-  if (inPointer.isPrimary) {
-    this.firstTouchId_ = undefined;
-    this.resetClickCount_();
-  }
-};
-
-
-/**
- * @private
- */
-ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
-  this.resetId_ = setTimeout(
-      this.resetClickCountHandler_.bind(this),
-      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
-};
-
-
-/**
- * @private
- */
-ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
-  this.clickCount_ = 0;
-  this.resetId_ = undefined;
-};
-
-
-/**
- * @private
- */
-ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
-  if (this.resetId_ !== undefined) {
-    clearTimeout(this.resetId_);
-  }
-};
-
-
-/**
- * @private
- * @param {Event} browserEvent Browser event
- * @param {Touch} inTouch Touch event
- * @return {Object} A pointer object.
- */
-ol.pointer.TouchSource.prototype.touchToPointer_ = function(browserEvent, inTouch) {
-  var e = this.dispatcher.cloneEvent(browserEvent, inTouch);
-  // Spec specifies that pointerId 1 is reserved for Mouse.
-  // Touch identifiers can start at 0.
-  // Add 2 to the touch identifier for compatibility.
-  e.pointerId = inTouch.identifier + 2;
-  // TODO: check if this is necessary?
-  //e.target = findTarget(e);
-  e.bubbles = true;
-  e.cancelable = true;
-  e.detail = this.clickCount_;
-  e.button = 0;
-  e.buttons = 1;
-  e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0;
-  e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
-  e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
-  e.isPrimary = this.isPrimaryTouch_(inTouch);
-  e.pointerType = ol.pointer.TouchSource.POINTER_TYPE;
-
-  // make sure that the properties that are different for
-  // each `Touch` object are not copied from the BrowserEvent object
-  e.clientX = inTouch.clientX;
-  e.clientY = inTouch.clientY;
-  e.screenX = inTouch.screenX;
-  e.screenY = inTouch.screenY;
-
-  return e;
-};
-
-
-/**
- * @private
- * @param {Event} inEvent Touch event
- * @param {function(Event, Object)} inFunction In function.
- */
-ol.pointer.TouchSource.prototype.processTouches_ = function(inEvent, inFunction) {
-  var touches = Array.prototype.slice.call(
-      inEvent.changedTouches);
-  var count = touches.length;
-  function preventDefault() {
-    inEvent.preventDefault();
-  }
-  var i, pointer;
-  for (i = 0; i < count; ++i) {
-    pointer = this.touchToPointer_(inEvent, touches[i]);
-    // forward touch preventDefaults
-    pointer.preventDefault = preventDefault;
-    inFunction.call(this, inEvent, pointer);
-  }
-};
-
-
-/**
- * @private
- * @param {TouchList} touchList The touch list.
- * @param {number} searchId Search identifier.
- * @return {boolean} True, if the `Touch` with the given id is in the list.
- */
-ol.pointer.TouchSource.prototype.findTouch_ = function(touchList, searchId) {
-  var l = touchList.length;
-  var touch;
-  for (var i = 0; i < l; i++) {
-    touch = touchList[i];
-    if (touch.identifier === searchId) {
-      return true;
-    }
-  }
-  return false;
-};
-
-
-/**
- * In some instances, a touchstart can happen without a touchend. This
- * leaves the pointermap in a broken state.
- * Therefore, on every touchstart, we remove the touches that did not fire a
- * touchend event.
- * To keep state globally consistent, we fire a pointercancel for
- * this "abandoned" touch
- *
- * @private
- * @param {Event} inEvent The in event.
- */
-ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) {
-  var touchList = inEvent.touches;
-  // pointerMap.getCount() should be < touchList.length here,
-  // as the touchstart has not been processed yet.
-  var keys = Object.keys(this.pointerMap);
-  var count = keys.length;
-  if (count >= touchList.length) {
-    var d = [];
-    var i, key, value;
-    for (i = 0; i < count; ++i) {
-      key = keys[i];
-      value = this.pointerMap[key];
-      // Never remove pointerId == 1, which is mouse.
-      // Touch identifiers are 2 smaller than their pointerId, which is the
-      // index in pointermap.
-      if (key != ol.pointer.MouseSource.POINTER_ID &&
-          !this.findTouch_(touchList, key - 2)) {
-        d.push(value.out);
-      }
-    }
-    for (i = 0; i < d.length; ++i) {
-      this.cancelOut_(inEvent, d[i]);
-    }
-  }
-};
-
-
-/**
- * Handler for `touchstart`, triggers `pointerover`,
- * `pointerenter` and `pointerdown` events.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.TouchSource.prototype.touchstart = function(inEvent) {
-  this.vacuumTouches_(inEvent);
-  this.setPrimaryTouch_(inEvent.changedTouches[0]);
-  this.dedupSynthMouse_(inEvent);
-  this.clickCount_++;
-  this.processTouches_(inEvent, this.overDown_);
-};
-
-
-/**
- * @private
- * @param {Event} browserEvent The event.
- * @param {Object} inPointer The in pointer object.
- */
-ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) {
-  this.pointerMap[inPointer.pointerId] = {
-    target: inPointer.target,
-    out: inPointer,
-    outTarget: inPointer.target
-  };
-  this.dispatcher.over(inPointer, browserEvent);
-  this.dispatcher.enter(inPointer, browserEvent);
-  this.dispatcher.down(inPointer, browserEvent);
-};
-
-
-/**
- * Handler for `touchmove`.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
-  inEvent.preventDefault();
-  this.processTouches_(inEvent, this.moveOverOut_);
-};
-
-
-/**
- * @private
- * @param {Event} browserEvent The event.
- * @param {Object} inPointer The in pointer.
- */
-ol.pointer.TouchSource.prototype.moveOverOut_ = function(browserEvent, inPointer) {
-  var event = inPointer;
-  var pointer = this.pointerMap[event.pointerId];
-  // a finger drifted off the screen, ignore it
-  if (!pointer) {
-    return;
-  }
-  var outEvent = pointer.out;
-  var outTarget = pointer.outTarget;
-  this.dispatcher.move(event, browserEvent);
-  if (outEvent && outTarget !== event.target) {
-    outEvent.relatedTarget = event.target;
-    event.relatedTarget = outTarget;
-    // recover from retargeting by shadow
-    outEvent.target = outTarget;
-    if (event.target) {
-      this.dispatcher.leaveOut(outEvent, browserEvent);
-      this.dispatcher.enterOver(event, browserEvent);
-    } else {
-      // clean up case when finger leaves the screen
-      event.target = outTarget;
-      event.relatedTarget = null;
-      this.cancelOut_(browserEvent, event);
-    }
-  }
-  pointer.out = event;
-  pointer.outTarget = event.target;
-};
-
-
-/**
- * Handler for `touchend`, triggers `pointerup`,
- * `pointerout` and `pointerleave` events.
- *
- * @param {Event} inEvent The event.
- */
-ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
-  this.dedupSynthMouse_(inEvent);
-  this.processTouches_(inEvent, this.upOut_);
-};
-
-
-/**
- * @private
- * @param {Event} browserEvent An event.
- * @param {Object} inPointer The inPointer object.
- */
-ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) {
-  this.dispatcher.up(inPointer, browserEvent);
-  this.dispatcher.out(inPointer, browserEvent);
-  this.dispatcher.leave(inPointer, browserEvent);
-  this.cleanUpPointer_(inPointer);
-};
-
-
-/**
- * Handler for `touchcancel`, triggers `pointercancel`,
- * `pointerout` and `pointerleave` events.
- *
- * @param {Event} inEvent The in event.
- */
-ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
-  this.processTouches_(inEvent, this.cancelOut_);
-};
-
-
-/**
- * @private
- * @param {Event} browserEvent The event.
- * @param {Object} inPointer The in pointer.
- */
-ol.pointer.TouchSource.prototype.cancelOut_ = function(browserEvent, inPointer) {
-  this.dispatcher.cancel(inPointer, browserEvent);
-  this.dispatcher.out(inPointer, browserEvent);
-  this.dispatcher.leave(inPointer, browserEvent);
-  this.cleanUpPointer_(inPointer);
-};
-
-
-/**
- * @private
- * @param {Object} inPointer The inPointer object.
- */
-ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
-  delete this.pointerMap[inPointer.pointerId];
-  this.removePrimaryPointer_(inPointer);
-};
-
-
-/**
- * Prevent synth mouse events from creating pointer events.
- *
- * @private
- * @param {Event} inEvent The in event.
- */
-ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) {
-  var lts = this.mouseSource.lastTouches;
-  var t = inEvent.changedTouches[0];
-  // only the primary finger will synth mouse events
-  if (this.isPrimaryTouch_(t)) {
-    // remember x/y of last touch
-    var lt = [t.clientX, t.clientY];
-    lts.push(lt);
-
-    setTimeout(function() {
-      // remove touch after timeout
-      ol.array.remove(lts, lt);
-    }, ol.pointer.TouchSource.DEDUP_TIMEOUT);
-  }
-};
-
-// Based on https://github.com/Polymer/PointerEvents
-
-// Copyright (c) 2013 The Polymer Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-goog.provide('ol.pointer.PointerEventHandler');
-
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.events.EventTarget');
-
-goog.require('ol.has');
-goog.require('ol.pointer.EventType');
-goog.require('ol.pointer.MouseSource');
-goog.require('ol.pointer.MsSource');
-goog.require('ol.pointer.NativeSource');
-goog.require('ol.pointer.PointerEvent');
-goog.require('ol.pointer.TouchSource');
-
-
-/**
- * @constructor
- * @extends {ol.events.EventTarget}
- * @param {Element|HTMLDocument} element Viewport element.
- */
-ol.pointer.PointerEventHandler = function(element) {
-  ol.events.EventTarget.call(this);
-
-  /**
-   * @const
-   * @private
-   * @type {Element|HTMLDocument}
-   */
-  this.element_ = element;
-
-  /**
-   * @const
-   * @type {!Object.<string, Event|Object>}
-   */
-  this.pointerMap = {};
-
-  /**
-   * @type {Object.<string, function(Event)>}
-   * @private
-   */
-  this.eventMap_ = {};
-
-  /**
-   * @type {Array.<ol.pointer.EventSource>}
-   * @private
-   */
-  this.eventSourceList_ = [];
-
-  this.registerSources();
-};
-ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget);
-
-
-/**
- * Set up the event sources (mouse, touch and native pointers)
- * that generate pointer events.
- */
-ol.pointer.PointerEventHandler.prototype.registerSources = function() {
-  if (ol.has.POINTER) {
-    this.registerSource('native', new ol.pointer.NativeSource(this));
-  } else if (ol.has.MSPOINTER) {
-    this.registerSource('ms', new ol.pointer.MsSource(this));
-  } else {
-    var mouseSource = new ol.pointer.MouseSource(this);
-    this.registerSource('mouse', mouseSource);
-
-    if (ol.has.TOUCH) {
-      this.registerSource('touch',
-          new ol.pointer.TouchSource(this, mouseSource));
-    }
-  }
-
-  // register events on the viewport element
-  this.register_();
-};
-
-
-/**
- * Add a new event source that will generate pointer events.
- *
- * @param {string} name A name for the event source
- * @param {ol.pointer.EventSource} source The source event.
- */
-ol.pointer.PointerEventHandler.prototype.registerSource = function(name, source) {
-  var s = source;
-  var newEvents = s.getEvents();
-
-  if (newEvents) {
-    newEvents.forEach(function(e) {
-      var handler = s.getHandlerForEvent(e);
-
-      if (handler) {
-        this.eventMap_[e] = handler.bind(s);
-      }
-    }, this);
-    this.eventSourceList_.push(s);
-  }
-};
-
-
-/**
- * Set up the events for all registered event sources.
- * @private
- */
-ol.pointer.PointerEventHandler.prototype.register_ = function() {
-  var l = this.eventSourceList_.length;
-  var eventSource;
-  for (var i = 0; i < l; i++) {
-    eventSource = this.eventSourceList_[i];
-    this.addEvents_(eventSource.getEvents());
-  }
-};
-
-
-/**
- * Remove all registered events.
- * @private
- */
-ol.pointer.PointerEventHandler.prototype.unregister_ = function() {
-  var l = this.eventSourceList_.length;
-  var eventSource;
-  for (var i = 0; i < l; i++) {
-    eventSource = this.eventSourceList_[i];
-    this.removeEvents_(eventSource.getEvents());
-  }
-};
-
-
-/**
- * Calls the right handler for a new event.
- * @private
- * @param {Event} inEvent Browser event.
- */
-ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
-  var type = inEvent.type;
-  var handler = this.eventMap_[type];
-  if (handler) {
-    handler(inEvent);
-  }
-};
-
-
-/**
- * Setup listeners for the given events.
- * @private
- * @param {Array.<string>} events List of events.
- */
-ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
-  events.forEach(function(eventName) {
-    ol.events.listen(this.element_, eventName, this.eventHandler_, this);
-  }, this);
-};
-
-
-/**
- * Unregister listeners for the given events.
- * @private
- * @param {Array.<string>} events List of events.
- */
-ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
-  events.forEach(function(e) {
-    ol.events.unlisten(this.element_, e, this.eventHandler_, this);
-  }, this);
-};
-
-
-/**
- * Returns a snapshot of inEvent, with writable properties.
- *
- * @param {Event} event Browser event.
- * @param {Event|Touch} inEvent An event that contains
- *    properties to copy.
- * @return {Object} An object containing shallow copies of
- *    `inEvent`'s properties.
- */
-ol.pointer.PointerEventHandler.prototype.cloneEvent = function(event, inEvent) {
-  var eventCopy = {}, p;
-  for (var i = 0, ii = ol.pointer.CLONE_PROPS.length; i < ii; i++) {
-    p = ol.pointer.CLONE_PROPS[i][0];
-    eventCopy[p] = event[p] || inEvent[p] || ol.pointer.CLONE_PROPS[i][1];
-  }
-
-  return eventCopy;
-};
-
-
-// EVENTS
-
-
-/**
- * Triggers a 'pointerdown' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.down = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event);
-};
-
-
-/**
- * Triggers a 'pointermove' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.move = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event);
-};
-
-
-/**
- * Triggers a 'pointerup' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.up = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERUP, data, event);
-};
-
-
-/**
- * Triggers a 'pointerenter' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.enter = function(data, event) {
-  data.bubbles = false;
-  this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event);
-};
-
-
-/**
- * Triggers a 'pointerleave' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.leave = function(data, event) {
-  data.bubbles = false;
-  this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event);
-};
-
-
-/**
- * Triggers a 'pointerover' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.over = function(data, event) {
-  data.bubbles = true;
-  this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event);
-};
-
-
-/**
- * Triggers a 'pointerout' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.out = function(data, event) {
-  data.bubbles = true;
-  this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event);
-};
-
-
-/**
- * Triggers a 'pointercancel' event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) {
-  this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event);
-};
-
-
-/**
- * Triggers a combination of 'pointerout' and 'pointerleave' events.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) {
-  this.out(data, event);
-  if (!this.contains_(data.target, data.relatedTarget)) {
-    this.leave(data, event);
-  }
-};
-
-
-/**
- * Triggers a combination of 'pointerover' and 'pointerevents' events.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) {
-  this.over(data, event);
-  if (!this.contains_(data.target, data.relatedTarget)) {
-    this.enter(data, event);
-  }
-};
-
-
-/**
- * @private
- * @param {Element} container The container element.
- * @param {Element} contained The contained element.
- * @return {boolean} Returns true if the container element
- *   contains the other element.
- */
-ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) {
-  if (!container || !contained) {
-    return false;
-  }
-  return container.contains(contained);
-};
-
-
-// EVENT CREATION AND TRACKING
-/**
- * Creates a new Event of type `inType`, based on the information in
- * `data`.
- *
- * @param {string} inType A string representing the type of event to create.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`.
- */
-ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) {
-  return new ol.pointer.PointerEvent(inType, event, data);
-};
-
-
-/**
- * Make and dispatch an event in one call.
- * @param {string} inType A string representing the type of event.
- * @param {Object} data Pointer event data.
- * @param {Event} event The event.
- */
-ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) {
-  var e = this.makeEvent(inType, data, event);
-  this.dispatchEvent(e);
-};
-
-
-/**
- * Creates a pointer event from a native pointer event
- * and dispatches this event.
- * @param {Event} event A platform event with a target.
- */
-ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) {
-  var e = this.makeEvent(event.type, event, event);
-  this.dispatchEvent(e);
-};
-
-
-/**
- * Wrap a native mouse event into a pointer event.
- * This proxy method is required for the legacy IE support.
- * @param {string} eventType The pointer event type.
- * @param {Event} event The event.
- * @return {ol.pointer.PointerEvent} The wrapped event.
- */
-ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) {
-  var pointerEvent = this.makeEvent(
-      eventType, ol.pointer.MouseSource.prepareEvent(event, this), event);
-  return pointerEvent;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
-  this.unregister_();
-  ol.events.EventTarget.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * Properties to copy when cloning an event, with default values.
- * @type {Array.<Array>}
- */
-ol.pointer.CLONE_PROPS = [
-  // MouseEvent
-  ['bubbles', false],
-  ['cancelable', false],
-  ['view', null],
-  ['detail', null],
-  ['screenX', 0],
-  ['screenY', 0],
-  ['clientX', 0],
-  ['clientY', 0],
-  ['ctrlKey', false],
-  ['altKey', false],
-  ['shiftKey', false],
-  ['metaKey', false],
-  ['button', 0],
-  ['relatedTarget', null],
-  // DOM Level 3
-  ['buttons', 0],
-  // PointerEvent
-  ['pointerId', 0],
-  ['width', 0],
-  ['height', 0],
-  ['pressure', 0],
-  ['tiltX', 0],
-  ['tiltY', 0],
-  ['pointerType', ''],
-  ['hwTimestamp', 0],
-  ['isPrimary', false],
-  // event instance
-  ['type', ''],
-  ['target', null],
-  ['currentTarget', null],
-  ['which', 0]
-];
-
-goog.provide('ol.MapBrowserEvent');
-goog.provide('ol.MapBrowserEvent.EventType');
-goog.provide('ol.MapBrowserEventHandler');
-goog.provide('ol.MapBrowserPointerEvent');
-
-goog.require('ol');
-goog.require('ol.MapEvent');
-goog.require('ol.events');
-goog.require('ol.events.EventTarget');
-goog.require('ol.events.EventType');
-goog.require('ol.pointer.EventType');
-goog.require('ol.pointer.PointerEventHandler');
-
-
-/**
- * @classdesc
- * Events emitted as map browser events are instances of this type.
- * See {@link ol.Map} for which events trigger a map browser event.
- *
- * @constructor
- * @extends {ol.MapEvent}
- * @implements {oli.MapBrowserEvent}
- * @param {string} type Event type.
- * @param {ol.Map} map Map.
- * @param {Event} browserEvent Browser event.
- * @param {boolean=} opt_dragging Is the map currently being dragged?
- * @param {?olx.FrameState=} opt_frameState Frame state.
- */
-ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging,
-    opt_frameState) {
-
-  ol.MapEvent.call(this, type, map, opt_frameState);
-
-  /**
-   * The original browser event.
-   * @const
-   * @type {Event}
-   * @api stable
-   */
-  this.originalEvent = browserEvent;
-
-  /**
-   * The pixel of the original browser event.
-   * @type {ol.Pixel}
-   * @api stable
-   */
-  this.pixel = map.getEventPixel(browserEvent);
-
-  /**
-   * The coordinate of the original browser event.
-   * @type {ol.Coordinate}
-   * @api stable
-   */
-  this.coordinate = map.getCoordinateFromPixel(this.pixel);
-
-  /**
-   * Indicates if the map is currently being dragged. Only set for
-   * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`.
-   *
-   * @type {boolean}
-   * @api stable
-   */
-  this.dragging = opt_dragging !== undefined ? opt_dragging : false;
-
-};
-ol.inherits(ol.MapBrowserEvent, ol.MapEvent);
-
-
-/**
- * Prevents the default browser action.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
- * @override
- * @api stable
- */
-ol.MapBrowserEvent.prototype.preventDefault = function() {
-  ol.MapEvent.prototype.preventDefault.call(this);
-  this.originalEvent.preventDefault();
-};
-
-
-/**
- * Prevents further propagation of the current event.
- * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
- * @override
- * @api stable
- */
-ol.MapBrowserEvent.prototype.stopPropagation = function() {
-  ol.MapEvent.prototype.stopPropagation.call(this);
-  this.originalEvent.stopPropagation();
-};
-
-
-/**
- * @constructor
- * @extends {ol.MapBrowserEvent}
- * @param {string} type Event type.
- * @param {ol.Map} map Map.
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @param {boolean=} opt_dragging Is the map currently being dragged?
- * @param {?olx.FrameState=} opt_frameState Frame state.
- */
-ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging,
-    opt_frameState) {
-
-  ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging,
-      opt_frameState);
-
-  /**
-   * @const
-   * @type {ol.pointer.PointerEvent}
-   */
-  this.pointerEvent = pointerEvent;
-
-};
-ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);
-
-
-/**
- * @param {ol.Map} map The map with the viewport to listen to events on.
- * @constructor
- * @extends {ol.events.EventTarget}
- */
-ol.MapBrowserEventHandler = function(map) {
-
-  ol.events.EventTarget.call(this);
-
-  /**
-   * This is the element that we will listen to the real events on.
-   * @type {ol.Map}
-   * @private
-   */
-  this.map_ = map;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.clickTimeoutId_ = 0;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.dragging_ = false;
-
-  /**
-   * @type {!Array.<ol.EventsKey>}
-   * @private
-   */
-  this.dragListenerKeys_ = [];
-
-  /**
-   * The most recent "down" type event (or null if none have occurred).
-   * Set on pointerdown.
-   * @type {ol.pointer.PointerEvent}
-   * @private
-   */
-  this.down_ = null;
-
-  var element = this.map_.getViewport();
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.activePointers_ = 0;
-
-  /**
-   * @type {!Object.<number, boolean>}
-   * @private
-   */
-  this.trackedTouches_ = {};
-
-  /**
-   * Event handler which generates pointer events for
-   * the viewport element.
-   *
-   * @type {ol.pointer.PointerEventHandler}
-   * @private
-   */
-  this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element);
-
-  /**
-   * Event handler which generates pointer events for
-   * the document (used when dragging).
-   *
-   * @type {ol.pointer.PointerEventHandler}
-   * @private
-   */
-  this.documentPointerEventHandler_ = null;
-
-  /**
-   * @type {?ol.EventsKey}
-   * @private
-   */
-  this.pointerdownListenerKey_ = ol.events.listen(this.pointerEventHandler_,
-      ol.pointer.EventType.POINTERDOWN,
-      this.handlePointerDown_, this);
-
-  /**
-   * @type {?ol.EventsKey}
-   * @private
-   */
-  this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_,
-      ol.pointer.EventType.POINTERMOVE,
-      this.relayEvent_, this);
-
-};
-ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget);
-
-
-/**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) {
-  var newEvent = new ol.MapBrowserPointerEvent(
-      ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent);
-  this.dispatchEvent(newEvent);
-  if (this.clickTimeoutId_ !== 0) {
-    // double-click
-    clearTimeout(this.clickTimeoutId_);
-    this.clickTimeoutId_ = 0;
-    newEvent = new ol.MapBrowserPointerEvent(
-        ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent);
-    this.dispatchEvent(newEvent);
-  } else {
-    // click
-    this.clickTimeoutId_ = setTimeout(function() {
-      this.clickTimeoutId_ = 0;
-      var newEvent = new ol.MapBrowserPointerEvent(
-          ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent);
-      this.dispatchEvent(newEvent);
-    }.bind(this), 250);
-  }
-};
-
-
-/**
- * Keeps track on how many pointers are currently active.
- *
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.updateActivePointers_ = function(pointerEvent) {
-  var event = pointerEvent;
-
-  if (event.type == ol.MapBrowserEvent.EventType.POINTERUP ||
-      event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) {
-    delete this.trackedTouches_[event.pointerId];
-  } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
-    this.trackedTouches_[event.pointerId] = true;
-  }
-  this.activePointers_ = Object.keys(this.trackedTouches_).length;
-};
-
-
-/**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
-  this.updateActivePointers_(pointerEvent);
-  var newEvent = new ol.MapBrowserPointerEvent(
-      ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent);
-  this.dispatchEvent(newEvent);
-
-  // We emulate click events on left mouse button click, touch contact, and pen
-  // contact. isMouseActionButton returns true in these cases (evt.button is set
-  // to 0).
-  // See http://www.w3.org/TR/pointerevents/#button-states
-  if (!this.dragging_ && this.isMouseActionButton_(pointerEvent)) {
-    ol.DEBUG && console.assert(this.down_, 'this.down_ must be truthy');
-    this.emulateClick_(this.down_);
-  }
-
-  ol.DEBUG && console.assert(this.activePointers_ >= 0,
-      'this.activePointers_ should be equal to or larger than 0');
-  if (this.activePointers_ === 0) {
-    this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
-    this.dragListenerKeys_.length = 0;
-    this.dragging_ = false;
-    this.down_ = null;
-    this.documentPointerEventHandler_.dispose();
-    this.documentPointerEventHandler_ = null;
-  }
-};
-
-
-/**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @return {boolean} If the left mouse button was pressed.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) {
-  return pointerEvent.button === 0;
-};
-
-
-/**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.handlePointerDown_ = function(pointerEvent) {
-  this.updateActivePointers_(pointerEvent);
-  var newEvent = new ol.MapBrowserPointerEvent(
-      ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent);
-  this.dispatchEvent(newEvent);
-
-  this.down_ = pointerEvent;
-
-  if (this.dragListenerKeys_.length === 0) {
-    /* Set up a pointer event handler on the `document`,
-     * which is required when the pointer is moved outside
-     * the viewport when dragging.
-     */
-    this.documentPointerEventHandler_ =
-        new ol.pointer.PointerEventHandler(document);
-
-    this.dragListenerKeys_.push(
-      ol.events.listen(this.documentPointerEventHandler_,
-          ol.MapBrowserEvent.EventType.POINTERMOVE,
-          this.handlePointerMove_, this),
-      ol.events.listen(this.documentPointerEventHandler_,
-          ol.MapBrowserEvent.EventType.POINTERUP,
-          this.handlePointerUp_, this),
-      /* Note that the listener for `pointercancel is set up on
-       * `pointerEventHandler_` and not `documentPointerEventHandler_` like
-       * the `pointerup` and `pointermove` listeners.
-       *
-       * The reason for this is the following: `TouchSource.vacuumTouches_()`
-       * issues `pointercancel` events, when there was no `touchend` for a
-       * `touchstart`. Now, let's say a first `touchstart` is registered on
-       * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up.
-       * But `documentPointerEventHandler_` doesn't know about the first
-       * `touchstart`. If there is no `touchend` for the `touchstart`, we can
-       * only receive a `touchcancel` from `pointerEventHandler_`, because it is
-       * only registered there.
-       */
-      ol.events.listen(this.pointerEventHandler_,
-          ol.MapBrowserEvent.EventType.POINTERCANCEL,
-          this.handlePointerUp_, this)
-    );
-  }
-};
-
-
-/**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.handlePointerMove_ = function(pointerEvent) {
-  // Fix IE10 on windows Surface : When you tap the tablet, it triggers
-  // multiple pointermove events between pointerdown and pointerup with
-  // the exact same coordinates of the pointerdown event. To avoid a
-  // 'false' touchmove event to be dispatched , we test if the pointer
-  // effectively moved.
-  if (this.isMoving_(pointerEvent)) {
-    this.dragging_ = true;
-    var newEvent = new ol.MapBrowserPointerEvent(
-        ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent,
-        this.dragging_);
-    this.dispatchEvent(newEvent);
-  }
-
-  // Some native android browser triggers mousemove events during small period
-  // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or
-  // https://code.google.com/p/android/issues/detail?id=19827
-  // ex: Galaxy Tab P3110 + Android 4.1.1
-  pointerEvent.preventDefault();
-};
-
-
-/**
- * Wrap and relay a pointer event.  Note that this requires that the type
- * string for the MapBrowserPointerEvent matches the PointerEvent type.
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
-  var dragging = !!(this.down_ && this.isMoving_(pointerEvent));
-  this.dispatchEvent(new ol.MapBrowserPointerEvent(
-      pointerEvent.type, this.map_, pointerEvent, dragging));
-};
-
-
-/**
- * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
- * @return {boolean} Is moving.
- * @private
- */
-ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) {
-  return pointerEvent.clientX != this.down_.clientX ||
-      pointerEvent.clientY != this.down_.clientY;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
-  if (this.relayedListenerKey_) {
-    ol.events.unlistenByKey(this.relayedListenerKey_);
-    this.relayedListenerKey_ = null;
-  }
-  if (this.pointerdownListenerKey_) {
-    ol.events.unlistenByKey(this.pointerdownListenerKey_);
-    this.pointerdownListenerKey_ = null;
-  }
-
-  this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.dragListenerKeys_.length = 0;
-
-  if (this.documentPointerEventHandler_) {
-    this.documentPointerEventHandler_.dispose();
-    this.documentPointerEventHandler_ = null;
-  }
-  if (this.pointerEventHandler_) {
-    this.pointerEventHandler_.dispose();
-    this.pointerEventHandler_ = null;
-  }
-  ol.events.EventTarget.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * Constants for event names.
- * @enum {string}
- */
-ol.MapBrowserEvent.EventType = {
-
-  /**
-   * A true single click with no dragging and no double click. Note that this
-   * event is delayed by 250 ms to ensure that it is not a double click.
-   * @event ol.MapBrowserEvent#singleclick
-   * @api stable
-   */
-  SINGLECLICK: 'singleclick',
-
-  /**
-   * A click with no dragging. A double click will fire two of this.
-   * @event ol.MapBrowserEvent#click
-   * @api stable
-   */
-  CLICK: ol.events.EventType.CLICK,
-
-  /**
-   * A true double click, with no dragging.
-   * @event ol.MapBrowserEvent#dblclick
-   * @api stable
-   */
-  DBLCLICK: ol.events.EventType.DBLCLICK,
-
-  /**
-   * Triggered when a pointer is dragged.
-   * @event ol.MapBrowserEvent#pointerdrag
-   * @api
-   */
-  POINTERDRAG: 'pointerdrag',
-
-  /**
-   * Triggered when a pointer is moved. Note that on touch devices this is
-   * triggered when the map is panned, so is not the same as mousemove.
-   * @event ol.MapBrowserEvent#pointermove
-   * @api stable
-   */
-  POINTERMOVE: 'pointermove',
-
-  POINTERDOWN: 'pointerdown',
-  POINTERUP: 'pointerup',
-  POINTEROVER: 'pointerover',
-  POINTEROUT: 'pointerout',
-  POINTERENTER: 'pointerenter',
-  POINTERLEAVE: 'pointerleave',
-  POINTERCANCEL: 'pointercancel'
-};
-
-goog.provide('ol.Tile');
-
-goog.require('ol');
-goog.require('ol.events.EventTarget');
-goog.require('ol.events.EventType');
-
-
-/**
- * @classdesc
- * Base class for tiles.
- *
- * @constructor
- * @extends {ol.events.EventTarget}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Tile.State} state State.
- */
-ol.Tile = function(tileCoord, state) {
-
-  ol.events.EventTarget.call(this);
-
-  /**
-   * @type {ol.TileCoord}
-   */
-  this.tileCoord = tileCoord;
-
-  /**
-   * @protected
-   * @type {ol.Tile.State}
-   */
-  this.state = state;
-
-  /**
-   * An "interim" tile for this tile. The interim tile may be used while this
-   * one is loading, for "smooth" transitions when changing params/dimensions
-   * on the source.
-   * @type {ol.Tile}
-   */
-  this.interimTile = null;
-
-  /**
-   * A key assigned to the tile. This is used by the tile source to determine
-   * if this tile can effectively be used, or if a new tile should be created
-   * and this one be used as an interim tile for this new tile.
-   * @type {string}
-   */
-  this.key = '';
-
-};
-ol.inherits(ol.Tile, ol.events.EventTarget);
-
-
-/**
- * @protected
- */
-ol.Tile.prototype.changed = function() {
-  this.dispatchEvent(ol.events.EventType.CHANGE);
-};
-
-
-/**
- * Get the HTML image element for this tile (may be a Canvas, Image, or Video).
- * @abstract
- * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
- */
-ol.Tile.prototype.getImage = function() {};
-
-
-/**
- * @return {string} Key.
- */
-ol.Tile.prototype.getKey = function() {
-  return this.key + '/' + this.tileCoord;
-};
-
-/**
- * Get the interim tile most suitable for rendering using the chain of interim
- * tiles. This corresponds to the  most recent tile that has been loaded, if no
- * such tile exists, the original tile is returned.
- * @return {!ol.Tile} Best tile for rendering.
- */
-ol.Tile.prototype.getInterimTile = function() {
-  if (!this.interimTile) {
-    //empty chain
-    return this;
-  }
-  var tile = this.interimTile;
-
-  // find the first loaded tile and return it. Since the chain is sorted in
-  // decreasing order of creation time, there is no need to search the remainder
-  // of the list (all those tiles correspond to older requests and will be
-  // cleaned up by refreshInterimChain)
-  do {
-    if (tile.getState() == ol.Tile.State.LOADED) {
-      return tile;
-    }
-    tile = tile.interimTile;
-  } while (tile);
-
-  // we can not find a better tile
-  return this;
-};
-
-/**
- * Goes through the chain of interim tiles and discards sections of the chain
- * that are no longer relevant.
- */
-ol.Tile.prototype.refreshInterimChain = function() {
-  if (!this.interimTile) {
-    return;
-  }
-
-  var tile = this.interimTile;
-  var prev = this;
-
-  do {
-    if (tile.getState() == ol.Tile.State.LOADED) {
-      //we have a loaded tile, we can discard the rest of the list
-      //we would could abort any LOADING tile request
-      //older than this tile (i.e. any LOADING tile following this entry in the chain)
-      tile.interimTile = null;
-      break;
-    } else if (tile.getState() == ol.Tile.State.LOADING) {
-      //keep this LOADING tile any loaded tiles later in the chain are
-      //older than this tile, so we're still interested in the request
-      prev = tile;
-    } else if (tile.getState() == ol.Tile.State.IDLE) {
-      //the head of the list is the most current tile, we don't need
-      //to start any other requests for this chain
-      prev.interimTile = tile.interimTile;
-    } else {
-      prev = tile;
-    }
-    tile = prev.interimTile;
-  } while (tile);
-};
-
-/**
- * Get the tile coordinate for this tile.
- * @return {ol.TileCoord} The tile coordinate.
- * @api
- */
-ol.Tile.prototype.getTileCoord = function() {
-  return this.tileCoord;
-};
-
-
-/**
- * @return {ol.Tile.State} State.
- */
-ol.Tile.prototype.getState = function() {
-  return this.state;
-};
-
-
-/**
- * Load the image or retry if loading previously failed.
- * Loading is taken care of by the tile queue, and calling this method is
- * only needed for preloading or for reloading in case of an error.
- * @abstract
- * @api
- */
-ol.Tile.prototype.load = function() {};
-
-
-/**
- * @enum {number}
- */
-ol.Tile.State = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3,
-  EMPTY: 4,
-  ABORT: 5
-};
-
-goog.provide('ol.structs.PriorityQueue');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.obj');
-
-
-/**
- * Priority queue.
- *
- * The implementation is inspired from the Closure Library's Heap class and
- * Python's heapq module.
- *
- * @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html
- * @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py
- *
- * @constructor
- * @param {function(T): number} priorityFunction Priority function.
- * @param {function(T): string} keyFunction Key function.
- * @struct
- * @template T
- */
-ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
-
-  /**
-   * @type {function(T): number}
-   * @private
-   */
-  this.priorityFunction_ = priorityFunction;
-
-  /**
-   * @type {function(T): string}
-   * @private
-   */
-  this.keyFunction_ = keyFunction;
-
-  /**
-   * @type {Array.<T>}
-   * @private
-   */
-  this.elements_ = [];
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.priorities_ = [];
-
-  /**
-   * @type {Object.<string, boolean>}
-   * @private
-   */
-  this.queuedElements_ = {};
-
-};
-
-
-/**
- * @const
- * @type {number}
- */
-ol.structs.PriorityQueue.DROP = Infinity;
-
-
-if (ol.DEBUG) {
-  /**
-   * FIXME empty description for jsdoc
-   */
-  ol.structs.PriorityQueue.prototype.assertValid = function() {
-    var elements = this.elements_;
-    var priorities = this.priorities_;
-    var n = elements.length;
-    console.assert(priorities.length == n);
-    var i, priority;
-    for (i = 0; i < (n >> 1) - 1; ++i) {
-      priority = priorities[i];
-      console.assert(priority <= priorities[this.getLeftChildIndex_(i)],
-          'priority smaller than or equal to priority of left child (%s <= %s)',
-          priority, priorities[this.getLeftChildIndex_(i)]);
-      console.assert(priority <= priorities[this.getRightChildIndex_(i)],
-          'priority smaller than or equal to priority of right child (%s <= %s)',
-          priority, priorities[this.getRightChildIndex_(i)]);
-    }
-  };
-}
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.structs.PriorityQueue.prototype.clear = function() {
-  this.elements_.length = 0;
-  this.priorities_.length = 0;
-  ol.obj.clear(this.queuedElements_);
-};
-
-
-/**
- * Remove and return the highest-priority element. O(log N).
- * @return {T} Element.
- */
-ol.structs.PriorityQueue.prototype.dequeue = function() {
-  var elements = this.elements_;
-  ol.DEBUG && console.assert(elements.length > 0,
-      'must have elements in order to be able to dequeue');
-  var priorities = this.priorities_;
-  var element = elements[0];
-  if (elements.length == 1) {
-    elements.length = 0;
-    priorities.length = 0;
-  } else {
-    elements[0] = elements.pop();
-    priorities[0] = priorities.pop();
-    this.siftUp_(0);
-  }
-  var elementKey = this.keyFunction_(element);
-  ol.DEBUG && console.assert(elementKey in this.queuedElements_,
-      'key %s is not listed as queued', elementKey);
-  delete this.queuedElements_[elementKey];
-  return element;
-};
-
-
-/**
- * Enqueue an element. O(log N).
- * @param {T} element Element.
- * @return {boolean} The element was added to the queue.
- */
-ol.structs.PriorityQueue.prototype.enqueue = function(element) {
-  ol.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_),
-      31); // Tried to enqueue an `element` that was already added to the queue
-  var priority = this.priorityFunction_(element);
-  if (priority != ol.structs.PriorityQueue.DROP) {
-    this.elements_.push(element);
-    this.priorities_.push(priority);
-    this.queuedElements_[this.keyFunction_(element)] = true;
-    this.siftDown_(0, this.elements_.length - 1);
-    return true;
-  }
-  return false;
-};
-
-
-/**
- * @return {number} Count.
- */
-ol.structs.PriorityQueue.prototype.getCount = function() {
-  return this.elements_.length;
-};
-
-
-/**
- * Gets the index of the left child of the node at the given index.
- * @param {number} index The index of the node to get the left child for.
- * @return {number} The index of the left child.
- * @private
- */
-ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) {
-  return index * 2 + 1;
-};
-
-
-/**
- * Gets the index of the right child of the node at the given index.
- * @param {number} index The index of the node to get the right child for.
- * @return {number} The index of the right child.
- * @private
- */
-ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) {
-  return index * 2 + 2;
-};
-
-
-/**
- * Gets the index of the parent of the node at the given index.
- * @param {number} index The index of the node to get the parent for.
- * @return {number} The index of the parent.
- * @private
- */
-ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
-  return (index - 1) >> 1;
-};
-
-
-/**
- * Make this a heap. O(N).
- * @private
- */
-ol.structs.PriorityQueue.prototype.heapify_ = function() {
-  var i;
-  for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
-    this.siftUp_(i);
-  }
-};
-
-
-/**
- * @return {boolean} Is empty.
- */
-ol.structs.PriorityQueue.prototype.isEmpty = function() {
-  return this.elements_.length === 0;
-};
-
-
-/**
- * @param {string} key Key.
- * @return {boolean} Is key queued.
- */
-ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
-  return key in this.queuedElements_;
-};
-
-
-/**
- * @param {T} element Element.
- * @return {boolean} Is queued.
- */
-ol.structs.PriorityQueue.prototype.isQueued = function(element) {
-  return this.isKeyQueued(this.keyFunction_(element));
-};
-
-
-/**
- * @param {number} index The index of the node to move down.
- * @private
- */
-ol.structs.PriorityQueue.prototype.siftUp_ = function(index) {
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var count = elements.length;
-  var element = elements[index];
-  var priority = priorities[index];
-  var startIndex = index;
-
-  while (index < (count >> 1)) {
-    var lIndex = this.getLeftChildIndex_(index);
-    var rIndex = this.getRightChildIndex_(index);
-
-    var smallerChildIndex = rIndex < count &&
-        priorities[rIndex] < priorities[lIndex] ?
-        rIndex : lIndex;
-
-    elements[index] = elements[smallerChildIndex];
-    priorities[index] = priorities[smallerChildIndex];
-    index = smallerChildIndex;
-  }
-
-  elements[index] = element;
-  priorities[index] = priority;
-  this.siftDown_(startIndex, index);
-};
-
-
-/**
- * @param {number} startIndex The index of the root.
- * @param {number} index The index of the node to move up.
- * @private
- */
-ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) {
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var element = elements[index];
-  var priority = priorities[index];
-
-  while (index > startIndex) {
-    var parentIndex = this.getParentIndex_(index);
-    if (priorities[parentIndex] > priority) {
-      elements[index] = elements[parentIndex];
-      priorities[index] = priorities[parentIndex];
-      index = parentIndex;
-    } else {
-      break;
-    }
-  }
-  elements[index] = element;
-  priorities[index] = priority;
-};
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.structs.PriorityQueue.prototype.reprioritize = function() {
-  var priorityFunction = this.priorityFunction_;
-  var elements = this.elements_;
-  var priorities = this.priorities_;
-  var index = 0;
-  var n = elements.length;
-  var element, i, priority;
-  for (i = 0; i < n; ++i) {
-    element = elements[i];
-    priority = priorityFunction(element);
-    if (priority == ol.structs.PriorityQueue.DROP) {
-      delete this.queuedElements_[this.keyFunction_(element)];
-    } else {
-      priorities[index] = priority;
-      elements[index++] = element;
-    }
-  }
-  elements.length = index;
-  priorities.length = index;
-  this.heapify_();
-};
-
-goog.provide('ol.TileQueue');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.structs.PriorityQueue');
-
-
-/**
- * @constructor
- * @extends {ol.structs.PriorityQueue.<Array>}
- * @param {ol.TilePriorityFunction} tilePriorityFunction
- *     Tile priority function.
- * @param {function(): ?} tileChangeCallback
- *     Function called on each tile change event.
- * @struct
- */
-ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
-
-  ol.structs.PriorityQueue.call(
-      this,
-      /**
-       * @param {Array} element Element.
-       * @return {number} Priority.
-       */
-      function(element) {
-        return tilePriorityFunction.apply(null, element);
-      },
-      /**
-       * @param {Array} element Element.
-       * @return {string} Key.
-       */
-      function(element) {
-        return /** @type {ol.Tile} */ (element[0]).getKey();
-      });
-
-  /**
-   * @private
-   * @type {function(): ?}
-   */
-  this.tileChangeCallback_ = tileChangeCallback;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.tilesLoading_ = 0;
-
-  /**
-   * @private
-   * @type {!Object.<string,boolean>}
-   */
-  this.tilesLoadingKeys_ = {};
-
-};
-ol.inherits(ol.TileQueue, ol.structs.PriorityQueue);
-
-
-/**
- * @inheritDoc
- */
-ol.TileQueue.prototype.enqueue = function(element) {
-  var added = ol.structs.PriorityQueue.prototype.enqueue.call(this, element);
-  if (added) {
-    var tile = element[0];
-    ol.events.listen(tile, ol.events.EventType.CHANGE,
-        this.handleTileChange, this);
-  }
-  return added;
-};
-
-
-/**
- * @return {number} Number of tiles loading.
- */
-ol.TileQueue.prototype.getTilesLoading = function() {
-  return this.tilesLoading_;
-};
-
-
-/**
- * @param {ol.events.Event} event Event.
- * @protected
- */
-ol.TileQueue.prototype.handleTileChange = function(event) {
-  var tile = /** @type {ol.Tile} */ (event.target);
-  var state = tile.getState();
-  if (state === ol.Tile.State.LOADED || state === ol.Tile.State.ERROR ||
-      state === ol.Tile.State.EMPTY || state === ol.Tile.State.ABORT) {
-    ol.events.unlisten(tile, ol.events.EventType.CHANGE,
-        this.handleTileChange, this);
-    var tileKey = tile.getKey();
-    if (tileKey in this.tilesLoadingKeys_) {
-      delete this.tilesLoadingKeys_[tileKey];
-      --this.tilesLoading_;
-    }
-    this.tileChangeCallback_();
-  }
-  ol.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_);
-};
-
-
-/**
- * @param {number} maxTotalLoading Maximum number tiles to load simultaneously.
- * @param {number} maxNewLoads Maximum number of new tiles to load.
- */
-ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) {
-  var newLoads = 0;
-  var tile, tileKey;
-  while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads &&
-         this.getCount() > 0) {
-    tile = /** @type {ol.Tile} */ (this.dequeue()[0]);
-    tileKey = tile.getKey();
-    if (tile.getState() === ol.Tile.State.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
-      this.tilesLoadingKeys_[tileKey] = true;
-      ++this.tilesLoading_;
-      ++newLoads;
-      tile.load();
-    }
-    ol.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_);
-  }
-};
-
-goog.provide('ol.Kinetic');
-
-goog.require('ol.animation');
-
-
-/**
- * @classdesc
- * Implementation of inertial deceleration for map movement.
- *
- * @constructor
- * @param {number} decay Rate of decay (must be negative).
- * @param {number} minVelocity Minimum velocity (pixels/millisecond).
- * @param {number} delay Delay to consider to calculate the kinetic
- *     initial values (milliseconds).
- * @struct
- * @api
- */
-ol.Kinetic = function(decay, minVelocity, delay) {
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.decay_ = decay;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minVelocity_ = minVelocity;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delay_ = delay;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.points_ = [];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.angle_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.initialVelocity_ = 0;
-};
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.Kinetic.prototype.begin = function() {
-  this.points_.length = 0;
-  this.angle_ = 0;
-  this.initialVelocity_ = 0;
-};
-
-
-/**
- * @param {number} x X.
- * @param {number} y Y.
- */
-ol.Kinetic.prototype.update = function(x, y) {
-  this.points_.push(x, y, Date.now());
-};
-
-
-/**
- * @return {boolean} Whether we should do kinetic animation.
- */
-ol.Kinetic.prototype.end = function() {
-  if (this.points_.length < 6) {
-    // at least 2 points are required (i.e. there must be at least 6 elements
-    // in the array)
-    return false;
-  }
-  var delay = Date.now() - this.delay_;
-  var lastIndex = this.points_.length - 3;
-  if (this.points_[lastIndex + 2] < delay) {
-    // the last tracked point is too old, which means that the user stopped
-    // panning before releasing the map
-    return false;
-  }
-
-  // get the first point which still falls into the delay time
-  var firstIndex = lastIndex - 3;
-  while (firstIndex > 0 && this.points_[firstIndex + 2] > delay) {
-    firstIndex -= 3;
-  }
-  var duration = this.points_[lastIndex + 2] - this.points_[firstIndex + 2];
-  var dx = this.points_[lastIndex] - this.points_[firstIndex];
-  var dy = this.points_[lastIndex + 1] - this.points_[firstIndex + 1];
-  this.angle_ = Math.atan2(dy, dx);
-  this.initialVelocity_ = Math.sqrt(dx * dx + dy * dy) / duration;
-  return this.initialVelocity_ > this.minVelocity_;
-};
-
-
-/**
- * @param {ol.Coordinate} source Source coordinate for the animation.
- * @return {ol.PreRenderFunction} Pre-render function for kinetic animation.
- */
-ol.Kinetic.prototype.pan = function(source) {
-  var decay = this.decay_;
-  var initialVelocity = this.initialVelocity_;
-  var velocity = this.minVelocity_ - initialVelocity;
-  var duration = this.getDuration_();
-  var easingFunction = (
-      /**
-       * @param {number} t T.
-       * @return {number} Easing.
-       */
-      function(t) {
-        return initialVelocity * (Math.exp((decay * t) * duration) - 1) /
-            velocity;
-      });
-  return ol.animation.pan({
-    source: source,
-    duration: duration,
-    easing: easingFunction
-  });
-};
-
-
-/**
- * @private
- * @return {number} Duration of animation (milliseconds).
- */
-ol.Kinetic.prototype.getDuration_ = function() {
-  return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_;
-};
-
-
-/**
- * @return {number} Total distance travelled (pixels).
- */
-ol.Kinetic.prototype.getDistance = function() {
-  return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
-};
-
-
-/**
- * @return {number} Angle of the kinetic panning animation (radians).
- */
-ol.Kinetic.prototype.getAngle = function() {
-  return this.angle_;
-};
-
-// FIXME factor out key precondition (shift et. al)
-
-goog.provide('ol.interaction.Interaction');
-
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.animation');
-goog.require('ol.easing');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * User actions that change the state of the map. Some are similar to controls,
- * but are not associated with a DOM element.
- * For example, {@link ol.interaction.KeyboardZoom} is functionally the same as
- * {@link ol.control.Zoom}, but triggered by a keyboard event not a button
- * element event.
- * Although interactions do not have a DOM element, some of them do render
- * vectors and so are visible on the screen.
- *
- * @constructor
- * @param {olx.interaction.InteractionOptions} options Options.
- * @extends {ol.Object}
- * @api
- */
-ol.interaction.Interaction = function(options) {
-
-  ol.Object.call(this);
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = null;
-
-  this.setActive(true);
-
-  /**
-   * @type {function(ol.MapBrowserEvent):boolean}
-   */
-  this.handleEvent = options.handleEvent;
-
-};
-ol.inherits(ol.interaction.Interaction, ol.Object);
-
-
-/**
- * Return whether the interaction is currently active.
- * @return {boolean} `true` if the interaction is active, `false` otherwise.
- * @observable
- * @api
- */
-ol.interaction.Interaction.prototype.getActive = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.interaction.Interaction.Property.ACTIVE));
-};
-
-
-/**
- * Get the map associated with this interaction.
- * @return {ol.Map} Map.
- * @api
- */
-ol.interaction.Interaction.prototype.getMap = function() {
-  return this.map_;
-};
-
-
-/**
- * Activate or deactivate the interaction.
- * @param {boolean} active Active.
- * @observable
- * @api
- */
-ol.interaction.Interaction.prototype.setActive = function(active) {
-  this.set(ol.interaction.Interaction.Property.ACTIVE, active);
-};
-
-
-/**
- * Remove the interaction from its current map and attach it to the new map.
- * Subclasses may set up event handlers to get notified about changes to
- * the map here.
- * @param {ol.Map} map Map.
- */
-ol.interaction.Interaction.prototype.setMap = function(map) {
-  this.map_ = map;
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {ol.View} view View.
- * @param {ol.Coordinate} delta Delta.
- * @param {number=} opt_duration Duration.
- */
-ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) {
-  var currentCenter = view.getCenter();
-  if (currentCenter) {
-    if (opt_duration && opt_duration > 0) {
-      map.beforeRender(ol.animation.pan({
-        source: currentCenter,
-        duration: opt_duration,
-        easing: ol.easing.linear
-      }));
-    }
-    var center = view.constrainCenter(
-        [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]);
-    view.setCenter(center);
-  }
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {ol.View} view View.
- * @param {number|undefined} rotation Rotation.
- * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- */
-ol.interaction.Interaction.rotate = function(map, view, rotation, opt_anchor, opt_duration) {
-  rotation = view.constrainRotation(rotation, 0);
-  ol.interaction.Interaction.rotateWithoutConstraints(
-      map, view, rotation, opt_anchor, opt_duration);
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {ol.View} view View.
- * @param {number|undefined} rotation Rotation.
- * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- */
-ol.interaction.Interaction.rotateWithoutConstraints = function(map, view, rotation, opt_anchor, opt_duration) {
-  if (rotation !== undefined) {
-    var currentRotation = view.getRotation();
-    var currentCenter = view.getCenter();
-    if (currentRotation !== undefined && currentCenter &&
-        opt_duration && opt_duration > 0) {
-      map.beforeRender(ol.animation.rotate({
-        rotation: currentRotation,
-        duration: opt_duration,
-        easing: ol.easing.easeOut
-      }));
-      if (opt_anchor) {
-        map.beforeRender(ol.animation.pan({
-          source: currentCenter,
-          duration: opt_duration,
-          easing: ol.easing.easeOut
-        }));
-      }
-    }
-    view.rotate(rotation, opt_anchor);
-  }
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {ol.View} view View.
- * @param {number|undefined} resolution Resolution to go to.
- * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- * @param {number=} opt_direction Zooming direction; > 0 indicates
- *     zooming out, in which case the constraints system will select
- *     the largest nearest resolution; < 0 indicates zooming in, in
- *     which case the constraints system will select the smallest
- *     nearest resolution; == 0 indicates that the zooming direction
- *     is unknown/not relevant, in which case the constraints system
- *     will select the nearest resolution. If not defined 0 is
- *     assumed.
- */
-ol.interaction.Interaction.zoom = function(map, view, resolution, opt_anchor, opt_duration, opt_direction) {
-  resolution = view.constrainResolution(resolution, 0, opt_direction);
-  ol.interaction.Interaction.zoomWithoutConstraints(
-      map, view, resolution, opt_anchor, opt_duration);
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {ol.View} view View.
- * @param {number} delta Delta from previous zoom level.
- * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- */
-ol.interaction.Interaction.zoomByDelta = function(map, view, delta, opt_anchor, opt_duration) {
-  var currentResolution = view.getResolution();
-  var resolution = view.constrainResolution(currentResolution, delta, 0);
-  ol.interaction.Interaction.zoomWithoutConstraints(
-      map, view, resolution, opt_anchor, opt_duration);
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {ol.View} view View.
- * @param {number|undefined} resolution Resolution to go to.
- * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- */
-ol.interaction.Interaction.zoomWithoutConstraints = function(map, view, resolution, opt_anchor, opt_duration) {
-  if (resolution) {
-    var currentResolution = view.getResolution();
-    var currentCenter = view.getCenter();
-    if (currentResolution !== undefined && currentCenter &&
-        resolution !== currentResolution &&
-        opt_duration && opt_duration > 0) {
-      map.beforeRender(ol.animation.zoom({
-        resolution: currentResolution,
-        duration: opt_duration,
-        easing: ol.easing.easeOut
-      }));
-      if (opt_anchor) {
-        map.beforeRender(ol.animation.pan({
-          source: currentCenter,
-          duration: opt_duration,
-          easing: ol.easing.easeOut
-        }));
-      }
-    }
-    if (opt_anchor) {
-      var center = view.calculateCenterZoom(resolution, opt_anchor);
-      view.setCenter(center);
-    }
-    view.setResolution(resolution);
-  }
-};
-
-
-/**
- * @enum {string}
- */
-ol.interaction.Interaction.Property = {
-  ACTIVE: 'active'
-};
-
-goog.provide('ol.interaction.DoubleClickZoom');
-
-goog.require('ol');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.interaction.Interaction');
-
-
-/**
- * @classdesc
- * Allows the user to zoom by double-clicking on the map.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.DoubleClickZoom = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = options.delta ? options.delta : 1;
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.DoubleClickZoom.handleEvent
-  });
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-
-};
-ol.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction);
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a
- * doubleclick) and eventually zooms the map.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.DoubleClickZoom}
- * @api
- */
-ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) {
-  var stopEvent = false;
-  var browserEvent = mapBrowserEvent.originalEvent;
-  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) {
-    var map = mapBrowserEvent.map;
-    var anchor = mapBrowserEvent.coordinate;
-    var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_;
-    var view = map.getView();
-    ol.interaction.Interaction.zoomByDelta(
-        map, view, delta, anchor, this.duration_);
-    mapBrowserEvent.preventDefault();
-    stopEvent = true;
-  }
-  return !stopEvent;
-};
-
-goog.provide('ol.events.condition');
-
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.asserts');
-goog.require('ol.functions');
-goog.require('ol.has');
-
-
-/**
- * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when
- * additionally the shift-key is pressed).
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the alt key is pressed.
- * @api stable
- */
-ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      !originalEvent.shiftKey);
-};
-
-
-/**
- * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise
- * (e.g. when additionally the platform-modifier-key is pressed).
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the alt and shift keys are pressed.
- * @api stable
- */
-ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      originalEvent.shiftKey);
-};
-
-
-/**
- * Return always true.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True.
- * @function
- * @api stable
- */
-ol.events.condition.always = ol.functions.TRUE;
-
-
-/**
- * Return `true` if the event is a `click` event, `false` otherwise.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event is a map `click` event.
- * @api stable
- */
-ol.events.condition.click = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK;
-};
-
-
-/**
- * Return `true` if the event has an "action"-producing mouse button.
- *
- * By definition, this includes left-click on windows/linux, and left-click
- * without the ctrl key on Macs.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} The result.
- */
-ol.events.condition.mouseActionButton = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return originalEvent.button == 0 &&
-      !(ol.has.WEBKIT && ol.has.MAC && originalEvent.ctrlKey);
-};
-
-
-/**
- * Return always false.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} False.
- * @function
- * @api stable
- */
-ol.events.condition.never = ol.functions.FALSE;
-
-
-/**
- * Return `true` if the browser event is a `pointermove` event, `false`
- * otherwise.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the browser event is a `pointermove` event.
- * @api
- */
-ol.events.condition.pointerMove = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == 'pointermove';
-};
-
-
-/**
- * Return `true` if the event is a map `singleclick` event, `false` otherwise.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event is a map `singleclick` event.
- * @api stable
- */
-ol.events.condition.singleClick = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK;
-};
-
-
-/**
- * Return `true` if the event is a map `dblclick` event, `false` otherwise.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event is a map `dblclick` event.
- * @api stable
- */
-ol.events.condition.doubleClick = function(mapBrowserEvent) {
-  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK;
-};
-
-
-/**
- * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is
- * pressed.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True only if there no modifier keys are pressed.
- * @api stable
- */
-ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      !originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      !originalEvent.shiftKey);
-};
-
-
-/**
- * Return `true` if only the platform-modifier-key (the meta-key on Mac,
- * ctrl-key otherwise) is pressed, `false` otherwise (e.g. when additionally
- * the shift-key is pressed).
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the platform modifier key is pressed.
- * @api stable
- */
-ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      !originalEvent.altKey &&
-      (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) &&
-      !originalEvent.shiftKey);
-};
-
-
-/**
- * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when
- * additionally the alt-key is pressed).
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if only the shift key is pressed.
- * @api stable
- */
-ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
-  var originalEvent = mapBrowserEvent.originalEvent;
-  return (
-      !originalEvent.altKey &&
-      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
-      originalEvent.shiftKey);
-};
-
-
-/**
- * Return `true` if the target element is not editable, i.e. not a `<input>`-,
- * `<select>`- or `<textarea>`-element, `false` otherwise.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True only if the target element is not editable.
- * @api
- */
-ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
-  var target = mapBrowserEvent.originalEvent.target;
-  var tagName = target.tagName;
-  return (
-      tagName !== 'INPUT' &&
-      tagName !== 'SELECT' &&
-      tagName !== 'TEXTAREA');
-};
-
-
-/**
- * Return `true` if the event originates from a mouse device.
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event originates from a mouse device.
- * @api stable
- */
-ol.events.condition.mouseOnly = function(mapBrowserEvent) {
-  ol.asserts.assert(mapBrowserEvent.pointerEvent, 56); // mapBrowserEvent must originate from a pointer event
-  // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType
-  return /** @type {ol.MapBrowserEvent} */ (mapBrowserEvent).pointerEvent.pointerType == 'mouse';
-};
-
-
-/**
- * Return `true` if the event originates from a primary pointer in
- * contact with the surface or if the left mouse button is pressed.
- * @see http://www.w3.org/TR/pointerevents/#button-states
- *
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} True if the event originates from a primary pointer.
- * @api
- */
-ol.events.condition.primaryAction = function(mapBrowserEvent) {
-  var pointerEvent = mapBrowserEvent.pointerEvent;
-  return pointerEvent.isPrimary && pointerEvent.button === 0;
-};
-
-goog.provide('ol.interaction.Pointer');
-
-goog.require('ol');
-goog.require('ol.functions');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserPointerEvent');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.obj');
-
-
-/**
- * @classdesc
- * Base class that calls user-defined functions on `down`, `move` and `up`
- * events. This class also manages "drag sequences".
- *
- * When the `handleDownEvent` user function returns `true` a drag sequence is
- * started. During a drag sequence the `handleDragEvent` user function is
- * called on `move` events. The drag sequence ends when the `handleUpEvent`
- * user function is called and returns `false`.
- *
- * @constructor
- * @param {olx.interaction.PointerOptions=} opt_options Options.
- * @extends {ol.interaction.Interaction}
- * @api
- */
-ol.interaction.Pointer = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var handleEvent = options.handleEvent ?
-      options.handleEvent : ol.interaction.Pointer.handleEvent;
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: handleEvent
-  });
-
-  /**
-   * @type {function(ol.MapBrowserPointerEvent):boolean}
-   * @private
-   */
-  this.handleDownEvent_ = options.handleDownEvent ?
-      options.handleDownEvent : ol.interaction.Pointer.handleDownEvent;
-
-  /**
-   * @type {function(ol.MapBrowserPointerEvent)}
-   * @private
-   */
-  this.handleDragEvent_ = options.handleDragEvent ?
-      options.handleDragEvent : ol.interaction.Pointer.handleDragEvent;
-
-  /**
-   * @type {function(ol.MapBrowserPointerEvent)}
-   * @private
-   */
-  this.handleMoveEvent_ = options.handleMoveEvent ?
-      options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent;
-
-  /**
-   * @type {function(ol.MapBrowserPointerEvent):boolean}
-   * @private
-   */
-  this.handleUpEvent_ = options.handleUpEvent ?
-      options.handleUpEvent : ol.interaction.Pointer.handleUpEvent;
-
-  /**
-   * @type {boolean}
-   * @protected
-   */
-  this.handlingDownUpSequence = false;
-
-  /**
-   * @type {Object.<number, ol.pointer.PointerEvent>}
-   * @private
-   */
-  this.trackedPointers_ = {};
-
-  /**
-   * @type {Array.<ol.pointer.PointerEvent>}
-   * @protected
-   */
-  this.targetPointers = [];
-
-};
-ol.inherits(ol.interaction.Pointer, ol.interaction.Interaction);
-
-
-/**
- * @param {Array.<ol.pointer.PointerEvent>} pointerEvents List of events.
- * @return {ol.Pixel} Centroid pixel.
- */
-ol.interaction.Pointer.centroid = function(pointerEvents) {
-  var length = pointerEvents.length;
-  var clientX = 0;
-  var clientY = 0;
-  for (var i = 0; i < length; i++) {
-    clientX += pointerEvents[i].clientX;
-    clientY += pointerEvents[i].clientY;
-  }
-  return [clientX / length, clientY / length];
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Whether the event is a pointerdown, pointerdrag
- *     or pointerup event.
- * @private
- */
-ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = function(mapBrowserEvent) {
-  var type = mapBrowserEvent.type;
-  return (
-      type === ol.MapBrowserEvent.EventType.POINTERDOWN ||
-      type === ol.MapBrowserEvent.EventType.POINTERDRAG ||
-      type === ol.MapBrowserEvent.EventType.POINTERUP);
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @private
- */
-ol.interaction.Pointer.prototype.updateTrackedPointers_ = function(mapBrowserEvent) {
-  if (this.isPointerDraggingEvent_(mapBrowserEvent)) {
-    var event = mapBrowserEvent.pointerEvent;
-
-    if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) {
-      delete this.trackedPointers_[event.pointerId];
-    } else if (mapBrowserEvent.type ==
-        ol.MapBrowserEvent.EventType.POINTERDOWN) {
-      this.trackedPointers_[event.pointerId] = event;
-    } else if (event.pointerId in this.trackedPointers_) {
-      // update only when there was a pointerdown event for this pointer
-      this.trackedPointers_[event.pointerId] = event;
-    }
-    this.targetPointers = ol.obj.getValues(this.trackedPointers_);
-  }
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.Pointer}
- */
-ol.interaction.Pointer.handleDragEvent = ol.nullFunction;
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Capture dragging.
- * @this {ol.interaction.Pointer}
- */
-ol.interaction.Pointer.handleUpEvent = ol.functions.FALSE;
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Capture dragging.
- * @this {ol.interaction.Pointer}
- */
-ol.interaction.Pointer.handleDownEvent = ol.functions.FALSE;
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.Pointer}
- */
-ol.interaction.Pointer.handleMoveEvent = ol.nullFunction;
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} and may call into
- * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are
- * detected.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.Pointer}
- * @api
- */
-ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) {
-  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
-    return true;
-  }
-
-  var stopEvent = false;
-  this.updateTrackedPointers_(mapBrowserEvent);
-  if (this.handlingDownUpSequence) {
-    if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDRAG) {
-      this.handleDragEvent_(mapBrowserEvent);
-    } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) {
-      this.handlingDownUpSequence = this.handleUpEvent_(mapBrowserEvent);
-    }
-  }
-  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
-    var handled = this.handleDownEvent_(mapBrowserEvent);
-    this.handlingDownUpSequence = handled;
-    stopEvent = this.shouldStopEvent(handled);
-  } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) {
-    this.handleMoveEvent_(mapBrowserEvent);
-  }
-  return !stopEvent;
-};
-
-
-/**
- * This method is used to determine if "down" events should be propagated to
- * other interactions or should be stopped.
- *
- * The method receives the return code of the "handleDownEvent" function.
- *
- * By default this function is the "identity" function. It's overidden in
- * child classes.
- *
- * @param {boolean} handled Was the event handled by the interaction?
- * @return {boolean} Should the event be stopped?
- * @protected
- */
-ol.interaction.Pointer.prototype.shouldStopEvent = function(handled) {
-  return handled;
-};
-
-goog.provide('ol.interaction.DragPan');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.coordinate');
-goog.require('ol.events.condition');
-goog.require('ol.functions');
-goog.require('ol.interaction.Pointer');
-
-
-/**
- * @classdesc
- * Allows the user to pan the map by dragging the map.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.DragPanOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.DragPan = function(opt_options) {
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.DragPan.handleDownEvent_,
-    handleDragEvent: ol.interaction.DragPan.handleDragEvent_,
-    handleUpEvent: ol.interaction.DragPan.handleUpEvent_
-  });
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.Kinetic|undefined}
-   */
-  this.kinetic_ = options.kinetic;
-
-  /**
-   * @private
-   * @type {?ol.PreRenderFunction}
-   */
-  this.kineticPreRenderFn_ = null;
-
-  /**
-   * @type {ol.Pixel}
-   */
-  this.lastCentroid = null;
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.noModifierKeys;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.noKinetic_ = false;
-
-};
-ol.inherits(ol.interaction.DragPan, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragPan}
- * @private
- */
-ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) {
-  ol.DEBUG && console.assert(this.targetPointers.length >= 1,
-      'the length of this.targetPointers should be more than 1');
-  var centroid =
-      ol.interaction.Pointer.centroid(this.targetPointers);
-  if (this.kinetic_) {
-    this.kinetic_.update(centroid[0], centroid[1]);
-  }
-  if (this.lastCentroid) {
-    var deltaX = this.lastCentroid[0] - centroid[0];
-    var deltaY = centroid[1] - this.lastCentroid[1];
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    var viewState = view.getState();
-    var center = [deltaX, deltaY];
-    ol.coordinate.scale(center, viewState.resolution);
-    ol.coordinate.rotate(center, viewState.rotation);
-    ol.coordinate.add(center, viewState.center);
-    center = view.constrainCenter(center);
-    view.setCenter(center);
-  }
-  this.lastCentroid = centroid;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.DragPan}
- * @private
- */
-ol.interaction.DragPan.handleUpEvent_ = function(mapBrowserEvent) {
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  if (this.targetPointers.length === 0) {
-    if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) {
-      var distance = this.kinetic_.getDistance();
-      var angle = this.kinetic_.getAngle();
-      var center = /** @type {!ol.Coordinate} */ (view.getCenter());
-      this.kineticPreRenderFn_ = this.kinetic_.pan(center);
-      map.beforeRender(this.kineticPreRenderFn_);
-      var centerpx = map.getPixelFromCoordinate(center);
-      var dest = map.getCoordinateFromPixel([
-        centerpx[0] - distance * Math.cos(angle),
-        centerpx[1] - distance * Math.sin(angle)
-      ]);
-      dest = view.constrainCenter(dest);
-      view.setCenter(dest);
-    } else {
-      // the view is not updated, force a render
-      map.render();
-    }
-    view.setHint(ol.View.Hint.INTERACTING, -1);
-    return false;
-  } else {
-    this.lastCentroid = null;
-    return true;
-  }
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.DragPan}
- * @private
- */
-ol.interaction.DragPan.handleDownEvent_ = function(mapBrowserEvent) {
-  if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) {
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    this.lastCentroid = null;
-    if (!this.handlingDownUpSequence) {
-      view.setHint(ol.View.Hint.INTERACTING, 1);
-    }
-    if (this.kineticPreRenderFn_ &&
-        map.removePreRenderFunction(this.kineticPreRenderFn_)) {
-      view.setCenter(mapBrowserEvent.frameState.viewState.center);
-      this.kineticPreRenderFn_ = null;
-    }
-    if (this.kinetic_) {
-      this.kinetic_.begin();
-    }
-    // No kinetic as soon as more than one pointer on the screen is
-    // detected. This is to prevent nasty pans after pinch.
-    this.noKinetic_ = this.targetPointers.length > 1;
-    return true;
-  } else {
-    return false;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.DragPan.prototype.shouldStopEvent = ol.functions.FALSE;
-
-goog.provide('ol.interaction.DragRotate');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.events.condition');
-goog.require('ol.functions');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
-
-
-/**
- * @classdesc
- * Allows the user to rotate the map by clicking and dragging on the map,
- * normally combined with an {@link ol.events.condition} that limits
- * it to when the alt and shift keys are held down.
- *
- * This interaction is only supported for mouse devices.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.DragRotateOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.DragRotate = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.DragRotate.handleDownEvent_,
-    handleDragEvent: ol.interaction.DragRotate.handleDragEvent_,
-    handleUpEvent: ol.interaction.DragRotate.handleUpEvent_
-  });
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.altShiftKeysOnly;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastAngle_ = undefined;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-};
-ol.inherits(ol.interaction.DragRotate, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragRotate}
- * @private
- */
-ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return;
-  }
-
-  var map = mapBrowserEvent.map;
-  var size = map.getSize();
-  var offset = mapBrowserEvent.pixel;
-  var theta =
-      Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2);
-  if (this.lastAngle_ !== undefined) {
-    var delta = theta - this.lastAngle_;
-    var view = map.getView();
-    var rotation = view.getRotation();
-    ol.interaction.Interaction.rotateWithoutConstraints(
-        map, view, rotation - delta);
-  }
-  this.lastAngle_ = theta;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.DragRotate}
- * @private
- */
-ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
-  }
-
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  view.setHint(ol.View.Hint.INTERACTING, -1);
-  var rotation = view.getRotation();
-  ol.interaction.Interaction.rotate(map, view, rotation,
-      undefined, this.duration_);
-  return false;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.DragRotate}
- * @private
- */
-ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return false;
-  }
-
-  if (ol.events.condition.mouseActionButton(mapBrowserEvent) &&
-      this.condition_(mapBrowserEvent)) {
-    var map = mapBrowserEvent.map;
-    map.getView().setHint(ol.View.Hint.INTERACTING, 1);
-    this.lastAngle_ = undefined;
-    return true;
-  } else {
-    return false;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.DragRotate.prototype.shouldStopEvent = ol.functions.FALSE;
-
-// FIXME add rotation
-
-goog.provide('ol.render.Box');
-
-goog.require('ol');
-goog.require('ol.Disposable');
-goog.require('ol.geom.Polygon');
-
-
-/**
- * @constructor
- * @extends {ol.Disposable}
- * @param {string} className CSS class name.
- */
-ol.render.Box = function(className) {
-
-  /**
-   * @type {ol.geom.Polygon}
-   * @private
-   */
-  this.geometry_ = null;
-
-  /**
-   * @type {HTMLDivElement}
-   * @private
-   */
-  this.element_ = /** @type {HTMLDivElement} */ (document.createElement('div'));
-  this.element_.style.position = 'absolute';
-  this.element_.className = 'ol-box ' + className;
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = null;
-
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.startPixel_ = null;
-
-  /**
-   * @private
-   * @type {ol.Pixel}
-   */
-  this.endPixel_ = null;
-
-};
-ol.inherits(ol.render.Box, ol.Disposable);
-
-
-/**
- * @inheritDoc
- */
-ol.render.Box.prototype.disposeInternal = function() {
-  this.setMap(null);
-};
-
-
-/**
- * @private
- */
-ol.render.Box.prototype.render_ = function() {
-  var startPixel = this.startPixel_;
-  var endPixel = this.endPixel_;
-  var px = 'px';
-  var style = this.element_.style;
-  style.left = Math.min(startPixel[0], endPixel[0]) + px;
-  style.top = Math.min(startPixel[1], endPixel[1]) + px;
-  style.width = Math.abs(endPixel[0] - startPixel[0]) + px;
-  style.height = Math.abs(endPixel[1] - startPixel[1]) + px;
-};
-
-
-/**
- * @param {ol.Map} map Map.
- */
-ol.render.Box.prototype.setMap = function(map) {
-  if (this.map_) {
-    this.map_.getOverlayContainer().removeChild(this.element_);
-    var style = this.element_.style;
-    style.left = style.top = style.width = style.height = 'inherit';
-  }
-  this.map_ = map;
-  if (this.map_) {
-    this.map_.getOverlayContainer().appendChild(this.element_);
-  }
-};
-
-
-/**
- * @param {ol.Pixel} startPixel Start pixel.
- * @param {ol.Pixel} endPixel End pixel.
- */
-ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
-  this.startPixel_ = startPixel;
-  this.endPixel_ = endPixel;
-  this.createOrUpdateGeometry();
-  this.render_();
-};
-
-
-/**
- * Creates or updates the cached geometry.
- */
-ol.render.Box.prototype.createOrUpdateGeometry = function() {
-  var startPixel = this.startPixel_;
-  var endPixel = this.endPixel_;
-  var pixels = [
-    startPixel,
-    [startPixel[0], endPixel[1]],
-    endPixel,
-    [endPixel[0], startPixel[1]]
-  ];
-  var coordinates = pixels.map(this.map_.getCoordinateFromPixel, this.map_);
-  // close the polygon
-  coordinates[4] = coordinates[0].slice();
-  if (!this.geometry_) {
-    this.geometry_ = new ol.geom.Polygon([coordinates]);
-  } else {
-    this.geometry_.setCoordinates([coordinates]);
-  }
-};
-
-
-/**
- * @return {ol.geom.Polygon} Geometry.
- */
-ol.render.Box.prototype.getGeometry = function() {
-  return this.geometry_;
-};
-
-// FIXME draw drag box
-goog.provide('ol.interaction.DragBox');
-
-goog.require('ol.events.Event');
-goog.require('ol');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.render.Box');
-
-
-/**
- * @const
- * @type {number}
- */
-ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED =
-    ol.DRAG_BOX_HYSTERESIS_PIXELS *
-    ol.DRAG_BOX_HYSTERESIS_PIXELS;
-
-
-/**
- * @classdesc
- * Allows the user to draw a vector box by clicking and dragging on the map,
- * normally combined with an {@link ol.events.condition} that limits
- * it to when the shift or other key is held down. This is used, for example,
- * for zooming to a specific area of the map
- * (see {@link ol.interaction.DragZoom} and
- * {@link ol.interaction.DragRotateAndZoom}).
- *
- * This interaction is only supported for mouse devices.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.interaction.DragBox.Event
- * @param {olx.interaction.DragBoxOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.DragBox = function(opt_options) {
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.DragBox.handleDownEvent_,
-    handleDragEvent: ol.interaction.DragBox.handleDragEvent_,
-    handleUpEvent: ol.interaction.DragBox.handleUpEvent_
-  });
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @type {ol.render.Box}
-   * @private
-   */
-  this.box_ = new ol.render.Box(options.className || 'ol-dragbox');
-
-  /**
-   * @type {ol.Pixel}
-   * @private
-   */
-  this.startPixel_ = null;
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.always;
-
-  /**
-   * @private
-   * @type {ol.DragBoxEndConditionType}
-   */
-  this.boxEndCondition_ = options.boxEndCondition ?
-      options.boxEndCondition : ol.interaction.DragBox.defaultBoxEndCondition;
-};
-ol.inherits(ol.interaction.DragBox, ol.interaction.Pointer);
-
-
-/**
- * The default condition for determining whether the boxend event
- * should fire.
- * @param  {ol.MapBrowserEvent} mapBrowserEvent The originating MapBrowserEvent
- *  leading to the box end.
- * @param  {ol.Pixel} startPixel      The starting pixel of the box.
- * @param  {ol.Pixel} endPixel        The end pixel of the box.
- * @return {boolean} Whether or not the boxend condition should be fired.
- */
-ol.interaction.DragBox.defaultBoxEndCondition = function(mapBrowserEvent,
-    startPixel, endPixel) {
-  var width = endPixel[0] - startPixel[0];
-  var height = endPixel[1] - startPixel[1];
-  return width * width + height * height >=
-      ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragBox}
- * @private
- */
-ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return;
-  }
-
-  this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);
-
-  this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXDRAG,
-    mapBrowserEvent.coordinate, mapBrowserEvent));
-};
-
-
-/**
- * Returns geometry of last drawn box.
- * @return {ol.geom.Polygon} Geometry.
- * @api stable
- */
-ol.interaction.DragBox.prototype.getGeometry = function() {
-  return this.box_.getGeometry();
-};
-
-
-/**
- * To be overriden by child classes.
- * FIXME: use constructor option instead of relying on overridding.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @protected
- */
-ol.interaction.DragBox.prototype.onBoxEnd = ol.nullFunction;
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.DragBox}
- * @private
- */
-ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
-  }
-
-  this.box_.setMap(null);
-
-  if (this.boxEndCondition_(mapBrowserEvent,
-      this.startPixel_, mapBrowserEvent.pixel)) {
-    this.onBoxEnd(mapBrowserEvent);
-    this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXEND,
-        mapBrowserEvent.coordinate, mapBrowserEvent));
-  }
-  return false;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.DragBox}
- * @private
- */
-ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return false;
-  }
-
-  if (ol.events.condition.mouseActionButton(mapBrowserEvent) &&
-      this.condition_(mapBrowserEvent)) {
-    this.startPixel_ = mapBrowserEvent.pixel;
-    this.box_.setMap(mapBrowserEvent.map);
-    this.box_.setPixels(this.startPixel_, this.startPixel_);
-    this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXSTART,
-        mapBrowserEvent.coordinate, mapBrowserEvent));
-    return true;
-  } else {
-    return false;
-  }
-};
-
-
-/**
- * @enum {string}
- */
-ol.interaction.DragBox.EventType = {
-  /**
-   * Triggered upon drag box start.
-   * @event ol.interaction.DragBox.Event#boxstart
-   * @api stable
-   */
-  BOXSTART: 'boxstart',
-
-  /**
-   * Triggered on drag when box is active.
-   * @event ol.interaction.DragBox.Event#boxdrag
-   * @api
-   */
-  BOXDRAG: 'boxdrag',
-
-  /**
-   * Triggered upon drag box end.
-   * @event ol.interaction.DragBox.Event#boxend
-   * @api stable
-   */
-  BOXEND: 'boxend'
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.DragBox} instances are instances of
- * this type.
- *
- * @param {string} type The event type.
- * @param {ol.Coordinate} coordinate The event coordinate.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Originating event.
- * @extends {ol.events.Event}
- * @constructor
- * @implements {oli.DragBoxEvent}
- */
-ol.interaction.DragBox.Event = function(type, coordinate, mapBrowserEvent) {
-  ol.events.Event.call(this, type);
-
-  /**
-   * The coordinate of the drag event.
-   * @const
-   * @type {ol.Coordinate}
-   * @api stable
-   */
-  this.coordinate = coordinate;
-
-  /**
-   * @const
-   * @type {ol.MapBrowserEvent}
-   * @api
-   */
-  this.mapBrowserEvent = mapBrowserEvent;
-
-};
-ol.inherits(ol.interaction.DragBox.Event, ol.events.Event);
-
-goog.provide('ol.interaction.DragZoom');
-
-goog.require('ol');
-goog.require('ol.animation');
-goog.require('ol.easing');
-goog.require('ol.events.condition');
-goog.require('ol.extent');
-goog.require('ol.interaction.DragBox');
-
-
-/**
- * @classdesc
- * Allows the user to zoom the map by clicking and dragging on the map,
- * normally combined with an {@link ol.events.condition} that limits
- * it to when a key, shift by default, is held down.
- *
- * To change the style of the box, use CSS and the `.ol-dragzoom` selector, or
- * your custom one configured with `className`.
- *
- * @constructor
- * @extends {ol.interaction.DragBox}
- * @param {olx.interaction.DragZoomOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.DragZoom = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  var condition = options.condition ?
-      options.condition : ol.events.condition.shiftKeyOnly;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 200;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.out_ = options.out !== undefined ? options.out : false;
-
-  ol.interaction.DragBox.call(this, {
-    condition: condition,
-    className: options.className || 'ol-dragzoom'
-  });
-
-};
-ol.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.DragZoom.prototype.onBoxEnd = function() {
-  var map = this.getMap();
-
-  var view = /** @type {!ol.View} */ (map.getView());
-
-  var size = /** @type {!ol.Size} */ (map.getSize());
-
-  var extent = this.getGeometry().getExtent();
-
-  if (this.out_) {
-    var mapExtent = view.calculateExtent(size);
-    var boxPixelExtent = ol.extent.createOrUpdateFromCoordinates([
-      map.getPixelFromCoordinate(ol.extent.getBottomLeft(extent)),
-      map.getPixelFromCoordinate(ol.extent.getTopRight(extent))]);
-    var factor = view.getResolutionForExtent(boxPixelExtent, size);
-
-    ol.extent.scaleFromCenter(mapExtent, 1 / factor);
-    extent = mapExtent;
-  }
-
-  var resolution = view.constrainResolution(
-      view.getResolutionForExtent(extent, size));
-
-  var currentResolution = /** @type {number} */ (view.getResolution());
-
-  var currentCenter = /** @type {!ol.Coordinate} */ (view.getCenter());
-
-  map.beforeRender(ol.animation.zoom({
-    resolution: currentResolution,
-    duration: this.duration_,
-    easing: ol.easing.easeOut
-  }));
-  map.beforeRender(ol.animation.pan({
-    source: currentCenter,
-    duration: this.duration_,
-    easing: ol.easing.easeOut
-  }));
-
-  view.setCenter(ol.extent.getCenter(extent));
-  view.setResolution(resolution);
-};
-
-goog.provide('ol.events.KeyCode');
-
-/**
- * @enum {number}
- * @const
- */
-ol.events.KeyCode = {
-  LEFT: 37,
-  UP: 38,
-  RIGHT: 39,
-  DOWN: 40
-};
-
-goog.provide('ol.interaction.KeyboardPan');
-
-goog.require('ol');
-goog.require('ol.coordinate');
-goog.require('ol.events.EventType');
-goog.require('ol.events.KeyCode');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Interaction');
-
-
-/**
- * @classdesc
- * Allows the user to pan the map using keyboard arrows.
- * Note that, although this interaction is by default included in maps,
- * the keys can only be used when browser focus is on the element to which
- * the keyboard events are attached. By default, this is the map div,
- * though you can change this with the `keyboardEventTarget` in
- * {@link ol.Map}. `document` never loses focus but, for any other element,
- * focus will have to be on, and returned to, this element if the keys are to
- * function.
- * See also {@link ol.interaction.KeyboardZoom}.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.KeyboardPanOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.KeyboardPan = function(opt_options) {
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.KeyboardPan.handleEvent
-  });
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
-   * @return {boolean} Combined condition result.
-   */
-  this.defaultCondition_ = function(mapBrowserEvent) {
-    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
-      ol.events.condition.targetNotEditable(mapBrowserEvent);
-  };
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition !== undefined ?
-      options.condition : this.defaultCondition_;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 100;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelDelta_ = options.pixelDelta !== undefined ?
-      options.pixelDelta : 128;
-
-};
-ol.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction);
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} if it was a
- * `KeyEvent`, and decides the direction to pan to (if an arrow key was
- * pressed).
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.KeyboardPan}
- * @api
- */
-ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) {
-  var stopEvent = false;
-  if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN) {
-    var keyEvent = mapBrowserEvent.originalEvent;
-    var keyCode = keyEvent.keyCode;
-    if (this.condition_(mapBrowserEvent) &&
-        (keyCode == ol.events.KeyCode.DOWN ||
-        keyCode == ol.events.KeyCode.LEFT ||
-        keyCode == ol.events.KeyCode.RIGHT ||
-        keyCode == ol.events.KeyCode.UP)) {
-      var map = mapBrowserEvent.map;
-      var view = map.getView();
-      var mapUnitsDelta = view.getResolution() * this.pixelDelta_;
-      var deltaX = 0, deltaY = 0;
-      if (keyCode == ol.events.KeyCode.DOWN) {
-        deltaY = -mapUnitsDelta;
-      } else if (keyCode == ol.events.KeyCode.LEFT) {
-        deltaX = -mapUnitsDelta;
-      } else if (keyCode == ol.events.KeyCode.RIGHT) {
-        deltaX = mapUnitsDelta;
-      } else {
-        deltaY = mapUnitsDelta;
-      }
-      var delta = [deltaX, deltaY];
-      ol.coordinate.rotate(delta, view.getRotation());
-      ol.interaction.Interaction.pan(map, view, delta, this.duration_);
-      mapBrowserEvent.preventDefault();
-      stopEvent = true;
-    }
-  }
-  return !stopEvent;
-};
-
-goog.provide('ol.interaction.KeyboardZoom');
-
-goog.require('ol');
-goog.require('ol.events.EventType');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Interaction');
-
-
-/**
- * @classdesc
- * Allows the user to zoom the map using keyboard + and -.
- * Note that, although this interaction is by default included in maps,
- * the keys can only be used when browser focus is on the element to which
- * the keyboard events are attached. By default, this is the map div,
- * though you can change this with the `keyboardEventTarget` in
- * {@link ol.Map}. `document` never loses focus but, for any other element,
- * focus will have to be on, and returned to, this element if the keys are to
- * function.
- * See also {@link ol.interaction.KeyboardPan}.
- *
- * @constructor
- * @param {olx.interaction.KeyboardZoomOptions=} opt_options Options.
- * @extends {ol.interaction.Interaction}
- * @api stable
- */
-ol.interaction.KeyboardZoom = function(opt_options) {
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.KeyboardZoom.handleEvent
-  });
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ? options.condition :
-          ol.events.condition.targetNotEditable;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = options.delta ? options.delta : 1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 100;
-
-};
-ol.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction);
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} if it was a
- * `KeyEvent`, and decides whether to zoom in or out (depending on whether the
- * key pressed was '+' or '-').
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.KeyboardZoom}
- * @api
- */
-ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) {
-  var stopEvent = false;
-  if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN ||
-      mapBrowserEvent.type == ol.events.EventType.KEYPRESS) {
-    var keyEvent = mapBrowserEvent.originalEvent;
-    var charCode = keyEvent.charCode;
-    if (this.condition_(mapBrowserEvent) &&
-        (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) {
-      var map = mapBrowserEvent.map;
-      var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_;
-      var view = map.getView();
-      ol.interaction.Interaction.zoomByDelta(
-          map, view, delta, undefined, this.duration_);
-      mapBrowserEvent.preventDefault();
-      stopEvent = true;
-    }
-  }
-  return !stopEvent;
-};
-
-goog.provide('ol.interaction.MouseWheelZoom');
-
-goog.require('ol');
-goog.require('ol.events.EventType');
-goog.require('ol.has');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.math');
-
-
-/**
- * @classdesc
- * Allows the user to zoom the map by scrolling the mouse wheel.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.MouseWheelZoomOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.MouseWheelZoom = function(opt_options) {
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.MouseWheelZoom.handleEvent
-  });
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.delta_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;
-
-  /**
-   * @private
-   * @type {?ol.Coordinate}
-   */
-  this.lastAnchor_ = null;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.startTime_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.timeoutId_ = undefined;
-
-};
-ol.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction);
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a
- * mousewheel-event) and eventually zooms the map.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.MouseWheelZoom}
- * @api
- */
-ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) {
-  var stopEvent = false;
-  if (mapBrowserEvent.type == ol.events.EventType.WHEEL ||
-      mapBrowserEvent.type == ol.events.EventType.MOUSEWHEEL) {
-    var map = mapBrowserEvent.map;
-    var wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent);
-
-    if (this.useAnchor_) {
-      this.lastAnchor_ = mapBrowserEvent.coordinate;
-    }
-
-    // Delta normalisation inspired by
-    // https://github.com/mapbox/mapbox-gl-js/blob/001c7b9/js/ui/handler/scroll_zoom.js
-    //TODO There's more good stuff in there for inspiration to improve this interaction.
-    var delta;
-    if (mapBrowserEvent.type == ol.events.EventType.WHEEL) {
-      delta = wheelEvent.deltaY;
-      if (ol.has.FIREFOX &&
-          wheelEvent.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
-        delta /= ol.has.DEVICE_PIXEL_RATIO;
-      }
-      if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_LINE) {
-        delta *= 40;
-      }
-    } else if (mapBrowserEvent.type == ol.events.EventType.MOUSEWHEEL) {
-      delta = -wheelEvent.wheelDeltaY;
-      if (ol.has.SAFARI) {
-        delta /= 3;
-      }
-    }
-
-    this.delta_ += delta;
-
-    if (this.startTime_ === undefined) {
-      this.startTime_ = Date.now();
-    }
-
-    var duration = ol.MOUSEWHEELZOOM_TIMEOUT_DURATION;
-    var timeLeft = Math.max(duration - (Date.now() - this.startTime_), 0);
-
-    clearTimeout(this.timeoutId_);
-    this.timeoutId_ = setTimeout(
-        this.doZoom_.bind(this, map), timeLeft);
-
-    mapBrowserEvent.preventDefault();
-    stopEvent = true;
-  }
-  return !stopEvent;
-};
-
-
-/**
- * @private
- * @param {ol.Map} map Map.
- */
-ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) {
-  var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA;
-  var delta = ol.math.clamp(this.delta_, -maxDelta, maxDelta);
-
-  var view = map.getView();
-
-  ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_,
-      this.duration_);
-
-  this.delta_ = 0;
-  this.lastAnchor_ = null;
-  this.startTime_ = undefined;
-  this.timeoutId_ = undefined;
-};
-
-
-/**
- * Enable or disable using the mouse's location as an anchor when zooming
- * @param {boolean} useAnchor true to zoom to the mouse's location, false
- * to zoom to the center of the map
- * @api
- */
-ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) {
-  this.useAnchor_ = useAnchor;
-  if (!useAnchor) {
-    this.lastAnchor_ = null;
-  }
-};
-
-goog.provide('ol.interaction.PinchRotate');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.functions');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
-
-
-/**
- * @classdesc
- * Allows the user to rotate the map by twisting with two fingers
- * on a touch screen.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.PinchRotateOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.PinchRotate = function(opt_options) {
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_,
-    handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_,
-    handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_
-  });
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.anchor_ = null;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastAngle_ = undefined;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.rotating_ = false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.rotationDelta_ = 0.0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.threshold_ = options.threshold !== undefined ? options.threshold : 0.3;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 250;
-
-};
-ol.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.PinchRotate}
- * @private
- */
-ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) {
-  ol.DEBUG && console.assert(this.targetPointers.length >= 2,
-      'length of this.targetPointers should be greater than or equal to 2');
-  var rotationDelta = 0.0;
-
-  var touch0 = this.targetPointers[0];
-  var touch1 = this.targetPointers[1];
-
-  // angle between touches
-  var angle = Math.atan2(
-      touch1.clientY - touch0.clientY,
-      touch1.clientX - touch0.clientX);
-
-  if (this.lastAngle_ !== undefined) {
-    var delta = angle - this.lastAngle_;
-    this.rotationDelta_ += delta;
-    if (!this.rotating_ &&
-        Math.abs(this.rotationDelta_) > this.threshold_) {
-      this.rotating_ = true;
-    }
-    rotationDelta = delta;
-  }
-  this.lastAngle_ = angle;
-
-  var map = mapBrowserEvent.map;
-
-  // rotate anchor point.
-  // FIXME: should be the intersection point between the lines:
-  //     touch0,touch1 and previousTouch0,previousTouch1
-  var viewportPosition = map.getViewport().getBoundingClientRect();
-  var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
-  centroid[0] -= viewportPosition.left;
-  centroid[1] -= viewportPosition.top;
-  this.anchor_ = map.getCoordinateFromPixel(centroid);
-
-  // rotate
-  if (this.rotating_) {
-    var view = map.getView();
-    var rotation = view.getRotation();
-    map.render();
-    ol.interaction.Interaction.rotateWithoutConstraints(map, view,
-        rotation + rotationDelta, this.anchor_);
-  }
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.PinchRotate}
- * @private
- */
-ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) {
-  if (this.targetPointers.length < 2) {
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    view.setHint(ol.View.Hint.INTERACTING, -1);
-    if (this.rotating_) {
-      var rotation = view.getRotation();
-      ol.interaction.Interaction.rotate(
-          map, view, rotation, this.anchor_, this.duration_);
-    }
-    return false;
-  } else {
-    return true;
-  }
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.PinchRotate}
- * @private
- */
-ol.interaction.PinchRotate.handleDownEvent_ = function(mapBrowserEvent) {
-  if (this.targetPointers.length >= 2) {
-    var map = mapBrowserEvent.map;
-    this.anchor_ = null;
-    this.lastAngle_ = undefined;
-    this.rotating_ = false;
-    this.rotationDelta_ = 0.0;
-    if (!this.handlingDownUpSequence) {
-      map.getView().setHint(ol.View.Hint.INTERACTING, 1);
-    }
-    map.render();
-    return true;
-  } else {
-    return false;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.PinchRotate.prototype.shouldStopEvent = ol.functions.FALSE;
-
-goog.provide('ol.interaction.PinchZoom');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.functions');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
-
-
-/**
- * @classdesc
- * Allows the user to zoom the map by pinching with two fingers
- * on a touch screen.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.PinchZoomOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.PinchZoom = function(opt_options) {
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_,
-    handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_,
-    handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_
-  });
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.anchor_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 400;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastDistance_ = undefined;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.lastScaleDelta_ = 1;
-
-};
-ol.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.PinchZoom}
- * @private
- */
-ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) {
-  ol.DEBUG && console.assert(this.targetPointers.length >= 2,
-      'length of this.targetPointers should be 2 or more');
-  var scaleDelta = 1.0;
-
-  var touch0 = this.targetPointers[0];
-  var touch1 = this.targetPointers[1];
-  var dx = touch0.clientX - touch1.clientX;
-  var dy = touch0.clientY - touch1.clientY;
-
-  // distance between touches
-  var distance = Math.sqrt(dx * dx + dy * dy);
-
-  if (this.lastDistance_ !== undefined) {
-    scaleDelta = this.lastDistance_ / distance;
-  }
-  this.lastDistance_ = distance;
-  if (scaleDelta != 1.0) {
-    this.lastScaleDelta_ = scaleDelta;
-  }
-
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  var resolution = view.getResolution();
-
-  // scale anchor point.
-  var viewportPosition = map.getViewport().getBoundingClientRect();
-  var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
-  centroid[0] -= viewportPosition.left;
-  centroid[1] -= viewportPosition.top;
-  this.anchor_ = map.getCoordinateFromPixel(centroid);
-
-  // scale, bypass the resolution constraint
-  map.render();
-  ol.interaction.Interaction.zoomWithoutConstraints(
-      map, view, resolution * scaleDelta, this.anchor_);
-
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.PinchZoom}
- * @private
- */
-ol.interaction.PinchZoom.handleUpEvent_ = function(mapBrowserEvent) {
-  if (this.targetPointers.length < 2) {
-    var map = mapBrowserEvent.map;
-    var view = map.getView();
-    view.setHint(ol.View.Hint.INTERACTING, -1);
-    var resolution = view.getResolution();
-    // Zoom to final resolution, with an animation, and provide a
-    // direction not to zoom out/in if user was pinching in/out.
-    // Direction is > 0 if pinching out, and < 0 if pinching in.
-    var direction = this.lastScaleDelta_ - 1;
-    ol.interaction.Interaction.zoom(map, view, resolution,
-        this.anchor_, this.duration_, direction);
-    return false;
-  } else {
-    return true;
-  }
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.PinchZoom}
- * @private
- */
-ol.interaction.PinchZoom.handleDownEvent_ = function(mapBrowserEvent) {
-  if (this.targetPointers.length >= 2) {
-    var map = mapBrowserEvent.map;
-    this.anchor_ = null;
-    this.lastDistance_ = undefined;
-    this.lastScaleDelta_ = 1;
-    if (!this.handlingDownUpSequence) {
-      map.getView().setHint(ol.View.Hint.INTERACTING, 1);
-    }
-    map.render();
-    return true;
-  } else {
-    return false;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.PinchZoom.prototype.shouldStopEvent = ol.functions.FALSE;
-
-goog.provide('ol.interaction');
-
-goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.Kinetic');
-goog.require('ol.interaction.DoubleClickZoom');
-goog.require('ol.interaction.DragPan');
-goog.require('ol.interaction.DragRotate');
-goog.require('ol.interaction.DragZoom');
-goog.require('ol.interaction.KeyboardPan');
-goog.require('ol.interaction.KeyboardZoom');
-goog.require('ol.interaction.MouseWheelZoom');
-goog.require('ol.interaction.PinchRotate');
-goog.require('ol.interaction.PinchZoom');
-
-
-/**
- * Set of interactions included in maps by default. Specific interactions can be
- * excluded by setting the appropriate option to false in the constructor
- * options, but the order of the interactions is fixed.  If you want to specify
- * a different order for interactions, you will need to create your own
- * {@link ol.interaction.Interaction} instances and insert them into a
- * {@link ol.Collection} in the order you want before creating your
- * {@link ol.Map} instance. The default set of interactions, in sequence, is:
- * * {@link ol.interaction.DragRotate}
- * * {@link ol.interaction.DoubleClickZoom}
- * * {@link ol.interaction.DragPan}
- * * {@link ol.interaction.PinchRotate}
- * * {@link ol.interaction.PinchZoom}
- * * {@link ol.interaction.KeyboardPan}
- * * {@link ol.interaction.KeyboardZoom}
- * * {@link ol.interaction.MouseWheelZoom}
- * * {@link ol.interaction.DragZoom}
- *
- * @param {olx.interaction.DefaultsOptions=} opt_options Defaults options.
- * @return {ol.Collection.<ol.interaction.Interaction>} A collection of
- * interactions to be used with the ol.Map constructor's interactions option.
- * @api stable
- */
-ol.interaction.defaults = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var interactions = new ol.Collection();
-
-  var kinetic = new ol.Kinetic(-0.005, 0.05, 100);
-
-  var altShiftDragRotate = options.altShiftDragRotate !== undefined ?
-      options.altShiftDragRotate : true;
-  if (altShiftDragRotate) {
-    interactions.push(new ol.interaction.DragRotate());
-  }
-
-  var doubleClickZoom = options.doubleClickZoom !== undefined ?
-      options.doubleClickZoom : true;
-  if (doubleClickZoom) {
-    interactions.push(new ol.interaction.DoubleClickZoom({
-      delta: options.zoomDelta,
-      duration: options.zoomDuration
-    }));
-  }
-
-  var dragPan = options.dragPan !== undefined ? options.dragPan : true;
-  if (dragPan) {
-    interactions.push(new ol.interaction.DragPan({
-      kinetic: kinetic
-    }));
-  }
-
-  var pinchRotate = options.pinchRotate !== undefined ? options.pinchRotate :
-      true;
-  if (pinchRotate) {
-    interactions.push(new ol.interaction.PinchRotate());
-  }
-
-  var pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true;
-  if (pinchZoom) {
-    interactions.push(new ol.interaction.PinchZoom({
-      duration: options.zoomDuration
-    }));
-  }
-
-  var keyboard = options.keyboard !== undefined ? options.keyboard : true;
-  if (keyboard) {
-    interactions.push(new ol.interaction.KeyboardPan());
-    interactions.push(new ol.interaction.KeyboardZoom({
-      delta: options.zoomDelta,
-      duration: options.zoomDuration
-    }));
-  }
-
-  var mouseWheelZoom = options.mouseWheelZoom !== undefined ?
-      options.mouseWheelZoom : true;
-  if (mouseWheelZoom) {
-    interactions.push(new ol.interaction.MouseWheelZoom({
-      duration: options.zoomDuration
-    }));
-  }
-
-  var shiftDragZoom = options.shiftDragZoom !== undefined ?
-      options.shiftDragZoom : true;
-  if (shiftDragZoom) {
-    interactions.push(new ol.interaction.DragZoom({
-      duration: options.zoomDuration
-    }));
-  }
-
-  return interactions;
-
-};
-
-goog.provide('ol.layer.Base');
-goog.provide('ol.layer.LayerProperty');
-
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.math');
-goog.require('ol.obj');
-
-
-/**
- * @enum {string}
- */
-ol.layer.LayerProperty = {
-  OPACITY: 'opacity',
-  VISIBLE: 'visible',
-  EXTENT: 'extent',
-  Z_INDEX: 'zIndex',
-  MAX_RESOLUTION: 'maxResolution',
-  MIN_RESOLUTION: 'minResolution',
-  SOURCE: 'source'
-};
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Note that with `ol.layer.Base` and all its subclasses, any property set in
- * the options is set as a {@link ol.Object} property on the layer object, so
- * is observable, and has get/set accessors.
- *
- * @constructor
- * @extends {ol.Object}
- * @param {olx.layer.BaseOptions} options Layer options.
- * @api stable
- */
-ol.layer.Base = function(options) {
-
-  ol.Object.call(this);
-
-  /**
-   * @type {Object.<string, *>}
-   */
-  var properties = ol.obj.assign({}, options);
-  properties[ol.layer.LayerProperty.OPACITY] =
-      options.opacity !== undefined ? options.opacity : 1;
-  properties[ol.layer.LayerProperty.VISIBLE] =
-      options.visible !== undefined ? options.visible : true;
-  properties[ol.layer.LayerProperty.Z_INDEX] =
-      options.zIndex !== undefined ? options.zIndex : 0;
-  properties[ol.layer.LayerProperty.MAX_RESOLUTION] =
-      options.maxResolution !== undefined ? options.maxResolution : Infinity;
-  properties[ol.layer.LayerProperty.MIN_RESOLUTION] =
-      options.minResolution !== undefined ? options.minResolution : 0;
-
-  this.setProperties(properties);
-
-  /**
-   * @type {ol.LayerState}
-   * @private
-   */
-  this.state_ = /** @type {ol.LayerState} */ ({
-    layer: /** @type {ol.layer.Layer} */ (this),
-    managed: true
-  });
-
-};
-ol.inherits(ol.layer.Base, ol.Object);
-
-
-/**
- * @return {ol.LayerState} Layer state.
- */
-ol.layer.Base.prototype.getLayerState = function() {
-  this.state_.opacity = ol.math.clamp(this.getOpacity(), 0, 1);
-  this.state_.sourceState = this.getSourceState();
-  this.state_.visible = this.getVisible();
-  this.state_.extent = this.getExtent();
-  this.state_.zIndex = this.getZIndex();
-  this.state_.maxResolution = this.getMaxResolution();
-  this.state_.minResolution = Math.max(this.getMinResolution(), 0);
-
-  return this.state_;
-};
-
-
-/**
- * @abstract
- * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
- *     modified in place).
- * @return {Array.<ol.layer.Layer>} Array of layers.
- */
-ol.layer.Base.prototype.getLayersArray = function(opt_array) {};
-
-
-/**
- * @abstract
- * @param {Array.<ol.LayerState>=} opt_states Optional list of layer
- *     states (to be modified in place).
- * @return {Array.<ol.LayerState>} List of layer states.
- */
-ol.layer.Base.prototype.getLayerStatesArray = function(opt_states) {};
-
-
-/**
- * Return the {@link ol.Extent extent} of the layer or `undefined` if it
- * will be visible regardless of extent.
- * @return {ol.Extent|undefined} The layer extent.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.getExtent = function() {
-  return /** @type {ol.Extent|undefined} */ (
-      this.get(ol.layer.LayerProperty.EXTENT));
-};
-
-
-/**
- * Return the maximum resolution of the layer.
- * @return {number} The maximum resolution of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.getMaxResolution = function() {
-  return /** @type {number} */ (
-      this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
-};
-
-
-/**
- * Return the minimum resolution of the layer.
- * @return {number} The minimum resolution of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.getMinResolution = function() {
-  return /** @type {number} */ (
-      this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
-};
-
-
-/**
- * Return the opacity of the layer (between 0 and 1).
- * @return {number} The opacity of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.getOpacity = function() {
-  return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY));
-};
-
-
-/**
- * @abstract
- * @return {ol.source.State} Source state.
- */
-ol.layer.Base.prototype.getSourceState = function() {};
-
-
-/**
- * Return the visibility of the layer (`true` or `false`).
- * @return {boolean} The visibility of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.getVisible = function() {
-  return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
-};
-
-
-/**
- * Return the Z-index of the layer, which is used to order layers before
- * rendering. The default Z-index is 0.
- * @return {number} The Z-index of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.getZIndex = function() {
-  return /** @type {number} */ (this.get(ol.layer.LayerProperty.Z_INDEX));
-};
-
-
-/**
- * Set the extent at which the layer is visible.  If `undefined`, the layer
- * will be visible at all extents.
- * @param {ol.Extent|undefined} extent The extent of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.setExtent = function(extent) {
-  this.set(ol.layer.LayerProperty.EXTENT, extent);
-};
-
-
-/**
- * Set the maximum resolution at which the layer is visible.
- * @param {number} maxResolution The maximum resolution of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
-  this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution);
-};
-
-
-/**
- * Set the minimum resolution at which the layer is visible.
- * @param {number} minResolution The minimum resolution of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.setMinResolution = function(minResolution) {
-  this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution);
-};
-
-
-/**
- * Set the opacity of the layer, allowed values range from 0 to 1.
- * @param {number} opacity The opacity of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.setOpacity = function(opacity) {
-  this.set(ol.layer.LayerProperty.OPACITY, opacity);
-};
-
-
-/**
- * Set the visibility of the layer (`true` or `false`).
- * @param {boolean} visible The visibility of the layer.
- * @observable
- * @api stable
- */
-ol.layer.Base.prototype.setVisible = function(visible) {
-  this.set(ol.layer.LayerProperty.VISIBLE, visible);
-};
-
-
-/**
- * Set Z-index of the layer, which is used to order layers before rendering.
- * The default Z-index is 0.
- * @param {number} zindex The z-index of the layer.
- * @observable
- * @api
- */
-ol.layer.Base.prototype.setZIndex = function(zindex) {
-  this.set(ol.layer.LayerProperty.Z_INDEX, zindex);
-};
-
-goog.provide('ol.source.State');
-
-
-/**
- * State of the source, one of 'undefined', 'loading', 'ready' or 'error'.
- * @enum {string}
- */
-ol.source.State = {
-  UNDEFINED: 'undefined',
-  LOADING: 'loading',
-  READY: 'ready',
-  ERROR: 'error'
-};
-
-
-goog.provide('ol.layer.Group');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.Collection');
-goog.require('ol.Object');
-goog.require('ol.ObjectEventType');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.layer.Base');
-goog.require('ol.obj');
-goog.require('ol.source.State');
-
-
-/**
- * @classdesc
- * A {@link ol.Collection} of layers that are handled together.
- *
- * A generic `change` event is triggered when the group/Collection changes.
- *
- * @constructor
- * @extends {ol.layer.Base}
- * @param {olx.layer.GroupOptions=} opt_options Layer options.
- * @api stable
- */
-ol.layer.Group = function(opt_options) {
-
-  var options = opt_options || {};
-  var baseOptions = /** @type {olx.layer.GroupOptions} */
-      (ol.obj.assign({}, options));
-  delete baseOptions.layers;
-
-  var layers = options.layers;
-
-  ol.layer.Base.call(this, baseOptions);
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.layersListenerKeys_ = [];
-
-  /**
-   * @private
-   * @type {Object.<string, Array.<ol.EventsKey>>}
-   */
-  this.listenerKeys_ = {};
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.Group.Property.LAYERS),
-      this.handleLayersChanged_, this);
-
-  if (layers) {
-    if (Array.isArray(layers)) {
-      layers = new ol.Collection(layers.slice());
-    } else {
-      ol.asserts.assert(layers instanceof ol.Collection,
-          43); // Expected `layers` to be an array or an `ol.Collection`
-      layers = layers;
-    }
-  } else {
-    layers = new ol.Collection();
-  }
-
-  this.setLayers(layers);
-
-};
-ol.inherits(ol.layer.Group, ol.layer.Base);
-
-
-/**
- * @private
- */
-ol.layer.Group.prototype.handleLayerChange_ = function() {
-  if (this.getVisible()) {
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.events.Event} event Event.
- * @private
- */
-ol.layer.Group.prototype.handleLayersChanged_ = function(event) {
-  this.layersListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.layersListenerKeys_.length = 0;
-
-  var layers = this.getLayers();
-  this.layersListenerKeys_.push(
-      ol.events.listen(layers, ol.Collection.EventType.ADD,
-          this.handleLayersAdd_, this),
-      ol.events.listen(layers, ol.Collection.EventType.REMOVE,
-          this.handleLayersRemove_, this));
-
-  for (var id in this.listenerKeys_) {
-    this.listenerKeys_[id].forEach(ol.events.unlistenByKey);
-  }
-  ol.obj.clear(this.listenerKeys_);
-
-  var layersArray = layers.getArray();
-  var i, ii, layer;
-  for (i = 0, ii = layersArray.length; i < ii; i++) {
-    layer = layersArray[i];
-    this.listenerKeys_[ol.getUid(layer).toString()] = [
-      ol.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE,
-          this.handleLayerChange_, this),
-      ol.events.listen(layer, ol.events.EventType.CHANGE,
-          this.handleLayerChange_, this)
-    ];
-  }
-
-  this.changed();
-};
-
-
-/**
- * @param {ol.Collection.Event} collectionEvent Collection event.
- * @private
- */
-ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) {
-  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
-  var key = ol.getUid(layer).toString();
-  ol.DEBUG && console.assert(!(key in this.listenerKeys_),
-      'listeners already registered');
-  this.listenerKeys_[key] = [
-    ol.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE,
-        this.handleLayerChange_, this),
-    ol.events.listen(layer, ol.events.EventType.CHANGE,
-        this.handleLayerChange_, this)
-  ];
-  this.changed();
-};
-
-
-/**
- * @param {ol.Collection.Event} collectionEvent Collection event.
- * @private
- */
-ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) {
-  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
-  var key = ol.getUid(layer).toString();
-  ol.DEBUG && console.assert(key in this.listenerKeys_, 'no listeners to unregister');
-  this.listenerKeys_[key].forEach(ol.events.unlistenByKey);
-  delete this.listenerKeys_[key];
-  this.changed();
-};
-
-
-/**
- * Returns the {@link ol.Collection collection} of {@link ol.layer.Layer layers}
- * in this group.
- * @return {!ol.Collection.<ol.layer.Base>} Collection of
- *   {@link ol.layer.Base layers} that are part of this group.
- * @observable
- * @api stable
- */
-ol.layer.Group.prototype.getLayers = function() {
-  return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get(
-      ol.layer.Group.Property.LAYERS));
-};
-
-
-/**
- * Set the {@link ol.Collection collection} of {@link ol.layer.Layer layers}
- * in this group.
- * @param {!ol.Collection.<ol.layer.Base>} layers Collection of
- *   {@link ol.layer.Base layers} that are part of this group.
- * @observable
- * @api stable
- */
-ol.layer.Group.prototype.setLayers = function(layers) {
-  this.set(ol.layer.Group.Property.LAYERS, layers);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.layer.Group.prototype.getLayersArray = function(opt_array) {
-  var array = opt_array !== undefined ? opt_array : [];
-  this.getLayers().forEach(function(layer) {
-    layer.getLayersArray(array);
-  });
-  return array;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) {
-  var states = opt_states !== undefined ? opt_states : [];
-
-  var pos = states.length;
-
-  this.getLayers().forEach(function(layer) {
-    layer.getLayerStatesArray(states);
-  });
-
-  var ownLayerState = this.getLayerState();
-  var i, ii, layerState;
-  for (i = pos, ii = states.length; i < ii; i++) {
-    layerState = states[i];
-    layerState.opacity *= ownLayerState.opacity;
-    layerState.visible = layerState.visible && ownLayerState.visible;
-    layerState.maxResolution = Math.min(
-        layerState.maxResolution, ownLayerState.maxResolution);
-    layerState.minResolution = Math.max(
-        layerState.minResolution, ownLayerState.minResolution);
-    if (ownLayerState.extent !== undefined) {
-      if (layerState.extent !== undefined) {
-        layerState.extent = ol.extent.getIntersection(
-            layerState.extent, ownLayerState.extent);
-      } else {
-        layerState.extent = ownLayerState.extent;
-      }
-    }
-  }
-
-  return states;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.layer.Group.prototype.getSourceState = function() {
-  return ol.source.State.READY;
-};
-
-/**
- * @enum {string}
- */
-ol.layer.Group.Property = {
-  LAYERS: 'layers'
-};
-
-goog.provide('ol.proj.EPSG3857');
-
-goog.require('ol');
-goog.require('ol.math');
-goog.require('ol.proj');
-goog.require('ol.proj.Projection');
-goog.require('ol.proj.Units');
-
-
-/**
- * @classdesc
- * Projection object for web/spherical Mercator (EPSG:3857).
- *
- * @constructor
- * @extends {ol.proj.Projection}
- * @param {string} code Code.
- * @private
- */
-ol.proj.EPSG3857_ = function(code) {
-  ol.proj.Projection.call(this, {
-    code: code,
-    units: ol.proj.Units.METERS,
-    extent: ol.proj.EPSG3857.EXTENT,
-    global: true,
-    worldExtent: ol.proj.EPSG3857.WORLD_EXTENT
-  });
-};
-ol.inherits(ol.proj.EPSG3857_, ol.proj.Projection);
-
-
-/**
- * @inheritDoc
- */
-ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) {
-  return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS);
-};
-
-
-/**
- * @const
- * @type {number}
- */
-ol.proj.EPSG3857.RADIUS = 6378137;
-
-
-/**
- * @const
- * @type {number}
- */
-ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS;
-
-
-/**
- * @const
- * @type {ol.Extent}
- */
-ol.proj.EPSG3857.EXTENT = [
-  -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE,
-  ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE
-];
-
-
-/**
- * @const
- * @type {ol.Extent}
- */
-ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85];
-
-
-/**
- * Lists several projection codes with the same meaning as EPSG:3857.
- *
- * @type {Array.<string>}
- */
-ol.proj.EPSG3857.CODES = [
-  'EPSG:3857',
-  'EPSG:102100',
-  'EPSG:102113',
-  'EPSG:900913',
-  'urn:ogc:def:crs:EPSG:6.18:3:3857',
-  'urn:ogc:def:crs:EPSG::3857',
-  'http://www.opengis.net/gml/srs/epsg.xml#3857'
-];
-
-
-/**
- * Projections equal to EPSG:3857.
- *
- * @const
- * @type {Array.<ol.proj.Projection>}
- */
-ol.proj.EPSG3857.PROJECTIONS = ol.proj.EPSG3857.CODES.map(function(code) {
-  return new ol.proj.EPSG3857_(code);
-});
-
-
-/**
- * Transformation from EPSG:4326 to EPSG:3857.
- *
- * @param {Array.<number>} input Input array of coordinate values.
- * @param {Array.<number>=} opt_output Output array of coordinate values.
- * @param {number=} opt_dimension Dimension (default is `2`).
- * @return {Array.<number>} Output array of coordinate values.
- */
-ol.proj.EPSG3857.fromEPSG4326 = function(input, opt_output, opt_dimension) {
-  var length = input.length,
-      dimension = opt_dimension > 1 ? opt_dimension : 2,
-      output = opt_output;
-  if (output === undefined) {
-    if (dimension > 2) {
-      // preserve values beyond second dimension
-      output = input.slice();
-    } else {
-      output = new Array(length);
-    }
-  }
-  ol.DEBUG && console.assert(output.length % dimension === 0,
-      'modulus of output.length with dimension should be 0');
-  var halfSize = ol.proj.EPSG3857.HALF_SIZE;
-  for (var i = 0; i < length; i += dimension) {
-    output[i] = halfSize * input[i] / 180;
-    var y = ol.proj.EPSG3857.RADIUS *
-        Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360));
-    if (y > halfSize) {
-      y = halfSize;
-    } else if (y < -halfSize) {
-      y = -halfSize;
-    }
-    output[i + 1] = y;
-  }
-  return output;
-};
-
-
-/**
- * Transformation from EPSG:3857 to EPSG:4326.
- *
- * @param {Array.<number>} input Input array of coordinate values.
- * @param {Array.<number>=} opt_output Output array of coordinate values.
- * @param {number=} opt_dimension Dimension (default is `2`).
- * @return {Array.<number>} Output array of coordinate values.
- */
-ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) {
-  var length = input.length,
-      dimension = opt_dimension > 1 ? opt_dimension : 2,
-      output = opt_output;
-  if (output === undefined) {
-    if (dimension > 2) {
-      // preserve values beyond second dimension
-      output = input.slice();
-    } else {
-      output = new Array(length);
-    }
-  }
-  ol.DEBUG && console.assert(output.length % dimension === 0,
-      'modulus of output.length with dimension should be 0');
-  for (var i = 0; i < length; i += dimension) {
-    output[i] = 180 * input[i] / ol.proj.EPSG3857.HALF_SIZE;
-    output[i + 1] = 360 * Math.atan(
-        Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90;
-  }
-  return output;
-};
-
-goog.provide('ol.sphere.WGS84');
-
-goog.require('ol.Sphere');
-
-
-/**
- * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid.
- * @const
- * @type {ol.Sphere}
- */
-ol.sphere.WGS84 = new ol.Sphere(6378137);
-
-goog.provide('ol.proj.EPSG4326');
-
-goog.require('ol');
-goog.require('ol.proj');
-goog.require('ol.proj.Projection');
-goog.require('ol.proj.Units');
-goog.require('ol.sphere.WGS84');
-
-
-/**
- * @classdesc
- * Projection object for WGS84 geographic coordinates (EPSG:4326).
- *
- * Note that OpenLayers does not strictly comply with the EPSG definition.
- * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x).
- * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates.
- *
- * @constructor
- * @extends {ol.proj.Projection}
- * @param {string} code Code.
- * @param {string=} opt_axisOrientation Axis orientation.
- * @private
- */
-ol.proj.EPSG4326_ = function(code, opt_axisOrientation) {
-  ol.proj.Projection.call(this, {
-    code: code,
-    units: ol.proj.Units.DEGREES,
-    extent: ol.proj.EPSG4326.EXTENT,
-    axisOrientation: opt_axisOrientation,
-    global: true,
-    metersPerUnit: ol.proj.EPSG4326.METERS_PER_UNIT,
-    worldExtent: ol.proj.EPSG4326.EXTENT
-  });
-};
-ol.inherits(ol.proj.EPSG4326_, ol.proj.Projection);
-
-
-/**
- * @inheritDoc
- */
-ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) {
-  return resolution;
-};
-
-
-/**
- * Extent of the EPSG:4326 projection which is the whole world.
- *
- * @const
- * @type {ol.Extent}
- */
-ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90];
-
-
-/**
- * @const
- * @type {number}
- */
-ol.proj.EPSG4326.METERS_PER_UNIT = Math.PI * ol.sphere.WGS84.radius / 180;
-
-
-/**
- * Projections equal to EPSG:4326.
- *
- * @const
- * @type {Array.<ol.proj.Projection>}
- */
-ol.proj.EPSG4326.PROJECTIONS = [
-  new ol.proj.EPSG4326_('CRS:84'),
-  new ol.proj.EPSG4326_('EPSG:4326', 'neu'),
-  new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG::4326', 'neu'),
-  new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'),
-  new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:1.3:CRS84'),
-  new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:2:84'),
-  new ol.proj.EPSG4326_('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'),
-  new ol.proj.EPSG4326_('urn:x-ogc:def:crs:EPSG:4326', 'neu')
-];
-
-goog.provide('ol.proj.common');
-
-goog.require('ol.proj');
-goog.require('ol.proj.EPSG3857');
-goog.require('ol.proj.EPSG4326');
-
-
-/**
- * FIXME empty description for jsdoc
- * @api
- */
-ol.proj.common.add = function() {
-  // Add transformations that don't alter coordinates to convert within set of
-  // projections with equal meaning.
-  ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS);
-  ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS);
-  // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like
-  // coordinates and back.
-  ol.proj.addEquivalentTransforms(
-      ol.proj.EPSG4326.PROJECTIONS,
-      ol.proj.EPSG3857.PROJECTIONS,
-      ol.proj.EPSG3857.fromEPSG4326,
-      ol.proj.EPSG3857.toEPSG4326);
-};
-
-goog.provide('ol.renderer.Type');
-
-
-/**
- * Available renderers: `'canvas'` or `'webgl'`.
- * @enum {string}
- */
-ol.renderer.Type = {
-  CANVAS: 'canvas',
-  WEBGL: 'webgl'
-};
-
-goog.provide('ol.render.Event');
-
-goog.require('ol');
-goog.require('ol.events.Event');
-
-
-/**
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.render.Event}
- * @param {ol.render.Event.Type} type Type.
- * @param {ol.render.VectorContext=} opt_vectorContext Vector context.
- * @param {olx.FrameState=} opt_frameState Frame state.
- * @param {?CanvasRenderingContext2D=} opt_context Context.
- * @param {?ol.webgl.Context=} opt_glContext WebGL Context.
- */
-ol.render.Event = function(
-    type, opt_vectorContext, opt_frameState, opt_context,
-    opt_glContext) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * For canvas, this is an instance of {@link ol.render.canvas.Immediate}.
-   * @type {ol.render.VectorContext|undefined}
-   * @api
-   */
-  this.vectorContext = opt_vectorContext;
-
-  /**
-   * An object representing the current render frame state.
-   * @type {olx.FrameState|undefined}
-   * @api
-   */
-  this.frameState = opt_frameState;
-
-  /**
-   * Canvas context. Only available when a Canvas renderer is used, null
-   * otherwise.
-   * @type {CanvasRenderingContext2D|null|undefined}
-   * @api
-   */
-  this.context = opt_context;
-
-  /**
-   * WebGL context. Only available when a WebGL renderer is used, null
-   * otherwise.
-   * @type {ol.webgl.Context|null|undefined}
-   * @api
-   */
-  this.glContext = opt_glContext;
-
-};
-ol.inherits(ol.render.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.render.Event.Type = {
-  /**
-   * @event ol.render.Event#postcompose
-   * @api
-   */
-  POSTCOMPOSE: 'postcompose',
-  /**
-   * @event ol.render.Event#precompose
-   * @api
-   */
-  PRECOMPOSE: 'precompose',
-  /**
-   * @event ol.render.Event#render
-   * @api
-   */
-  RENDER: 'render'
-};
-
-goog.provide('ol.layer.Layer');
-
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.layer.Base');
-goog.require('ol.layer.LayerProperty');
-goog.require('ol.obj');
-goog.require('ol.render.Event');
-goog.require('ol.source.State');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * A visual representation of raster or vector map data.
- * Layers group together those properties that pertain to how the data is to be
- * displayed, irrespective of the source of that data.
- *
- * Layers are usually added to a map with {@link ol.Map#addLayer}. Components
- * like {@link ol.interaction.Select} use unmanaged layers internally. These
- * unmanaged layers are associated with the map using
- * {@link ol.layer.Layer#setMap} instead.
- *
- * A generic `change` event is fired when the state of the source changes.
- *
- * @constructor
- * @extends {ol.layer.Base}
- * @fires ol.render.Event
- * @param {olx.layer.LayerOptions} options Layer options.
- * @api stable
- */
-ol.layer.Layer = function(options) {
-
-  var baseOptions = ol.obj.assign({}, options);
-  delete baseOptions.source;
-
-  ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions));
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.mapPrecomposeKey_ = null;
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.mapRenderKey_ = null;
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.sourceChangeKey_ = null;
-
-  if (options.map) {
-    this.setMap(options.map);
-  }
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.LayerProperty.SOURCE),
-      this.handleSourcePropertyChange_, this);
-
-  var source = options.source ? options.source : null;
-  this.setSource(source);
-};
-ol.inherits(ol.layer.Layer, ol.layer.Base);
-
-
-/**
- * Return `true` if the layer is visible, and if the passed resolution is
- * between the layer's minResolution and maxResolution. The comparison is
- * inclusive for `minResolution` and exclusive for `maxResolution`.
- * @param {ol.LayerState} layerState Layer state.
- * @param {number} resolution Resolution.
- * @return {boolean} The layer is visible at the given resolution.
- */
-ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
-  return layerState.visible && resolution >= layerState.minResolution &&
-      resolution < layerState.maxResolution;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
-  var array = opt_array ? opt_array : [];
-  array.push(this);
-  return array;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
-  var states = opt_states ? opt_states : [];
-  states.push(this.getLayerState());
-  return states;
-};
-
-
-/**
- * Get the layer source.
- * @return {ol.source.Source} The layer source (or `null` if not yet set).
- * @observable
- * @api stable
- */
-ol.layer.Layer.prototype.getSource = function() {
-  var source = this.get(ol.layer.LayerProperty.SOURCE);
-  return /** @type {ol.source.Source} */ (source) || null;
-};
-
-
-/**
-  * @inheritDoc
-  */
-ol.layer.Layer.prototype.getSourceState = function() {
-  var source = this.getSource();
-  return !source ? ol.source.State.UNDEFINED : source.getState();
-};
-
-
-/**
- * @private
- */
-ol.layer.Layer.prototype.handleSourceChange_ = function() {
-  this.changed();
-};
-
-
-/**
- * @private
- */
-ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() {
-  if (this.sourceChangeKey_) {
-    ol.events.unlistenByKey(this.sourceChangeKey_);
-    this.sourceChangeKey_ = null;
-  }
-  var source = this.getSource();
-  if (source) {
-    this.sourceChangeKey_ = ol.events.listen(source,
-        ol.events.EventType.CHANGE, this.handleSourceChange_, this);
-  }
-  this.changed();
-};
-
-
-/**
- * Sets the layer to be rendered on top of other layers on a map. The map will
- * not manage this layer in its layers collection, and the callback in
- * {@link ol.Map#forEachLayerAtPixel} will receive `null` as layer. This
- * is useful for temporary layers. To remove an unmanaged layer from the map,
- * use `#setMap(null)`.
- *
- * To add the layer to a map and have it managed by the map, use
- * {@link ol.Map#addLayer} instead.
- * @param {ol.Map} map Map.
- * @api
- */
-ol.layer.Layer.prototype.setMap = function(map) {
-  if (this.mapPrecomposeKey_) {
-    ol.events.unlistenByKey(this.mapPrecomposeKey_);
-    this.mapPrecomposeKey_ = null;
-  }
-  if (!map) {
-    this.changed();
-  }
-  if (this.mapRenderKey_) {
-    ol.events.unlistenByKey(this.mapRenderKey_);
-    this.mapRenderKey_ = null;
-  }
-  if (map) {
-    this.mapPrecomposeKey_ = ol.events.listen(
-        map, ol.render.Event.Type.PRECOMPOSE, function(evt) {
-          var layerState = this.getLayerState();
-          layerState.managed = false;
-          layerState.zIndex = Infinity;
-          evt.frameState.layerStatesArray.push(layerState);
-          evt.frameState.layerStates[ol.getUid(this)] = layerState;
-        }, this);
-    this.mapRenderKey_ = ol.events.listen(
-        this, ol.events.EventType.CHANGE, map.render, map);
-    this.changed();
-  }
-};
-
-
-/**
- * Set the layer source.
- * @param {ol.source.Source} source The layer source.
- * @observable
- * @api stable
- */
-ol.layer.Layer.prototype.setSource = function(source) {
-  this.set(ol.layer.LayerProperty.SOURCE, source);
-};
-
-goog.provide('ol.style.IconImageCache');
-
-goog.require('ol');
-goog.require('ol.color');
-
-
-/**
- * @constructor
- */
-ol.style.IconImageCache = function() {
-
-  /**
-   * @type {Object.<string, ol.style.IconImage>}
-   * @private
-   */
-  this.cache_ = {};
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.cacheSize_ = 0;
-
-  /**
-   * @const
-   * @type {number}
-   * @private
-   */
-  this.maxCacheSize_ = 32;
-};
-
-
-/**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Color} color Color.
- * @return {string} Cache key.
- */
-ol.style.IconImageCache.getKey = function(src, crossOrigin, color) {
-  ol.DEBUG && console.assert(crossOrigin !== undefined,
-      'argument crossOrigin must be defined');
-  var colorString = color ? ol.color.asString(color) : 'null';
-  return crossOrigin + ':' + src + ':' + colorString;
-};
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.style.IconImageCache.prototype.clear = function() {
-  this.cache_ = {};
-  this.cacheSize_ = 0;
-};
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.style.IconImageCache.prototype.expire = function() {
-  if (this.cacheSize_ > this.maxCacheSize_) {
-    var i = 0;
-    var key, iconImage;
-    for (key in this.cache_) {
-      iconImage = this.cache_[key];
-      if ((i++ & 3) === 0 && !iconImage.hasListener()) {
-        delete this.cache_[key];
-        --this.cacheSize_;
-      }
-    }
-  }
-};
-
-
-/**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Color} color Color.
- * @return {ol.style.IconImage} Icon image.
- */
-ol.style.IconImageCache.prototype.get = function(src, crossOrigin, color) {
-  var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
-  return key in this.cache_ ? this.cache_[key] : null;
-};
-
-
-/**
- * @param {string} src Src.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Color} color Color.
- * @param {ol.style.IconImage} iconImage Icon image.
- */
-ol.style.IconImageCache.prototype.set = function(src, crossOrigin, color,
-                                                 iconImage) {
-  var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
-  this.cache_[key] = iconImage;
-  ++this.cacheSize_;
-};
-
-goog.provide('ol.style');
-
-goog.require('ol.style.IconImageCache');
-
-ol.style.iconImageCache = new ol.style.IconImageCache();
-
-goog.provide('ol.transform');
-
-goog.require('ol.asserts');
-
-
-/**
- * Collection of affine 2d transformation functions. The functions work on an
- * array of 6 elements. The element order is compatible with the [SVGMatrix
- * interface](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix) and is
- * a subset (elements a to f) of a 3x3 martrix:
- * ```
- * [ a c e ]
- * [ b d f ]
- * [ 0 0 1 ]
- * ```
- */
-
-
-/**
- * @private
- * @type {ol.Transform}
- */
-ol.transform.tmp_ = new Array(6);
-
-
-/**
- * Create an identity transform.
- * @return {!ol.Transform} Identity transform.
- */
-ol.transform.create = function() {
-  return [1, 0, 0, 1, 0, 0];
-};
-
-
-/**
- * Resets the given transform to an identity transform.
- * @param {!ol.Transform} transform Transform.
- * @return {!ol.Transform} Transform.
- */
-ol.transform.reset = function(transform) {
-  return ol.transform.set(transform, 1, 0, 0, 1, 0, 0);
-};
-
-
-/**
- * Multiply the underlying matrices of two transforms and return the result in
- * the first transform.
- * @param {!ol.Transform} transform1 Transform parameters of matrix 1.
- * @param {!ol.Transform} transform2 Transform parameters of matrix 2.
- * @return {!ol.Transform} transform1 multiplied with transform2.
- */
-ol.transform.multiply = function(transform1, transform2) {
-  var a1 = transform1[0];
-  var b1 = transform1[1];
-  var c1 = transform1[2];
-  var d1 = transform1[3];
-  var e1 = transform1[4];
-  var f1 = transform1[5];
-  var a2 = transform2[0];
-  var b2 = transform2[1];
-  var c2 = transform2[2];
-  var d2 = transform2[3];
-  var e2 = transform2[4];
-  var f2 = transform2[5];
-
-  transform1[0] = a1 * a2 + c1 * b2;
-  transform1[1] = b1 * a2 + d1 * b2;
-  transform1[2] = a1 * c2 + c1 * d2;
-  transform1[3] = b1 * c2 + d1 * d2;
-  transform1[4] = a1 * e2 + c1 * f2 + e1;
-  transform1[5] = b1 * e2 + d1 * f2 + f1;
-
-  return transform1;
-};
-
-/**
- * Set the transform components a-f on a given transform.
- * @param {!ol.Transform} transform Transform.
- * @param {number} a The a component of the transform.
- * @param {number} b The b component of the transform.
- * @param {number} c The c component of the transform.
- * @param {number} d The d component of the transform.
- * @param {number} e The e component of the transform.
- * @param {number} f The f component of the transform.
- * @return {!ol.Transform} Matrix with transform applied.
- */
-ol.transform.set = function(transform, a, b, c, d, e, f) {
-  transform[0] = a;
-  transform[1] = b;
-  transform[2] = c;
-  transform[3] = d;
-  transform[4] = e;
-  transform[5] = f;
-  return transform;
-};
-
-
-/**
- * Set transform on one matrix from another matrix.
- * @param {!ol.Transform} transform1 Matrix to set transform to.
- * @param {!ol.Transform} transform2 Matrix to set transform from.
- * @return {!ol.Transform} transform1 with transform from transform2 applied.
- */
-ol.transform.setFromArray = function(transform1, transform2) {
-  transform1[0] = transform2[0];
-  transform1[1] = transform2[1];
-  transform1[2] = transform2[2];
-  transform1[3] = transform2[3];
-  transform1[4] = transform2[4];
-  transform1[5] = transform2[5];
-  return transform1;
-};
-
-
-/**
- * Transforms the given coordinate with the given transform returning the
- * resulting, transformed coordinate. The coordinate will be modified in-place.
- *
- * @param {ol.Transform} transform The transformation.
- * @param {ol.Coordinate|ol.Pixel} coordinate The coordinate to transform.
- * @return {ol.Coordinate|ol.Pixel} return coordinate so that operations can be
- *     chained together.
- */
-ol.transform.apply = function(transform, coordinate) {
-  var x = coordinate[0], y = coordinate[1];
-  coordinate[0] = transform[0] * x + transform[2] * y + transform[4];
-  coordinate[1] = transform[1] * x + transform[3] * y + transform[5];
-  return coordinate;
-};
-
-
-/**
- * Applies rotation to the given transform.
- * @param {!ol.Transform} transform Transform.
- * @param {number} angle Angle in radians.
- * @return {!ol.Transform} The rotated transform.
- */
-ol.transform.rotate = function(transform, angle) {
-  var cos = Math.cos(angle);
-  var sin = Math.sin(angle);
-  return ol.transform.multiply(transform,
-      ol.transform.set(ol.transform.tmp_, cos, sin, -sin, cos, 0, 0));
-};
-
-
-/**
- * Applies scale to a given transform.
- * @param {!ol.Transform} transform Transform.
- * @param {number} x Scale factor x.
- * @param {number} y Scale factor y.
- * @return {!ol.Transform} The scaled transform.
- */
-ol.transform.scale = function(transform, x, y) {
-  return ol.transform.multiply(transform,
-      ol.transform.set(ol.transform.tmp_, x, 0, 0, y, 0, 0));
-};
-
-
-/**
- * Applies translation to the given transform.
- * @param {!ol.Transform} transform Transform.
- * @param {number} dx Translation x.
- * @param {number} dy Translation y.
- * @return {!ol.Transform} The translated transform.
- */
-ol.transform.translate = function(transform, dx, dy) {
-  return ol.transform.multiply(transform,
-      ol.transform.set(ol.transform.tmp_, 1, 0, 0, 1, dx, dy));
-};
-
-
-/**
- * Creates a composite transform given an initial translation, scale, rotation, and
- * final translation (in that order only, not commutative).
- * @param {!ol.Transform} transform The transform (will be modified in place).
- * @param {number} dx1 Initial translation x.
- * @param {number} dy1 Initial translation y.
- * @param {number} sx Scale factor x.
- * @param {number} sy Scale factor y.
- * @param {number} angle Rotation (in counter-clockwise radians).
- * @param {number} dx2 Final translation x.
- * @param {number} dy2 Final translation y.
- * @return {!ol.Transform} The composite transform.
- */
-ol.transform.compose = function(transform, dx1, dy1, sx, sy, angle, dx2, dy2) {
-  var sin = Math.sin(angle);
-  var cos = Math.cos(angle);
-  transform[0] = sx * cos;
-  transform[1] = sy * sin;
-  transform[2] = -sx * sin;
-  transform[3] = sy * cos;
-  transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1;
-  transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1;
-  return transform;
-};
-
-
-/**
- * Invert the given transform.
- * @param {!ol.Transform} transform Transform.
- * @return {!ol.Transform} Inverse of the transform.
- */
-ol.transform.invert = function(transform) {
-  var det = ol.transform.determinant(transform);
-  ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted
-
-  var a = transform[0];
-  var b = transform[1];
-  var c = transform[2];
-  var d = transform[3];
-  var e = transform[4];
-  var f = transform[5];
-
-  transform[0] = d / det;
-  transform[1] = -b / det;
-  transform[2] = -c / det;
-  transform[3] = a / det;
-  transform[4] = (c * f - d * e) / det;
-  transform[5] = -(a * f - b * e) / det;
-
-  return transform;
-};
-
-
-/**
- * Returns the determinant of the given matrix.
- * @param {!ol.Transform} mat Matrix.
- * @return {number} Determinant.
- */
-ol.transform.determinant = function(mat) {
-  return mat[0] * mat[3] - mat[1] * mat[2];
-};
-
-goog.provide('ol.renderer.Map');
-
-goog.require('ol');
-goog.require('ol.Disposable');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.functions');
-goog.require('ol.layer.Layer');
-goog.require('ol.style');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.Disposable}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
- * @struct
- */
-ol.renderer.Map = function(container, map) {
-
-  ol.Disposable.call(this);
-
-
-  /**
-   * @private
-   * @type {ol.Map}
-   */
-  this.map_ = map;
-
-  /**
-   * @private
-   * @type {Object.<string, ol.renderer.Layer>}
-   */
-  this.layerRenderers_ = {};
-
-  /**
-   * @private
-   * @type {Object.<string, ol.EventsKey>}
-   */
-  this.layerRendererListeners_ = {};
-
-};
-ol.inherits(ol.renderer.Map, ol.Disposable);
-
-
-/**
- * @param {olx.FrameState} frameState FrameState.
- * @protected
- */
-ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) {
-  var viewState = frameState.viewState;
-  var coordinateToPixelTransform = frameState.coordinateToPixelTransform;
-  var pixelToCoordinateTransform = frameState.pixelToCoordinateTransform;
-  ol.DEBUG && console.assert(coordinateToPixelTransform,
-      'frameState has a coordinateToPixelTransform');
-
-  ol.transform.compose(coordinateToPixelTransform,
-      frameState.size[0] / 2, frameState.size[1] / 2,
-      1 / viewState.resolution, -1 / viewState.resolution,
-      -viewState.rotation,
-      -viewState.center[0], -viewState.center[1]);
-
-  ol.transform.invert(
-      ol.transform.setFromArray(pixelToCoordinateTransform, coordinateToPixelTransform));
-};
-
-
-/**
- * @abstract
- * @param {ol.layer.Layer} layer Layer.
- * @protected
- * @return {ol.renderer.Layer} layerRenderer Layer renderer.
- */
-ol.renderer.Map.prototype.createLayerRenderer = function(layer) {};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.Map.prototype.disposeInternal = function() {
-  for (var id in this.layerRenderers_) {
-    this.layerRenderers_[id].dispose();
-  }
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-ol.renderer.Map.expireIconCache_ = function(map, frameState) {
-  var cache = ol.style.iconImageCache;
-  cache.expire();
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, (ol.Feature|ol.render.Feature),
- *     ol.layer.Layer): T} callback Feature callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
- *     function, only layers which are visible and for which this function
- *     returns `true` will be tested for features.  By default, all visible
- *     layers will be tested.
- * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
- */
-ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg,
-        layerFilter, thisArg2) {
-  var result;
-  var viewState = frameState.viewState;
-  var viewResolution = viewState.resolution;
-
-  /**
-   * @param {ol.Feature|ol.render.Feature} feature Feature.
-   * @param {ol.layer.Layer} layer Layer.
-   * @return {?} Callback result.
-   */
-  function forEachFeatureAtCoordinate(feature, layer) {
-    var key = ol.getUid(feature).toString();
-    var managed = frameState.layerStates[ol.getUid(layer)].managed;
-    if (!(key in frameState.skippedFeatureUids && !managed)) {
-      return callback.call(thisArg, feature, managed ? layer : null);
-    }
-  }
-
-  var projection = viewState.projection;
-
-  var translatedCoordinate = coordinate;
-  if (projection.canWrapX()) {
-    var projectionExtent = projection.getExtent();
-    var worldWidth = ol.extent.getWidth(projectionExtent);
-    var x = coordinate[0];
-    if (x < projectionExtent[0] || x > projectionExtent[2]) {
-      var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
-      translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
-    }
-  }
-
-  var layerStates = frameState.layerStatesArray;
-  var numLayers = layerStates.length;
-  var i;
-  for (i = numLayers - 1; i >= 0; --i) {
-    var layerState = layerStates[i];
-    var layer = layerState.layer;
-    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
-        layerFilter.call(thisArg2, layer)) {
-      var layerRenderer = this.getLayerRenderer(layer);
-      if (layer.getSource()) {
-        result = layerRenderer.forEachFeatureAtCoordinate(
-            layer.getSource().getWrapX() ? translatedCoordinate : coordinate,
-            frameState, forEachFeatureAtCoordinate, thisArg);
-      }
-      if (result) {
-        return result;
-      }
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
- *     function, only layers which are visible and for which this function
- *     returns `true` will be tested for features.  By default, all visible
- *     layers will be tested.
- * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
- */
-ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
-        layerFilter, thisArg2) {
-  var result;
-  var viewState = frameState.viewState;
-  var viewResolution = viewState.resolution;
-
-  var layerStates = frameState.layerStatesArray;
-  var numLayers = layerStates.length;
-  var i;
-  for (i = numLayers - 1; i >= 0; --i) {
-    var layerState = layerStates[i];
-    var layer = layerState.layer;
-    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
-        layerFilter.call(thisArg2, layer)) {
-      var layerRenderer = this.getLayerRenderer(layer);
-      result = layerRenderer.forEachLayerAtPixel(
-          pixel, frameState, callback, thisArg);
-      if (result) {
-        return result;
-      }
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
- *     function, only layers which are visible and for which this function
- *     returns `true` will be tested for features.  By default, all visible
- *     layers will be tested.
- * @param {U} thisArg Value to use as `this` when executing `layerFilter`.
- * @return {boolean} Is there a feature at the given coordinate?
- * @template U
- */
-ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) {
-  var hasFeature = this.forEachFeatureAtCoordinate(
-      coordinate, frameState, ol.functions.TRUE, this, layerFilter, thisArg);
-
-  return hasFeature !== undefined;
-};
-
-
-/**
- * @param {ol.layer.Layer} layer Layer.
- * @protected
- * @return {ol.renderer.Layer} Layer renderer.
- */
-ol.renderer.Map.prototype.getLayerRenderer = function(layer) {
-  var layerKey = ol.getUid(layer).toString();
-  if (layerKey in this.layerRenderers_) {
-    return this.layerRenderers_[layerKey];
-  } else {
-    var layerRenderer = this.createLayerRenderer(layer);
-    this.layerRenderers_[layerKey] = layerRenderer;
-    this.layerRendererListeners_[layerKey] = ol.events.listen(layerRenderer,
-        ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this);
-
-    return layerRenderer;
-  }
-};
-
-
-/**
- * @param {string} layerKey Layer key.
- * @protected
- * @return {ol.renderer.Layer} Layer renderer.
- */
-ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) {
-  ol.DEBUG && console.assert(layerKey in this.layerRenderers_,
-      'given layerKey (%s) exists in layerRenderers', layerKey);
-  return this.layerRenderers_[layerKey];
-};
-
-
-/**
- * @protected
- * @return {Object.<string, ol.renderer.Layer>} Layer renderers.
- */
-ol.renderer.Map.prototype.getLayerRenderers = function() {
-  return this.layerRenderers_;
-};
-
-
-/**
- * @return {ol.Map} Map.
- */
-ol.renderer.Map.prototype.getMap = function() {
-  return this.map_;
-};
-
-
-/**
- * @abstract
- * @return {string} Type
- */
-ol.renderer.Map.prototype.getType = function() {};
-
-
-/**
- * Handle changes in a layer renderer.
- * @private
- */
-ol.renderer.Map.prototype.handleLayerRendererChange_ = function() {
-  this.map_.render();
-};
-
-
-/**
- * @param {string} layerKey Layer key.
- * @return {ol.renderer.Layer} Layer renderer.
- * @private
- */
-ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) {
-  ol.DEBUG && console.assert(layerKey in this.layerRenderers_,
-      'given layerKey (%s) exists in layerRenderers', layerKey);
-  var layerRenderer = this.layerRenderers_[layerKey];
-  delete this.layerRenderers_[layerKey];
-
-  ol.DEBUG && console.assert(layerKey in this.layerRendererListeners_,
-      'given layerKey (%s) exists in layerRendererListeners', layerKey);
-  ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]);
-  delete this.layerRendererListeners_[layerKey];
-
-  return layerRenderer;
-};
-
-
-/**
- * Render.
- * @param {?olx.FrameState} frameState Frame state.
- */
-ol.renderer.Map.prototype.renderFrame = ol.nullFunction;
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = function(map, frameState) {
-  var layerKey;
-  for (layerKey in this.layerRenderers_) {
-    if (!frameState || !(layerKey in frameState.layerStates)) {
-      this.removeLayerRendererByKey_(layerKey).dispose();
-    }
-  }
-};
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @protected
- */
-ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
-  frameState.postRenderFunctions.push(
-    /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_)
-  );
-};
-
-
-/**
- * @param {!olx.FrameState} frameState Frame state.
- * @protected
- */
-ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers = function(frameState) {
-  var layerKey;
-  for (layerKey in this.layerRenderers_) {
-    if (!(layerKey in frameState.layerStates)) {
-      frameState.postRenderFunctions.push(
-        /** @type {ol.PostRenderFunction} */ (this.removeUnusedLayerRenderers_.bind(this))
-      );
-      return;
-    }
-  }
-};
-
-
-/**
- * @param {ol.LayerState} state1 First layer state.
- * @param {ol.LayerState} state2 Second layer state.
- * @return {number} The zIndex difference.
- */
-ol.renderer.Map.sortByZIndex = function(state1, state2) {
-  return state1.zIndex - state2.zIndex;
-};
-
-goog.provide('ol.layer.Image');
-
-goog.require('ol');
-goog.require('ol.layer.Layer');
-
-
-/**
- * @classdesc
- * Server-rendered images that are available for arbitrary extents and
- * resolutions.
- * Note that any property set in the options is set as a {@link ol.Object}
- * property on the layer object; for example, setting `title: 'My Title'` in the
- * options means that `title` is observable, and has get/set accessors.
- *
- * @constructor
- * @extends {ol.layer.Layer}
- * @fires ol.render.Event
- * @param {olx.layer.ImageOptions=} opt_options Layer options.
- * @api stable
- */
-ol.layer.Image = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (options));
-};
-ol.inherits(ol.layer.Image, ol.layer.Layer);
-
-
-/**
- * Return the associated {@link ol.source.Image source} of the image layer.
- * @function
- * @return {ol.source.Image} Source.
- * @api stable
- */
-ol.layer.Image.prototype.getSource;
-
-goog.provide('ol.layer.Tile');
-
-goog.require('ol');
-goog.require('ol.layer.Layer');
-goog.require('ol.obj');
-
-
-/**
- * @classdesc
- * For layer sources that provide pre-rendered, tiled images in grids that are
- * organized by zoom levels for specific resolutions.
- * Note that any property set in the options is set as a {@link ol.Object}
- * property on the layer object; for example, setting `title: 'My Title'` in the
- * options means that `title` is observable, and has get/set accessors.
- *
- * @constructor
- * @extends {ol.layer.Layer}
- * @fires ol.render.Event
- * @param {olx.layer.TileOptions=} opt_options Tile layer options.
- * @api stable
- */
-ol.layer.Tile = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  var baseOptions = ol.obj.assign({}, options);
-
-  delete baseOptions.preload;
-  delete baseOptions.useInterimTilesOnError;
-  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (baseOptions));
-
-  this.setPreload(options.preload !== undefined ? options.preload : 0);
-  this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ?
-      options.useInterimTilesOnError : true);
-};
-ol.inherits(ol.layer.Tile, ol.layer.Layer);
-
-
-/**
- * Return the level as number to which we will preload tiles up to.
- * @return {number} The level to preload tiles up to.
- * @observable
- * @api
- */
-ol.layer.Tile.prototype.getPreload = function() {
-  return /** @type {number} */ (this.get(ol.layer.Tile.Property.PRELOAD));
-};
-
-
-/**
- * Return the associated {@link ol.source.Tile tilesource} of the layer.
- * @function
- * @return {ol.source.Tile} Source.
- * @api stable
- */
-ol.layer.Tile.prototype.getSource;
-
-
-/**
- * Set the level as number to which we will preload tiles up to.
- * @param {number} preload The level to preload tiles up to.
- * @observable
- * @api
- */
-ol.layer.Tile.prototype.setPreload = function(preload) {
-  this.set(ol.layer.Tile.Property.PRELOAD, preload);
-};
-
-
-/**
- * Whether we use interim tiles on error.
- * @return {boolean} Use interim tiles on error.
- * @observable
- * @api
- */
-ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR));
-};
-
-
-/**
- * Set whether we use interim tiles on error.
- * @param {boolean} useInterimTilesOnError Use interim tiles on error.
- * @observable
- * @api
- */
-ol.layer.Tile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
-  this.set(
-      ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
-};
-
-
-/**
- * @enum {string}
- */
-ol.layer.Tile.Property = {
-  PRELOAD: 'preload',
-  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
-};
-
-goog.provide('ol.ImageBase');
-
-goog.require('ol');
-goog.require('ol.events.EventTarget');
-goog.require('ol.events.EventType');
-
-
-/**
- * @constructor
- * @extends {ol.events.EventTarget}
- * @param {ol.Extent} extent Extent.
- * @param {number|undefined} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Image.State} state State.
- * @param {Array.<ol.Attribution>} attributions Attributions.
- */
-ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) {
-
-  ol.events.EventTarget.call(this);
-
-  /**
-   * @private
-   * @type {Array.<ol.Attribution>}
-   */
-  this.attributions_ = attributions;
-
-  /**
-   * @protected
-   * @type {ol.Extent}
-   */
-  this.extent = extent;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = pixelRatio;
-
-  /**
-   * @protected
-   * @type {number|undefined}
-   */
-  this.resolution = resolution;
-
-  /**
-   * @protected
-   * @type {ol.Image.State}
-   */
-  this.state = state;
-
-};
-ol.inherits(ol.ImageBase, ol.events.EventTarget);
-
-
-/**
- * @protected
- */
-ol.ImageBase.prototype.changed = function() {
-  this.dispatchEvent(ol.events.EventType.CHANGE);
-};
-
-
-/**
- * @return {Array.<ol.Attribution>} Attributions.
- */
-ol.ImageBase.prototype.getAttributions = function() {
-  return this.attributions_;
-};
-
-
-/**
- * @return {ol.Extent} Extent.
- */
-ol.ImageBase.prototype.getExtent = function() {
-  return this.extent;
-};
-
-
-/**
- * @abstract
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
- */
-ol.ImageBase.prototype.getImage = function(opt_context) {};
-
-
-/**
- * @return {number} PixelRatio.
- */
-ol.ImageBase.prototype.getPixelRatio = function() {
-  return this.pixelRatio_;
-};
-
-
-/**
- * @return {number} Resolution.
- */
-ol.ImageBase.prototype.getResolution = function() {
-  ol.DEBUG && console.assert(this.resolution !== undefined, 'resolution not yet set');
-  return /** @type {number} */ (this.resolution);
-};
-
-
-/**
- * @return {ol.Image.State} State.
- */
-ol.ImageBase.prototype.getState = function() {
-  return this.state;
-};
-
-
-/**
- * Load not yet loaded URI.
- * @abstract
- */
-ol.ImageBase.prototype.load = function() {};
-
-goog.provide('ol.Image');
-
-goog.require('ol');
-goog.require('ol.ImageBase');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.obj');
-
-
-/**
- * @constructor
- * @extends {ol.ImageBase}
- * @param {ol.Extent} extent Extent.
- * @param {number|undefined} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {Array.<ol.Attribution>} attributions Attributions.
- * @param {string} src Image source URI.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
- */
-ol.Image = function(extent, resolution, pixelRatio, attributions, src,
-    crossOrigin, imageLoadFunction) {
-
-  ol.ImageBase.call(this, extent, resolution, pixelRatio, ol.Image.State.IDLE,
-      attributions);
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.src_ = src;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement|Image|HTMLVideoElement}
-   */
-  this.image_ = new Image();
-  if (crossOrigin !== null) {
-    this.image_.crossOrigin = crossOrigin;
-  }
-
-  /**
-   * @private
-   * @type {Object.<number, (HTMLCanvasElement|Image|HTMLVideoElement)>}
-   */
-  this.imageByContext_ = {};
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.imageListenerKeys_ = null;
-
-  /**
-   * @protected
-   * @type {ol.Image.State}
-   */
-  this.state = ol.Image.State.IDLE;
-
-  /**
-   * @private
-   * @type {ol.ImageLoadFunctionType}
-   */
-  this.imageLoadFunction_ = imageLoadFunction;
-
-};
-ol.inherits(ol.Image, ol.ImageBase);
-
-
-/**
- * Get the HTML image element (may be a Canvas, Image, or Video).
- * @param {Object=} opt_context Object.
- * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
- * @api
- */
-ol.Image.prototype.getImage = function(opt_context) {
-  if (opt_context !== undefined) {
-    var image;
-    var key = ol.getUid(opt_context);
-    if (key in this.imageByContext_) {
-      return this.imageByContext_[key];
-    } else if (ol.obj.isEmpty(this.imageByContext_)) {
-      image = this.image_;
-    } else {
-      image = /** @type {Image} */ (this.image_.cloneNode(false));
-    }
-    this.imageByContext_[key] = image;
-    return image;
-  } else {
-    return this.image_;
-  }
-};
-
-
-/**
- * Tracks loading or read errors.
- *
- * @private
- */
-ol.Image.prototype.handleImageError_ = function() {
-  this.state = ol.Image.State.ERROR;
-  this.unlistenImage_();
-  this.changed();
-};
-
-
-/**
- * Tracks successful image load.
- *
- * @private
- */
-ol.Image.prototype.handleImageLoad_ = function() {
-  if (this.resolution === undefined) {
-    this.resolution = ol.extent.getHeight(this.extent) / this.image_.height;
-  }
-  this.state = ol.Image.State.LOADED;
-  this.unlistenImage_();
-  this.changed();
-};
-
-
-/**
- * Load the image or retry if loading previously failed.
- * Loading is taken care of by the tile queue, and calling this method is
- * only needed for preloading or for reloading in case of an error.
- * @api
- */
-ol.Image.prototype.load = function() {
-  if (this.state == ol.Image.State.IDLE || this.state == ol.Image.State.ERROR) {
-    this.state = ol.Image.State.LOADING;
-    this.changed();
-    ol.DEBUG && console.assert(!this.imageListenerKeys_,
-        'this.imageListenerKeys_ should be null');
-    this.imageListenerKeys_ = [
-      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
-          this.handleImageError_, this),
-      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
-          this.handleImageLoad_, this)
-    ];
-    this.imageLoadFunction_(this, this.src_);
-  }
-};
-
-
-/**
- * @param {HTMLCanvasElement|Image|HTMLVideoElement} image Image.
- */
-ol.Image.prototype.setImage = function(image) {
-  this.image_ = image;
-};
-
-
-/**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
- */
-ol.Image.prototype.unlistenImage_ = function() {
-  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
-};
-
-
-/**
- * @enum {number}
- */
-ol.Image.State = {
-  IDLE: 0,
-  LOADING: 1,
-  LOADED: 2,
-  ERROR: 3
-};
-
-goog.provide('ol.render.canvas');
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultFont = '10px sans-serif';
-
-
-/**
- * @const
- * @type {ol.Color}
- */
-ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultLineCap = 'round';
-
-
-/**
- * @const
- * @type {Array.<number>}
- */
-ol.render.canvas.defaultLineDash = [];
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultLineJoin = 'round';
-
-
-/**
- * @const
- * @type {number}
- */
-ol.render.canvas.defaultMiterLimit = 10;
-
-
-/**
- * @const
- * @type {ol.Color}
- */
-ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultTextAlign = 'center';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.canvas.defaultTextBaseline = 'middle';
-
-
-/**
- * @const
- * @type {number}
- */
-ol.render.canvas.defaultLineWidth = 1;
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} rotation Rotation.
- * @param {number} offsetX X offset.
- * @param {number} offsetY Y offset.
- */
-ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) {
-  if (rotation !== 0) {
-    context.translate(offsetX, offsetY);
-    context.rotate(rotation);
-    context.translate(-offsetX, -offsetY);
-  }
-};
-
-goog.provide('ol.style.Image');
-
-
-/**
- * @classdesc
- * A base class used for creating subclasses and not instantiated in
- * apps. Base class for {@link ol.style.Icon}, {@link ol.style.Circle} and
- * {@link ol.style.RegularShape}.
- *
- * @constructor
- * @param {ol.StyleImageOptions} options Options.
- * @api
- */
-ol.style.Image = function(options) {
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.opacity_ = options.opacity;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.rotateWithView_ = options.rotateWithView;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.rotation_ = options.rotation;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.scale_ = options.scale;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.snapToPixel_ = options.snapToPixel;
-
-};
-
-
-/**
- * Get the symbolizer opacity.
- * @return {number} Opacity.
- * @api
- */
-ol.style.Image.prototype.getOpacity = function() {
-  return this.opacity_;
-};
-
-
-/**
- * Determine whether the symbolizer rotates with the map.
- * @return {boolean} Rotate with map.
- * @api
- */
-ol.style.Image.prototype.getRotateWithView = function() {
-  return this.rotateWithView_;
-};
-
-
-/**
- * Get the symoblizer rotation.
- * @return {number} Rotation.
- * @api
- */
-ol.style.Image.prototype.getRotation = function() {
-  return this.rotation_;
-};
-
-
-/**
- * Get the symbolizer scale.
- * @return {number} Scale.
- * @api
- */
-ol.style.Image.prototype.getScale = function() {
-  return this.scale_;
-};
-
-
-/**
- * Determine whether the symbolizer should be snapped to a pixel.
- * @return {boolean} The symbolizer should snap to a pixel.
- * @api
- */
-ol.style.Image.prototype.getSnapToPixel = function() {
-  return this.snapToPixel_;
-};
-
-
-/**
- * Get the anchor point in pixels. The anchor determines the center point for the
- * symbolizer.
- * @abstract
- * @return {Array.<number>} Anchor.
- */
-ol.style.Image.prototype.getAnchor = function() {};
-
-
-/**
- * Get the image element for the symbolizer.
- * @abstract
- * @param {number} pixelRatio Pixel ratio.
- * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
- */
-ol.style.Image.prototype.getImage = function(pixelRatio) {};
-
-
-/**
- * @abstract
- * @param {number} pixelRatio Pixel ratio.
- * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
- */
-ol.style.Image.prototype.getHitDetectionImage = function(pixelRatio) {};
-
-
-/**
- * @abstract
- * @return {ol.Image.State} Image state.
- */
-ol.style.Image.prototype.getImageState = function() {};
-
-
-/**
- * @abstract
- * @return {ol.Size} Image size.
- */
-ol.style.Image.prototype.getImageSize = function() {};
-
-
-/**
- * @abstract
- * @return {ol.Size} Size of the hit-detection image.
- */
-ol.style.Image.prototype.getHitDetectionImageSize = function() {};
-
-
-/**
- * Get the origin of the symbolizer.
- * @abstract
- * @return {Array.<number>} Origin.
- */
-ol.style.Image.prototype.getOrigin = function() {};
-
-
-/**
- * Get the size of the symbolizer (in pixels).
- * @abstract
- * @return {ol.Size} Size.
- */
-ol.style.Image.prototype.getSize = function() {};
-
-
-/**
- * Set the opacity.
- *
- * @param {number} opacity Opacity.
- * @api
- */
-ol.style.Image.prototype.setOpacity = function(opacity) {
-  this.opacity_ = opacity;
-};
-
-
-/**
- * Set whether to rotate the style with the view.
- *
- * @param {boolean} rotateWithView Rotate with map.
- */
-ol.style.Image.prototype.setRotateWithView = function(rotateWithView) {
-  this.rotateWithView_ = rotateWithView;
-};
-
-
-/**
- * Set the rotation.
- *
- * @param {number} rotation Rotation.
- * @api
- */
-ol.style.Image.prototype.setRotation = function(rotation) {
-  this.rotation_ = rotation;
-};
-
-
-/**
- * Set the scale.
- *
- * @param {number} scale Scale.
- * @api
- */
-ol.style.Image.prototype.setScale = function(scale) {
-  this.scale_ = scale;
-};
-
-
-/**
- * Set whether to snap the image to the closest pixel.
- *
- * @param {boolean} snapToPixel Snap to pixel?
- */
-ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) {
-  this.snapToPixel_ = snapToPixel;
-};
-
-
-/**
- * @abstract
- * @param {function(this: T, ol.events.Event)} listener Listener function.
- * @param {T} thisArg Value to use as `this` when executing `listener`.
- * @return {ol.EventsKey|undefined} Listener key.
- * @template T
- */
-ol.style.Image.prototype.listenImageChange = function(listener, thisArg) {};
-
-
-/**
- * Load not yet loaded URI.
- * @abstract
- */
-ol.style.Image.prototype.load = function() {};
-
-
-/**
- * @abstract
- * @param {function(this: T, ol.events.Event)} listener Listener function.
- * @param {T} thisArg Value to use as `this` when executing `listener`.
- * @template T
- */
-ol.style.Image.prototype.unlistenImageChange = function(listener, thisArg) {};
-
-goog.provide('ol.style.Circle');
-
-goog.require('ol');
-goog.require('ol.color');
-goog.require('ol.colorlike');
-goog.require('ol.dom');
-goog.require('ol.has');
-goog.require('ol.Image');
-goog.require('ol.render.canvas');
-goog.require('ol.style.Image');
-
-
-/**
- * @classdesc
- * Set circle style for vector features.
- *
- * @constructor
- * @param {olx.style.CircleOptions=} opt_options Options.
- * @extends {ol.style.Image}
- * @api
- */
-ol.style.Circle = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {ol.style.AtlasManager|undefined}
-   */
-  this.atlasManager_ = options.atlasManager;
-
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.checksums_ = null;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = null;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.hitDetectionCanvas_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.Fill}
-   */
-  this.fill_ = options.fill !== undefined ? options.fill : null;
-
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.radius_ = options.radius;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.origin_ = [0, 0];
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.anchor_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.size_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.imageSize_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.hitDetectionImageSize_ = null;
-
-  this.render_(this.atlasManager_);
-
-  /**
-   * @type {boolean}
-   */
-  var snapToPixel = options.snapToPixel !== undefined ?
-      options.snapToPixel : true;
-
-  ol.style.Image.call(this, {
-    opacity: 1,
-    rotateWithView: false,
-    rotation: 0,
-    scale: 1,
-    snapToPixel: snapToPixel
-  });
-
-};
-ol.inherits(ol.style.Circle, ol.style.Image);
-
-
-/**
- * Clones the style.  If an atlasmanger was provided to the original style it will be used in the cloned style, too.
- * @return {ol.style.Image} The cloned style.
- * @api
- */
-ol.style.Circle.prototype.clone = function() {
-  var style = new ol.style.Circle({
-    fill: this.getFill() ? this.getFill().clone() : undefined,
-    stroke: this.getStroke() ? this.getStroke().clone() : undefined,
-    radius: this.getRadius(),
-    snapToPixel: this.getSnapToPixel(),
-    atlasManager: this.atlasManager_
-  });
-  style.setOpacity(this.getOpacity());
-  style.setScale(this.getScale());
-  return style;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getAnchor = function() {
-  return this.anchor_;
-};
-
-
-/**
- * Get the fill style for the circle.
- * @return {ol.style.Fill} Fill style.
- * @api
- */
-ol.style.Circle.prototype.getFill = function() {
-  return this.fill_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getHitDetectionImage = function(pixelRatio) {
-  return this.hitDetectionCanvas_;
-};
-
-
-/**
- * Get the image used to render the circle.
- * @param {number} pixelRatio Pixel ratio.
- * @return {HTMLCanvasElement} Canvas element.
- * @api
- */
-ol.style.Circle.prototype.getImage = function(pixelRatio) {
-  return this.canvas_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getImageState = function() {
-  return ol.Image.State.LOADED;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getImageSize = function() {
-  return this.imageSize_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getHitDetectionImageSize = function() {
-  return this.hitDetectionImageSize_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getOrigin = function() {
-  return this.origin_;
-};
-
-
-/**
- * Get the circle radius.
- * @return {number} Radius.
- * @api
- */
-ol.style.Circle.prototype.getRadius = function() {
-  return this.radius_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.getSize = function() {
-  return this.size_;
-};
-
-
-/**
- * Get the stroke style for the circle.
- * @return {ol.style.Stroke} Stroke style.
- * @api
- */
-ol.style.Circle.prototype.getStroke = function() {
-  return this.stroke_;
-};
-
-
-/**
- * Set the circle radius.
- *
- * @param {number} radius Circle radius.
- * @api
- */
-ol.style.Circle.prototype.setRadius = function(radius) {
-  this.radius_ = radius;
-  this.render_(this.atlasManager_);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.listenImageChange = ol.nullFunction;
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.load = ol.nullFunction;
-
-
-/**
- * @inheritDoc
- */
-ol.style.Circle.prototype.unlistenImageChange = ol.nullFunction;
-
-
-/**
- * @private
- * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
- */
-ol.style.Circle.prototype.render_ = function(atlasManager) {
-  var imageSize;
-  var lineDash = null;
-  var strokeStyle;
-  var strokeWidth = 0;
-
-  if (this.stroke_) {
-    strokeStyle = ol.colorlike.asColorLike(this.stroke_.getColor());
-    strokeWidth = this.stroke_.getWidth();
-    if (strokeWidth === undefined) {
-      strokeWidth = ol.render.canvas.defaultLineWidth;
-    }
-    lineDash = this.stroke_.getLineDash();
-    if (!ol.has.CANVAS_LINE_DASH) {
-      lineDash = null;
-    }
-  }
-
-
-  var size = 2 * (this.radius_ + strokeWidth) + 1;
-
-  /** @type {ol.CircleRenderOptions} */
-  var renderOptions = {
-    strokeStyle: strokeStyle,
-    strokeWidth: strokeWidth,
-    size: size,
-    lineDash: lineDash
-  };
-
-  if (atlasManager === undefined) {
-    // no atlas manager is used, create a new canvas
-    var context = ol.dom.createCanvasContext2D(size, size);
-    this.canvas_ = context.canvas;
-
-    // canvas.width and height are rounded to the closest integer
-    size = this.canvas_.width;
-    imageSize = size;
-
-    // draw the circle on the canvas
-    this.draw_(renderOptions, context, 0, 0);
-
-    this.createHitDetectionCanvas_(renderOptions);
-  } else {
-    // an atlas manager is used, add the symbol to an atlas
-    size = Math.round(size);
-
-    var hasCustomHitDetectionImage = !this.fill_;
-    var renderHitDetectionCallback;
-    if (hasCustomHitDetectionImage) {
-      // render the hit-detection image into a separate atlas image
-      renderHitDetectionCallback =
-          this.drawHitDetectionCanvas_.bind(this, renderOptions);
-    }
-
-    var id = this.getChecksum();
-    var info = atlasManager.add(
-        id, size, size, this.draw_.bind(this, renderOptions),
-        renderHitDetectionCallback);
-    ol.DEBUG && console.assert(info, 'circle radius is too large');
-
-    this.canvas_ = info.image;
-    this.origin_ = [info.offsetX, info.offsetY];
-    imageSize = info.image.width;
-
-    if (hasCustomHitDetectionImage) {
-      this.hitDetectionCanvas_ = info.hitImage;
-      this.hitDetectionImageSize_ =
-          [info.hitImage.width, info.hitImage.height];
-    } else {
-      this.hitDetectionCanvas_ = this.canvas_;
-      this.hitDetectionImageSize_ = [imageSize, imageSize];
-    }
-  }
-
-  this.anchor_ = [size / 2, size / 2];
-  this.size_ = [size, size];
-  this.imageSize_ = [imageSize, imageSize];
-};
-
-
-/**
- * @private
- * @param {ol.CircleRenderOptions} renderOptions Render options.
- * @param {CanvasRenderingContext2D} context The rendering context.
- * @param {number} x The origin for the symbol (x).
- * @param {number} y The origin for the symbol (y).
- */
-ol.style.Circle.prototype.draw_ = function(renderOptions, context, x, y) {
-  // reset transform
-  context.setTransform(1, 0, 0, 1, 0, 0);
-
-  // then move to (x, y)
-  context.translate(x, y);
-
-  context.beginPath();
-  context.arc(
-      renderOptions.size / 2, renderOptions.size / 2,
-      this.radius_, 0, 2 * Math.PI, true);
-
-  if (this.fill_) {
-    context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor());
-    context.fill();
-  }
-  if (this.stroke_) {
-    context.strokeStyle = renderOptions.strokeStyle;
-    context.lineWidth = renderOptions.strokeWidth;
-    if (renderOptions.lineDash) {
-      context.setLineDash(renderOptions.lineDash);
-    }
-    context.stroke();
-  }
-  context.closePath();
-};
-
-
-/**
- * @private
- * @param {ol.CircleRenderOptions} renderOptions Render options.
- */
-ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) {
-  this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
-  if (this.fill_) {
-    this.hitDetectionCanvas_ = this.canvas_;
-    return;
-  }
-
-  // if no fill style is set, create an extra hit-detection image with a
-  // default fill style
-  var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size);
-  this.hitDetectionCanvas_ = context.canvas;
-
-  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
-};
-
-
-/**
- * @private
- * @param {ol.CircleRenderOptions} renderOptions Render options.
- * @param {CanvasRenderingContext2D} context The context.
- * @param {number} x The origin for the symbol (x).
- * @param {number} y The origin for the symbol (y).
- */
-ol.style.Circle.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) {
-  // reset transform
-  context.setTransform(1, 0, 0, 1, 0, 0);
-
-  // then move to (x, y)
-  context.translate(x, y);
-
-  context.beginPath();
-  context.arc(
-      renderOptions.size / 2, renderOptions.size / 2,
-      this.radius_, 0, 2 * Math.PI, true);
-
-  context.fillStyle = ol.color.asString(ol.render.canvas.defaultFillStyle);
-  context.fill();
-  if (this.stroke_) {
-    context.strokeStyle = renderOptions.strokeStyle;
-    context.lineWidth = renderOptions.strokeWidth;
-    if (renderOptions.lineDash) {
-      context.setLineDash(renderOptions.lineDash);
-    }
-    context.stroke();
-  }
-  context.closePath();
-};
-
-
-/**
- * @return {string} The checksum.
- */
-ol.style.Circle.prototype.getChecksum = function() {
-  var strokeChecksum = this.stroke_ ?
-      this.stroke_.getChecksum() : '-';
-  var fillChecksum = this.fill_ ?
-      this.fill_.getChecksum() : '-';
-
-  var recalculate = !this.checksums_ ||
-      (strokeChecksum != this.checksums_[1] ||
-      fillChecksum != this.checksums_[2] ||
-      this.radius_ != this.checksums_[3]);
-
-  if (recalculate) {
-    var checksum = 'c' + strokeChecksum + fillChecksum +
-        (this.radius_ !== undefined ? this.radius_.toString() : '-');
-    this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_];
-  }
-
-  return this.checksums_[0];
-};
-
-goog.provide('ol.style.Fill');
-
-goog.require('ol');
-goog.require('ol.color');
-
-
-/**
- * @classdesc
- * Set fill style for vector features.
- *
- * @constructor
- * @param {olx.style.FillOptions=} opt_options Options.
- * @api
- */
-ol.style.Fill = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {ol.Color|ol.ColorLike}
-   */
-  this.color_ = options.color !== undefined ? options.color : null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.checksum_ = undefined;
-};
-
-
-/**
- * Clones the style. The color is not cloned if it is an {@link ol.ColorLike}.
- * @return {ol.style.Fill} The cloned style.
- * @api
- */
-ol.style.Fill.prototype.clone = function() {
-  var color = this.getColor();
-  return new ol.style.Fill({
-    color: (color && color.slice) ? color.slice() : color || undefined
-  });
-};
-
-
-/**
- * Get the fill color.
- * @return {ol.Color|ol.ColorLike} Color.
- * @api
- */
-ol.style.Fill.prototype.getColor = function() {
-  return this.color_;
-};
-
-
-/**
- * Set the color.
- *
- * @param {ol.Color|ol.ColorLike} color Color.
- * @api
- */
-ol.style.Fill.prototype.setColor = function(color) {
-  this.color_ = color;
-  this.checksum_ = undefined;
-};
-
-
-/**
- * @return {string} The checksum.
- */
-ol.style.Fill.prototype.getChecksum = function() {
-  if (this.checksum_ === undefined) {
-    if (
-        this.color_ instanceof CanvasPattern ||
-        this.color_ instanceof CanvasGradient
-    ) {
-      this.checksum_ = ol.getUid(this.color_).toString();
-    } else {
-      this.checksum_ = 'f' + (this.color_ ?
-          ol.color.asString(this.color_) : '-');
-    }
-  }
-
-  return this.checksum_;
-};
-
-goog.provide('ol.style.Stroke');
-
-goog.require('ol');
-
-
-/**
- * @classdesc
- * Set stroke style for vector features.
- * Note that the defaults given are the Canvas defaults, which will be used if
- * option is not defined. The `get` functions return whatever was entered in
- * the options; they will not return the default.
- *
- * @constructor
- * @param {olx.style.StrokeOptions=} opt_options Options.
- * @api
- */
-ol.style.Stroke = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {ol.Color|ol.ColorLike}
-   */
-  this.color_ = options.color !== undefined ? options.color : null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.lineCap_ = options.lineCap;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.lineJoin_ = options.lineJoin;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.miterLimit_ = options.miterLimit;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.width_ = options.width;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.checksum_ = undefined;
-};
-
-
-/**
- * Clones the style.
- * @return {ol.style.Stroke} The cloned style.
- * @api
- */
-ol.style.Stroke.prototype.clone = function() {
-  var color = this.getColor();
-  return new ol.style.Stroke({
-    color: (color && color.slice) ? color.slice() : color || undefined,
-    lineCap: this.getLineCap(),
-    lineDash: this.getLineDash() ? this.getLineDash().slice() : undefined,
-    lineJoin: this.getLineJoin(),
-    miterLimit: this.getMiterLimit(),
-    width: this.getWidth()
-  });
-};
-
-
-/**
- * Get the stroke color.
- * @return {ol.Color|ol.ColorLike} Color.
- * @api
- */
-ol.style.Stroke.prototype.getColor = function() {
-  return this.color_;
-};
-
-
-/**
- * Get the line cap type for the stroke.
- * @return {string|undefined} Line cap.
- * @api
- */
-ol.style.Stroke.prototype.getLineCap = function() {
-  return this.lineCap_;
-};
-
-
-/**
- * Get the line dash style for the stroke.
- * @return {Array.<number>} Line dash.
- * @api
- */
-ol.style.Stroke.prototype.getLineDash = function() {
-  return this.lineDash_;
-};
-
-
-/**
- * Get the line join type for the stroke.
- * @return {string|undefined} Line join.
- * @api
- */
-ol.style.Stroke.prototype.getLineJoin = function() {
-  return this.lineJoin_;
-};
-
-
-/**
- * Get the miter limit for the stroke.
- * @return {number|undefined} Miter limit.
- * @api
- */
-ol.style.Stroke.prototype.getMiterLimit = function() {
-  return this.miterLimit_;
-};
-
-
-/**
- * Get the stroke width.
- * @return {number|undefined} Width.
- * @api
- */
-ol.style.Stroke.prototype.getWidth = function() {
-  return this.width_;
-};
-
-
-/**
- * Set the color.
- *
- * @param {ol.Color|ol.ColorLike} color Color.
- * @api
- */
-ol.style.Stroke.prototype.setColor = function(color) {
-  this.color_ = color;
-  this.checksum_ = undefined;
-};
-
-
-/**
- * Set the line cap.
- *
- * @param {string|undefined} lineCap Line cap.
- * @api
- */
-ol.style.Stroke.prototype.setLineCap = function(lineCap) {
-  this.lineCap_ = lineCap;
-  this.checksum_ = undefined;
-};
-
-
-/**
- * Set the line dash.
- *
- * Please note that Internet Explorer 10 and lower [do not support][mdn] the
- * `setLineDash` method on the `CanvasRenderingContext2D` and therefore this
- * property will have no visual effect in these browsers.
- *
- * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility
- *
- * @param {Array.<number>} lineDash Line dash.
- * @api
- */
-ol.style.Stroke.prototype.setLineDash = function(lineDash) {
-  this.lineDash_ = lineDash;
-  this.checksum_ = undefined;
-};
-
-
-/**
- * Set the line join.
- *
- * @param {string|undefined} lineJoin Line join.
- * @api
- */
-ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
-  this.lineJoin_ = lineJoin;
-  this.checksum_ = undefined;
-};
-
-
-/**
- * Set the miter limit.
- *
- * @param {number|undefined} miterLimit Miter limit.
- * @api
- */
-ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
-  this.miterLimit_ = miterLimit;
-  this.checksum_ = undefined;
-};
-
-
-/**
- * Set the width.
- *
- * @param {number|undefined} width Width.
- * @api
- */
-ol.style.Stroke.prototype.setWidth = function(width) {
-  this.width_ = width;
-  this.checksum_ = undefined;
-};
-
-
-/**
- * @return {string} The checksum.
- */
-ol.style.Stroke.prototype.getChecksum = function() {
-  if (this.checksum_ === undefined) {
-    this.checksum_ = 's';
-    if (this.color_) {
-      if (typeof this.color_ === 'string') {
-        this.checksum_ += this.color_;
-      } else {
-        this.checksum_ += ol.getUid(this.color_).toString();
-      }
-    } else {
-      this.checksum_ += '-';
-    }
-    this.checksum_ += ',' +
-        (this.lineCap_ !== undefined ?
-            this.lineCap_.toString() : '-') + ',' +
-        (this.lineDash_ ?
-            this.lineDash_.toString() : '-') + ',' +
-        (this.lineJoin_ !== undefined ?
-            this.lineJoin_ : '-') + ',' +
-        (this.miterLimit_ !== undefined ?
-            this.miterLimit_.toString() : '-') + ',' +
-        (this.width_ !== undefined ?
-            this.width_.toString() : '-');
-  }
-
-  return this.checksum_;
-};
-
-goog.provide('ol.style.Style');
-
-goog.require('ol.asserts');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.style.Circle');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Stroke');
-
-
-/**
- * @classdesc
- * Container for vector feature rendering styles. Any changes made to the style
- * or its children through `set*()` methods will not take effect until the
- * feature or layer that uses the style is re-rendered.
- *
- * @constructor
- * @struct
- * @param {olx.style.StyleOptions=} opt_options Style options.
- * @api
- */
-ol.style.Style = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {string|ol.geom.Geometry|ol.StyleGeometryFunction}
-   */
-  this.geometry_ = null;
-
-  /**
-   * @private
-   * @type {!ol.StyleGeometryFunction}
-   */
-  this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;
-
-  if (options.geometry !== undefined) {
-    this.setGeometry(options.geometry);
-  }
-
-  /**
-   * @private
-   * @type {ol.style.Fill}
-   */
-  this.fill_ = options.fill !== undefined ? options.fill : null;
-
-  /**
-   * @private
-   * @type {ol.style.Image}
-   */
-  this.image_ = options.image !== undefined ? options.image : null;
-
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
-
-  /**
-   * @private
-   * @type {ol.style.Text}
-   */
-  this.text_ = options.text !== undefined ? options.text : null;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.zIndex_ = options.zIndex;
-
-};
-
-
-/**
- * Clones the style.
- * @return {ol.style.Style} The cloned style.
- * @api
- */
-ol.style.Style.prototype.clone = function() {
-  var geometry = this.getGeometry();
-  if (geometry && geometry.clone) {
-    geometry = geometry.clone();
-  }
-  return new ol.style.Style({
-    geometry: geometry,
-    fill: this.getFill() ? this.getFill().clone() : undefined,
-    image: this.getImage() ? this.getImage().clone() : undefined,
-    stroke: this.getStroke() ? this.getStroke().clone() : undefined,
-    text: this.getText() ? this.getText().clone() : undefined,
-    zIndex: this.getZIndex()
-  });
-};
-
-
-/**
- * Get the geometry to be rendered.
- * @return {string|ol.geom.Geometry|ol.StyleGeometryFunction}
- * Feature property or geometry or function that returns the geometry that will
- * be rendered with this style.
- * @api
- */
-ol.style.Style.prototype.getGeometry = function() {
-  return this.geometry_;
-};
-
-
-/**
- * Get the function used to generate a geometry for rendering.
- * @return {!ol.StyleGeometryFunction} Function that is called with a feature
- * and returns the geometry to render instead of the feature's geometry.
- * @api
- */
-ol.style.Style.prototype.getGeometryFunction = function() {
-  return this.geometryFunction_;
-};
-
-
-/**
- * Get the fill style.
- * @return {ol.style.Fill} Fill style.
- * @api
- */
-ol.style.Style.prototype.getFill = function() {
-  return this.fill_;
-};
-
-
-/**
- * Get the image style.
- * @return {ol.style.Image} Image style.
- * @api
- */
-ol.style.Style.prototype.getImage = function() {
-  return this.image_;
-};
-
-
-/**
- * Get the stroke style.
- * @return {ol.style.Stroke} Stroke style.
- * @api
- */
-ol.style.Style.prototype.getStroke = function() {
-  return this.stroke_;
-};
-
-
-/**
- * Get the text style.
- * @return {ol.style.Text} Text style.
- * @api
- */
-ol.style.Style.prototype.getText = function() {
-  return this.text_;
-};
-
-
-/**
- * Get the z-index for the style.
- * @return {number|undefined} ZIndex.
- * @api
- */
-ol.style.Style.prototype.getZIndex = function() {
-  return this.zIndex_;
-};
-
-
-/**
- * Set a geometry that is rendered instead of the feature's geometry.
- *
- * @param {string|ol.geom.Geometry|ol.StyleGeometryFunction} geometry
- *     Feature property or geometry or function returning a geometry to render
- *     for this style.
- * @api
- */
-ol.style.Style.prototype.setGeometry = function(geometry) {
-  if (typeof geometry === 'function') {
-    this.geometryFunction_ = geometry;
-  } else if (typeof geometry === 'string') {
-    this.geometryFunction_ = function(feature) {
-      return /** @type {ol.geom.Geometry} */ (feature.get(geometry));
-    };
-  } else if (!geometry) {
-    this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;
-  } else if (geometry !== undefined) {
-    this.geometryFunction_ = function() {
-      return /** @type {ol.geom.Geometry} */ (geometry);
-    };
-  }
-  this.geometry_ = geometry;
-};
-
-
-/**
- * Set the z-index.
- *
- * @param {number|undefined} zIndex ZIndex.
- * @api
- */
-ol.style.Style.prototype.setZIndex = function(zIndex) {
-  this.zIndex_ = zIndex;
-};
-
-
-/**
- * Convert the provided object into a style function.  Functions passed through
- * unchanged.  Arrays of ol.style.Style or single style objects wrapped in a
- * new style function.
- * @param {ol.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj
- *     A style function, a single style, or an array of styles.
- * @return {ol.StyleFunction} A style function.
- */
-ol.style.Style.createFunction = function(obj) {
-  var styleFunction;
-
-  if (typeof obj === 'function') {
-    styleFunction = obj;
-  } else {
-    /**
-     * @type {Array.<ol.style.Style>}
-     */
-    var styles;
-    if (Array.isArray(obj)) {
-      styles = obj;
-    } else {
-      ol.asserts.assert(obj instanceof ol.style.Style,
-          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
-      styles = [obj];
-    }
-    styleFunction = function() {
-      return styles;
-    };
-  }
-  return styleFunction;
-};
-
-
-/**
- * @type {Array.<ol.style.Style>}
- * @private
- */
-ol.style.Style.default_ = null;
-
-
-/**
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @return {Array.<ol.style.Style>} Style.
- */
-ol.style.Style.defaultFunction = function(feature, resolution) {
-  // We don't use an immediately-invoked function
-  // and a closure so we don't get an error at script evaluation time in
-  // browsers that do not support Canvas. (ol.style.Circle does
-  // canvas.getContext('2d') at construction time, which will cause an.error
-  // in such browsers.)
-  if (!ol.style.Style.default_) {
-    var fill = new ol.style.Fill({
-      color: 'rgba(255,255,255,0.4)'
-    });
-    var stroke = new ol.style.Stroke({
-      color: '#3399CC',
-      width: 1.25
-    });
-    ol.style.Style.default_ = [
-      new ol.style.Style({
-        image: new ol.style.Circle({
-          fill: fill,
-          stroke: stroke,
-          radius: 5
-        }),
-        fill: fill,
-        stroke: stroke
-      })
-    ];
-  }
-  return ol.style.Style.default_;
-};
-
-
-/**
- * Default styles for editing features.
- * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles
- */
-ol.style.Style.createDefaultEditing = function() {
-  /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */
-  var styles = {};
-  var white = [255, 255, 255, 1];
-  var blue = [0, 153, 255, 1];
-  var width = 3;
-  styles[ol.geom.GeometryType.POLYGON] = [
-    new ol.style.Style({
-      fill: new ol.style.Fill({
-        color: [255, 255, 255, 0.5]
-      })
-    })
-  ];
-  styles[ol.geom.GeometryType.MULTI_POLYGON] =
-      styles[ol.geom.GeometryType.POLYGON];
-
-  styles[ol.geom.GeometryType.LINE_STRING] = [
-    new ol.style.Style({
-      stroke: new ol.style.Stroke({
-        color: white,
-        width: width + 2
-      })
-    }),
-    new ol.style.Style({
-      stroke: new ol.style.Stroke({
-        color: blue,
-        width: width
-      })
-    })
-  ];
-  styles[ol.geom.GeometryType.MULTI_LINE_STRING] =
-      styles[ol.geom.GeometryType.LINE_STRING];
-
-  styles[ol.geom.GeometryType.CIRCLE] =
-      styles[ol.geom.GeometryType.POLYGON].concat(
-          styles[ol.geom.GeometryType.LINE_STRING]
-      );
-
-
-  styles[ol.geom.GeometryType.POINT] = [
-    new ol.style.Style({
-      image: new ol.style.Circle({
-        radius: width * 2,
-        fill: new ol.style.Fill({
-          color: blue
-        }),
-        stroke: new ol.style.Stroke({
-          color: white,
-          width: width / 2
-        })
-      }),
-      zIndex: Infinity
-    })
-  ];
-  styles[ol.geom.GeometryType.MULTI_POINT] =
-      styles[ol.geom.GeometryType.POINT];
-
-  styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] =
-      styles[ol.geom.GeometryType.POLYGON].concat(
-          styles[ol.geom.GeometryType.LINE_STRING],
-          styles[ol.geom.GeometryType.POINT]
-      );
-
-  return styles;
-};
-
-
-/**
- * Function that is called with a feature and returns its default geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature to get the geometry
- *     for.
- * @return {ol.geom.Geometry|ol.render.Feature|undefined} Geometry to render.
- */
-ol.style.Style.defaultGeometryFunction = function(feature) {
-  return feature.getGeometry();
-};
-
-goog.provide('ol.layer.Vector');
-
-goog.require('ol');
-goog.require('ol.layer.Layer');
-goog.require('ol.obj');
-goog.require('ol.style.Style');
-
-
-/**
- * @classdesc
- * Vector data that is rendered client-side.
- * Note that any property set in the options is set as a {@link ol.Object}
- * property on the layer object; for example, setting `title: 'My Title'` in the
- * options means that `title` is observable, and has get/set accessors.
- *
- * @constructor
- * @extends {ol.layer.Layer}
- * @fires ol.render.Event
- * @param {olx.layer.VectorOptions=} opt_options Options.
- * @api stable
- */
-ol.layer.Vector = function(opt_options) {
-
-  var options = opt_options ?
-      opt_options : /** @type {olx.layer.VectorOptions} */ ({});
-
-  ol.DEBUG && console.assert(
-      options.renderOrder === undefined || !options.renderOrder ||
-      typeof options.renderOrder === 'function',
-      'renderOrder must be a comparator function');
-
-  var baseOptions = ol.obj.assign({}, options);
-
-  delete baseOptions.style;
-  delete baseOptions.renderBuffer;
-  delete baseOptions.updateWhileAnimating;
-  delete baseOptions.updateWhileInteracting;
-  ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.renderBuffer_ = options.renderBuffer !== undefined ?
-      options.renderBuffer : 100;
-
-  /**
-   * User provided style.
-   * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
-   * @private
-   */
-  this.style_ = null;
-
-  /**
-   * Style function for use within the library.
-   * @type {ol.StyleFunction|undefined}
-   * @private
-   */
-  this.styleFunction_ = undefined;
-
-  this.setStyle(options.style);
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.updateWhileAnimating_ = options.updateWhileAnimating !== undefined ?
-      options.updateWhileAnimating : false;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ?
-      options.updateWhileInteracting : false;
-
-};
-ol.inherits(ol.layer.Vector, ol.layer.Layer);
-
-
-/**
- * @return {number|undefined} Render buffer.
- */
-ol.layer.Vector.prototype.getRenderBuffer = function() {
-  return this.renderBuffer_;
-};
-
-
-/**
- * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
- *     order.
- */
-ol.layer.Vector.prototype.getRenderOrder = function() {
-  return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ (
-      this.get(ol.layer.Vector.Property.RENDER_ORDER));
-};
-
-
-/**
- * Return the associated {@link ol.source.Vector vectorsource} of the layer.
- * @function
- * @return {ol.source.Vector} Source.
- * @api stable
- */
-ol.layer.Vector.prototype.getSource;
-
-
-/**
- * Get the style for features.  This returns whatever was passed to the `style`
- * option at construction or to the `setStyle` method.
- * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
- *     Layer style.
- * @api stable
- */
-ol.layer.Vector.prototype.getStyle = function() {
-  return this.style_;
-};
-
-
-/**
- * Get the style function.
- * @return {ol.StyleFunction|undefined} Layer style function.
- * @api stable
- */
-ol.layer.Vector.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
-};
-
-
-/**
- * @return {boolean} Whether the rendered layer should be updated while
- *     animating.
- */
-ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
-  return this.updateWhileAnimating_;
-};
-
-
-/**
- * @return {boolean} Whether the rendered layer should be updated while
- *     interacting.
- */
-ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
-  return this.updateWhileInteracting_;
-};
-
-
-/**
- * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
- *     Render order.
- */
-ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
-  ol.DEBUG && console.assert(
-      renderOrder === undefined || !renderOrder ||
-      typeof renderOrder === 'function',
-      'renderOrder must be a comparator function');
-  this.set(ol.layer.Vector.Property.RENDER_ORDER, renderOrder);
-};
-
-
-/**
- * Set the style for features.  This can be a single style object, an array
- * of styles, or a function that takes a feature and resolution and returns
- * an array of styles. If it is `undefined` the default style is used. If
- * it is `null` the layer has no style (a `null` style), so only features
- * that have their own styles will be rendered in the layer. See
- * {@link ol.style} for information on the default style.
- * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|null|undefined}
- *     style Layer style.
- * @api stable
- */
-ol.layer.Vector.prototype.setStyle = function(style) {
-  this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction;
-  this.styleFunction_ = style === null ?
-      undefined : ol.style.Style.createFunction(this.style_);
-  this.changed();
-};
-
-
-/**
- * @enum {string}
- */
-ol.layer.Vector.Property = {
-  RENDER_ORDER: 'renderOrder'
-};
-
-goog.provide('ol.layer.VectorTile');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.layer.Tile');
-goog.require('ol.layer.Vector');
-goog.require('ol.obj');
-
-
-/**
- * @classdesc
- * Layer for vector tile data that is rendered client-side.
- * Note that any property set in the options is set as a {@link ol.Object}
- * property on the layer object; for example, setting `title: 'My Title'` in the
- * options means that `title` is observable, and has get/set accessors.
- *
- * @constructor
- * @extends {ol.layer.Vector}
- * @param {olx.layer.VectorTileOptions=} opt_options Options.
- * @api
- */
-ol.layer.VectorTile = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  var baseOptions = ol.obj.assign({}, options);
-
-  delete baseOptions.preload;
-  delete baseOptions.useInterimTilesOnError;
-  ol.layer.Vector.call(this,  /** @type {olx.layer.VectorOptions} */ (baseOptions));
-
-  this.setPreload(options.preload ? options.preload : 0);
-  this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
-      options.useInterimTilesOnError : true);
-
-  ol.asserts.assert(options.renderMode == undefined ||
-      options.renderMode == ol.layer.VectorTile.RenderType.IMAGE ||
-      options.renderMode == ol.layer.VectorTile.RenderType.HYBRID ||
-      options.renderMode == ol.layer.VectorTile.RenderType.VECTOR,
-      28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`
-
-  /**
-   * @private
-   * @type {ol.layer.VectorTile.RenderType|string}
-   */
-  this.renderMode_ = options.renderMode || ol.layer.VectorTile.RenderType.HYBRID;
-
-};
-ol.inherits(ol.layer.VectorTile, ol.layer.Vector);
-
-
-/**
- * Return the level as number to which we will preload tiles up to.
- * @return {number} The level to preload tiles up to.
- * @observable
- * @api
- */
-ol.layer.VectorTile.prototype.getPreload = function() {
-  return /** @type {number} */ (this.get(ol.layer.VectorTile.Property.PRELOAD));
-};
-
-
-/**
- * @return {ol.layer.VectorTile.RenderType|string} The render mode.
- */
-ol.layer.VectorTile.prototype.getRenderMode = function() {
-  return this.renderMode_;
-};
-
-
-/**
- * Whether we use interim tiles on error.
- * @return {boolean} Use interim tiles on error.
- * @observable
- * @api
- */
-ol.layer.VectorTile.prototype.getUseInterimTilesOnError = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.layer.VectorTile.Property.USE_INTERIM_TILES_ON_ERROR));
-};
-
-
-/**
- * Set the level as number to which we will preload tiles up to.
- * @param {number} preload The level to preload tiles up to.
- * @observable
- * @api
- */
-ol.layer.VectorTile.prototype.setPreload = function(preload) {
-  this.set(ol.layer.Tile.Property.PRELOAD, preload);
-};
-
-
-/**
- * Set whether we use interim tiles on error.
- * @param {boolean} useInterimTilesOnError Use interim tiles on error.
- * @observable
- * @api
- */
-ol.layer.VectorTile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
-  this.set(
-      ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
-};
-
-
-/**
- * @enum {string}
- */
-ol.layer.VectorTile.Property = {
-  PRELOAD: 'preload',
-  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
-};
-
-
-/**
- * @enum {string}
- * Render mode for vector tiles:
- *  * `'image'`: Vector tiles are rendered as images. Great performance, but
- *    point symbols and texts are always rotated with the view and pixels are
- *    scaled during zoom animations.
- *  * `'hybrid'`: Polygon and line elements are rendered as images, so pixels
- *    are scaled during zoom animations. Point symbols and texts are accurately
- *    rendered as vectors and can stay upright on rotated views.
- *  * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering
- *    even during animations, but slower performance than the other options.
- * @api
- */
-ol.layer.VectorTile.RenderType = {
-  IMAGE: 'image',
-  HYBRID: 'hybrid',
-  VECTOR: 'vector'
-};
-
-goog.provide('ol.render.VectorContext');
-
-
-/**
- * Context for drawing geometries.  A vector context is available on render
- * events and does not need to be constructed directly.
- * @constructor
- * @struct
- * @api
- */
-ol.render.VectorContext = function() {
-};
-
-
-/**
- * Render a geometry.
- *
- * @abstract
- * @param {ol.geom.Geometry} geometry The geometry to render.
- */
-ol.render.VectorContext.prototype.drawGeometry = function(geometry) {};
-
-
-/**
- * Set the rendering style.
- *
- * @abstract
- * @param {ol.style.Style} style The rendering style.
- */
-ol.render.VectorContext.prototype.setStyle = function(style) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.Circle} circleGeometry Circle geometry.
- * @param {ol.Feature} feature Feature,
- */
-ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
- */
-ol.render.VectorContext.prototype.drawFeature = function(feature, style) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
- *     collection.
- * @param {ol.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line
- *     string geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry
- *     MultiLineString geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint
- *     geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
- * @param {ol.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon
- *     geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {};
-
-
-/**
- * @abstract
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.VectorContext.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {};
-
-
-/**
- * @abstract
- * @param {ol.style.Fill} fillStyle Fill style.
- * @param {ol.style.Stroke} strokeStyle Stroke style.
- */
-ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {};
-
-
-/**
- * @abstract
- * @param {ol.style.Image} imageStyle Image style.
- */
-ol.render.VectorContext.prototype.setImageStyle = function(imageStyle) {};
-
-
-/**
- * @abstract
- * @param {ol.style.Text} textStyle Text style.
- */
-ol.render.VectorContext.prototype.setTextStyle = function(textStyle) {};
-
-// FIXME test, especially polygons with holes and multipolygons
-// FIXME need to handle large thick features (where pixel size matters)
-// FIXME add offset and end to ol.geom.flat.transform.transform2D?
-
-goog.provide('ol.render.canvas.Immediate');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.colorlike');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.transform');
-goog.require('ol.has');
-goog.require('ol.render.VectorContext');
-goog.require('ol.render.canvas');
-goog.require('ol.transform');
-
-
-/**
- * @classdesc
- * A concrete subclass of {@link ol.render.VectorContext} that implements
- * direct rendering of features and geometries to an HTML5 Canvas context.
- * Instances of this class are created internally by the library and
- * provided to application code as vectorContext member of the
- * {@link ol.render.Event} object associated with postcompose, precompose and
- * render events emitted by layers and maps.
- *
- * @constructor
- * @extends {ol.render.VectorContext}
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Extent} extent Extent.
- * @param {ol.Transform} transform Transform.
- * @param {number} viewRotation View rotation.
- * @struct
- */
-ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform, viewRotation) {
-  ol.render.VectorContext.call(this);
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = context;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = pixelRatio;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = extent;
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.transform_ = transform;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.viewRotation_ = viewRotation;
-
-  /**
-   * @private
-   * @type {?ol.CanvasFillState}
-   */
-  this.contextFillState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
-   */
-  this.contextStrokeState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasTextState}
-   */
-  this.contextTextState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasFillState}
-   */
-  this.fillState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
-   */
-  this.strokeState_ = null;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageAnchorX_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageAnchorY_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageHeight_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageOpacity_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageOriginX_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageOriginY_ = 0;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.imageRotateWithView_ = false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageRotation_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageScale_ = 0;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.imageSnapToPixel_ = false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.imageWidth_ = 0;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.text_ = '';
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetX_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetY_ = 0;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.textRotateWithView_ = false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textRotation_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textScale_ = 0;
-
-  /**
-   * @private
-   * @type {?ol.CanvasFillState}
-   */
-  this.textFillState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
-   */
-  this.textStrokeState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasTextState}
-   */
-  this.textState_ = null;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.pixelCoordinates_ = [];
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.tmpLocalTransform_ = ol.transform.create();
-
-};
-ol.inherits(ol.render.canvas.Immediate, ol.render.VectorContext);
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @private
- */
-ol.render.canvas.Immediate.prototype.drawImages_ = function(flatCoordinates, offset, end, stride) {
-  if (!this.image_) {
-    return;
-  }
-  ol.DEBUG && console.assert(offset === 0, 'offset should be 0');
-  ol.DEBUG && console.assert(end == flatCoordinates.length,
-      'end should be equal to the length of flatCoordinates');
-  var pixelCoordinates = ol.geom.flat.transform.transform2D(
-      flatCoordinates, offset, end, 2, this.transform_,
-      this.pixelCoordinates_);
-  var context = this.context_;
-  var localTransform = this.tmpLocalTransform_;
-  var alpha = context.globalAlpha;
-  if (this.imageOpacity_ != 1) {
-    context.globalAlpha = alpha * this.imageOpacity_;
-  }
-  var rotation = this.imageRotation_;
-  if (this.imageRotateWithView_) {
-    rotation += this.viewRotation_;
-  }
-  var i, ii;
-  for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) {
-    var x = pixelCoordinates[i] - this.imageAnchorX_;
-    var y = pixelCoordinates[i + 1] - this.imageAnchorY_;
-    if (this.imageSnapToPixel_) {
-      x = Math.round(x);
-      y = Math.round(y);
-    }
-    if (rotation !== 0 || this.imageScale_ != 1) {
-      var centerX = x + this.imageAnchorX_;
-      var centerY = y + this.imageAnchorY_;
-      ol.transform.compose(localTransform,
-          centerX, centerY,
-          this.imageScale_, this.imageScale_,
-          rotation,
-          -centerX, -centerY);
-      context.setTransform.apply(context, localTransform);
-    }
-    context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_,
-        this.imageWidth_, this.imageHeight_, x, y,
-        this.imageWidth_, this.imageHeight_);
-  }
-  if (rotation !== 0 || this.imageScale_ != 1) {
-    context.setTransform(1, 0, 0, 1, 0, 0);
-  }
-  if (this.imageOpacity_ != 1) {
-    context.globalAlpha = alpha;
-  }
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @private
- */
-ol.render.canvas.Immediate.prototype.drawText_ = function(flatCoordinates, offset, end, stride) {
-  if (!this.textState_ || this.text_ === '') {
-    return;
-  }
-  if (this.textFillState_) {
-    this.setContextFillState_(this.textFillState_);
-  }
-  if (this.textStrokeState_) {
-    this.setContextStrokeState_(this.textStrokeState_);
-  }
-  this.setContextTextState_(this.textState_);
-  ol.DEBUG && console.assert(offset === 0, 'offset should be 0');
-  ol.DEBUG && console.assert(end == flatCoordinates.length,
-      'end should be equal to the length of flatCoordinates');
-  var pixelCoordinates = ol.geom.flat.transform.transform2D(
-      flatCoordinates, offset, end, stride, this.transform_,
-      this.pixelCoordinates_);
-  var context = this.context_;
-  var rotation = this.textRotation_;
-  if (this.textRotateWithView_) {
-    rotation += this.viewRotation_;
-  }
-  for (; offset < end; offset += stride) {
-    var x = pixelCoordinates[offset] + this.textOffsetX_;
-    var y = pixelCoordinates[offset + 1] + this.textOffsetY_;
-    if (rotation !== 0 || this.textScale_ != 1) {
-      var localTransform = ol.transform.compose(this.tmpLocalTransform_,
-          x, y,
-          this.textScale_, this.textScale_,
-          rotation,
-          -x, -y);
-      context.setTransform.apply(context, localTransform);
-    }
-    if (this.textStrokeState_) {
-      context.strokeText(this.text_, x, y);
-    }
-    if (this.textFillState_) {
-      context.fillText(this.text_, x, y);
-    }
-  }
-  if (rotation !== 0 || this.textScale_ != 1) {
-    context.setTransform(1, 0, 0, 1, 0, 0);
-  }
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {boolean} close Close.
- * @private
- * @return {number} end End.
- */
-ol.render.canvas.Immediate.prototype.moveToLineTo_ = function(flatCoordinates, offset, end, stride, close) {
-  var context = this.context_;
-  var pixelCoordinates = ol.geom.flat.transform.transform2D(
-      flatCoordinates, offset, end, stride, this.transform_,
-      this.pixelCoordinates_);
-  context.moveTo(pixelCoordinates[0], pixelCoordinates[1]);
-  var length = pixelCoordinates.length;
-  if (close) {
-    length -= 2;
-  }
-  for (var i = 2; i < length; i += 2) {
-    context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]);
-  }
-  if (close) {
-    context.closePath();
-  }
-  return end;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @private
- * @return {number} End.
- */
-ol.render.canvas.Immediate.prototype.drawRings_ = function(flatCoordinates, offset, ends, stride) {
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    offset = this.moveToLineTo_(
-        flatCoordinates, offset, ends[i], stride, true);
-  }
-  return offset;
-};
-
-
-/**
- * Render a circle geometry into the canvas.  Rendering is immediate and uses
- * the current fill and stroke styles.
- *
- * @param {ol.geom.Circle} geometry Circle geometry.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawCircle = function(geometry) {
-  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
-  }
-  if (this.fillState_ || this.strokeState_) {
-    if (this.fillState_) {
-      this.setContextFillState_(this.fillState_);
-    }
-    if (this.strokeState_) {
-      this.setContextStrokeState_(this.strokeState_);
-    }
-    var pixelCoordinates = ol.geom.SimpleGeometry.transform2D(
-        geometry, this.transform_, this.pixelCoordinates_);
-    var dx = pixelCoordinates[2] - pixelCoordinates[0];
-    var dy = pixelCoordinates[3] - pixelCoordinates[1];
-    var radius = Math.sqrt(dx * dx + dy * dy);
-    var context = this.context_;
-    context.beginPath();
-    context.arc(
-        pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI);
-    if (this.fillState_) {
-      context.fill();
-    }
-    if (this.strokeState_) {
-      context.stroke();
-    }
-  }
-  if (this.text_ !== '') {
-    this.drawText_(geometry.getCenter(), 0, 2, 2);
-  }
-};
-
-
-/**
- * Set the rendering style.  Note that since this is an immediate rendering API,
- * any `zIndex` on the provided style will be ignored.
- *
- * @param {ol.style.Style} style The rendering style.
- * @api
- */
-ol.render.canvas.Immediate.prototype.setStyle = function(style) {
-  this.setFillStrokeStyle(style.getFill(), style.getStroke());
-  this.setImageStyle(style.getImage());
-  this.setTextStyle(style.getText());
-};
-
-
-/**
- * Render a geometry into the canvas.  Call
- * {@link ol.render.canvas.Immediate#setStyle} first to set the rendering style.
- *
- * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawGeometry = function(geometry) {
-  var type = geometry.getType();
-  switch (type) {
-    case ol.geom.GeometryType.POINT:
-      this.drawPoint(/** @type {ol.geom.Point} */ (geometry));
-      break;
-    case ol.geom.GeometryType.LINE_STRING:
-      this.drawLineString(/** @type {ol.geom.LineString} */ (geometry));
-      break;
-    case ol.geom.GeometryType.POLYGON:
-      this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry));
-      break;
-    case ol.geom.GeometryType.MULTI_POINT:
-      this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry));
-      break;
-    case ol.geom.GeometryType.MULTI_LINE_STRING:
-      this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry));
-      break;
-    case ol.geom.GeometryType.MULTI_POLYGON:
-      this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry));
-      break;
-    case ol.geom.GeometryType.GEOMETRY_COLLECTION:
-      this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry));
-      break;
-    case ol.geom.GeometryType.CIRCLE:
-      this.drawCircle(/** @type {ol.geom.Circle} */ (geometry));
-      break;
-    default:
-      ol.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type);
-  }
-};
-
-
-/**
- * Render a feature into the canvas.  Note that any `zIndex` on the provided
- * style will be ignored - features are rendered immediately in the order that
- * this method is called.  If you need `zIndex` support, you should be using an
- * {@link ol.layer.Vector} instead.
- *
- * @param {ol.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
- * @api
- */
-ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) {
-  var geometry = style.getGeometryFunction()(feature);
-  if (!geometry ||
-      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
-  }
-  this.setStyle(style);
-  this.drawGeometry(geometry);
-};
-
-
-/**
- * Render a GeometryCollection to the canvas.  Rendering is immediate and
- * uses the current styles appropriate for each geometry in the collection.
- *
- * @param {ol.geom.GeometryCollection} geometry Geometry collection.
- */
-ol.render.canvas.Immediate.prototype.drawGeometryCollection = function(geometry) {
-  var geometries = geometry.getGeometriesArray();
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    this.drawGeometry(geometries[i]);
-  }
-};
-
-
-/**
- * Render a Point geometry into the canvas.  Rendering is immediate and uses
- * the current style.
- *
- * @param {ol.geom.Point|ol.render.Feature} geometry Point geometry.
- */
-ol.render.canvas.Immediate.prototype.drawPoint = function(geometry) {
-  var flatCoordinates = geometry.getFlatCoordinates();
-  var stride = geometry.getStride();
-  if (this.image_) {
-    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-  if (this.text_ !== '') {
-    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-};
-
-
-/**
- * Render a MultiPoint geometry  into the canvas.  Rendering is immediate and
- * uses the current style.
- *
- * @param {ol.geom.MultiPoint|ol.render.Feature} geometry MultiPoint geometry.
- */
-ol.render.canvas.Immediate.prototype.drawMultiPoint = function(geometry) {
-  var flatCoordinates = geometry.getFlatCoordinates();
-  var stride = geometry.getStride();
-  if (this.image_) {
-    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-  if (this.text_ !== '') {
-    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
-  }
-};
-
-
-/**
- * Render a LineString into the canvas.  Rendering is immediate and uses
- * the current style.
- *
- * @param {ol.geom.LineString|ol.render.Feature} geometry LineString geometry.
- */
-ol.render.canvas.Immediate.prototype.drawLineString = function(geometry) {
-  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
-  }
-  if (this.strokeState_) {
-    this.setContextStrokeState_(this.strokeState_);
-    var context = this.context_;
-    var flatCoordinates = geometry.getFlatCoordinates();
-    context.beginPath();
-    this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length,
-        geometry.getStride(), false);
-    context.stroke();
-  }
-  if (this.text_ !== '') {
-    var flatMidpoint = geometry.getFlatMidpoint();
-    this.drawText_(flatMidpoint, 0, 2, 2);
-  }
-};
-
-
-/**
- * Render a MultiLineString geometry into the canvas.  Rendering is immediate
- * and uses the current style.
- *
- * @param {ol.geom.MultiLineString|ol.render.Feature} geometry MultiLineString
- *     geometry.
- */
-ol.render.canvas.Immediate.prototype.drawMultiLineString = function(geometry) {
-  var geometryExtent = geometry.getExtent();
-  if (!ol.extent.intersects(this.extent_, geometryExtent)) {
-    return;
-  }
-  if (this.strokeState_) {
-    this.setContextStrokeState_(this.strokeState_);
-    var context = this.context_;
-    var flatCoordinates = geometry.getFlatCoordinates();
-    var offset = 0;
-    var ends = geometry.getEnds();
-    var stride = geometry.getStride();
-    context.beginPath();
-    var i, ii;
-    for (i = 0, ii = ends.length; i < ii; ++i) {
-      offset = this.moveToLineTo_(
-          flatCoordinates, offset, ends[i], stride, false);
-    }
-    context.stroke();
-  }
-  if (this.text_ !== '') {
-    var flatMidpoints = geometry.getFlatMidpoints();
-    this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2);
-  }
-};
-
-
-/**
- * Render a Polygon geometry into the canvas.  Rendering is immediate and uses
- * the current style.
- *
- * @param {ol.geom.Polygon|ol.render.Feature} geometry Polygon geometry.
- */
-ol.render.canvas.Immediate.prototype.drawPolygon = function(geometry) {
-  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
-  }
-  if (this.strokeState_ || this.fillState_) {
-    if (this.fillState_) {
-      this.setContextFillState_(this.fillState_);
-    }
-    if (this.strokeState_) {
-      this.setContextStrokeState_(this.strokeState_);
-    }
-    var context = this.context_;
-    context.beginPath();
-    this.drawRings_(geometry.getOrientedFlatCoordinates(),
-        0, geometry.getEnds(), geometry.getStride());
-    if (this.fillState_) {
-      context.fill();
-    }
-    if (this.strokeState_) {
-      context.stroke();
-    }
-  }
-  if (this.text_ !== '') {
-    var flatInteriorPoint = geometry.getFlatInteriorPoint();
-    this.drawText_(flatInteriorPoint, 0, 2, 2);
-  }
-};
-
-
-/**
- * Render MultiPolygon geometry into the canvas.  Rendering is immediate and
- * uses the current style.
- * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
- */
-ol.render.canvas.Immediate.prototype.drawMultiPolygon = function(geometry) {
-  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
-  }
-  if (this.strokeState_ || this.fillState_) {
-    if (this.fillState_) {
-      this.setContextFillState_(this.fillState_);
-    }
-    if (this.strokeState_) {
-      this.setContextStrokeState_(this.strokeState_);
-    }
-    var context = this.context_;
-    var flatCoordinates = geometry.getOrientedFlatCoordinates();
-    var offset = 0;
-    var endss = geometry.getEndss();
-    var stride = geometry.getStride();
-    var i, ii;
-    context.beginPath();
-    for (i = 0, ii = endss.length; i < ii; ++i) {
-      var ends = endss[i];
-      offset = this.drawRings_(flatCoordinates, offset, ends, stride);
-    }
-    if (this.fillState_) {
-      context.fill();
-    }
-    if (this.strokeState_) {
-      context.stroke();
-    }
-  }
-  if (this.text_ !== '') {
-    var flatInteriorPoints = geometry.getFlatInteriorPoints();
-    this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2);
-  }
-};
-
-
-/**
- * @param {ol.CanvasFillState} fillState Fill state.
- * @private
- */
-ol.render.canvas.Immediate.prototype.setContextFillState_ = function(fillState) {
-  var context = this.context_;
-  var contextFillState = this.contextFillState_;
-  if (!contextFillState) {
-    context.fillStyle = fillState.fillStyle;
-    this.contextFillState_ = {
-      fillStyle: fillState.fillStyle
-    };
-  } else {
-    if (contextFillState.fillStyle != fillState.fillStyle) {
-      contextFillState.fillStyle = context.fillStyle = fillState.fillStyle;
-    }
-  }
-};
-
-
-/**
- * @param {ol.CanvasStrokeState} strokeState Stroke state.
- * @private
- */
-ol.render.canvas.Immediate.prototype.setContextStrokeState_ = function(strokeState) {
-  var context = this.context_;
-  var contextStrokeState = this.contextStrokeState_;
-  if (!contextStrokeState) {
-    context.lineCap = strokeState.lineCap;
-    if (ol.has.CANVAS_LINE_DASH) {
-      context.setLineDash(strokeState.lineDash);
-    }
-    context.lineJoin = strokeState.lineJoin;
-    context.lineWidth = strokeState.lineWidth;
-    context.miterLimit = strokeState.miterLimit;
-    context.strokeStyle = strokeState.strokeStyle;
-    this.contextStrokeState_ = {
-      lineCap: strokeState.lineCap,
-      lineDash: strokeState.lineDash,
-      lineJoin: strokeState.lineJoin,
-      lineWidth: strokeState.lineWidth,
-      miterLimit: strokeState.miterLimit,
-      strokeStyle: strokeState.strokeStyle
-    };
-  } else {
-    if (contextStrokeState.lineCap != strokeState.lineCap) {
-      contextStrokeState.lineCap = context.lineCap = strokeState.lineCap;
-    }
-    if (ol.has.CANVAS_LINE_DASH) {
-      if (!ol.array.equals(
-          contextStrokeState.lineDash, strokeState.lineDash)) {
-        context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash);
-      }
-    }
-    if (contextStrokeState.lineJoin != strokeState.lineJoin) {
-      contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin;
-    }
-    if (contextStrokeState.lineWidth != strokeState.lineWidth) {
-      contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth;
-    }
-    if (contextStrokeState.miterLimit != strokeState.miterLimit) {
-      contextStrokeState.miterLimit = context.miterLimit =
-          strokeState.miterLimit;
-    }
-    if (contextStrokeState.strokeStyle != strokeState.strokeStyle) {
-      contextStrokeState.strokeStyle = context.strokeStyle =
-          strokeState.strokeStyle;
-    }
-  }
-};
-
-
-/**
- * @param {ol.CanvasTextState} textState Text state.
- * @private
- */
-ol.render.canvas.Immediate.prototype.setContextTextState_ = function(textState) {
-  var context = this.context_;
-  var contextTextState = this.contextTextState_;
-  if (!contextTextState) {
-    context.font = textState.font;
-    context.textAlign = textState.textAlign;
-    context.textBaseline = textState.textBaseline;
-    this.contextTextState_ = {
-      font: textState.font,
-      textAlign: textState.textAlign,
-      textBaseline: textState.textBaseline
-    };
-  } else {
-    if (contextTextState.font != textState.font) {
-      contextTextState.font = context.font = textState.font;
-    }
-    if (contextTextState.textAlign != textState.textAlign) {
-      contextTextState.textAlign = context.textAlign = textState.textAlign;
-    }
-    if (contextTextState.textBaseline != textState.textBaseline) {
-      contextTextState.textBaseline = context.textBaseline =
-          textState.textBaseline;
-    }
-  }
-};
-
-
-/**
- * Set the fill and stroke style for subsequent draw operations.  To clear
- * either fill or stroke styles, pass null for the appropriate parameter.
- *
- * @param {ol.style.Fill} fillStyle Fill style.
- * @param {ol.style.Stroke} strokeStyle Stroke style.
- */
-ol.render.canvas.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
-  if (!fillStyle) {
-    this.fillState_ = null;
-  } else {
-    var fillStyleColor = fillStyle.getColor();
-    this.fillState_ = {
-      fillStyle: ol.colorlike.asColorLike(fillStyleColor ?
-          fillStyleColor : ol.render.canvas.defaultFillStyle)
-    };
-  }
-  if (!strokeStyle) {
-    this.strokeState_ = null;
-  } else {
-    var strokeStyleColor = strokeStyle.getColor();
-    var strokeStyleLineCap = strokeStyle.getLineCap();
-    var strokeStyleLineDash = strokeStyle.getLineDash();
-    var strokeStyleLineJoin = strokeStyle.getLineJoin();
-    var strokeStyleWidth = strokeStyle.getWidth();
-    var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
-    this.strokeState_ = {
-      lineCap: strokeStyleLineCap !== undefined ?
-          strokeStyleLineCap : ol.render.canvas.defaultLineCap,
-      lineDash: strokeStyleLineDash ?
-          strokeStyleLineDash : ol.render.canvas.defaultLineDash,
-      lineJoin: strokeStyleLineJoin !== undefined ?
-          strokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
-      lineWidth: this.pixelRatio_ * (strokeStyleWidth !== undefined ?
-          strokeStyleWidth : ol.render.canvas.defaultLineWidth),
-      miterLimit: strokeStyleMiterLimit !== undefined ?
-          strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
-      strokeStyle: ol.colorlike.asColorLike(strokeStyleColor ?
-          strokeStyleColor : ol.render.canvas.defaultStrokeStyle)
-    };
-  }
-};
-
-
-/**
- * Set the image style for subsequent draw operations.  Pass null to remove
- * the image style.
- *
- * @param {ol.style.Image} imageStyle Image style.
- */
-ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) {
-  if (!imageStyle) {
-    this.image_ = null;
-  } else {
-    var imageAnchor = imageStyle.getAnchor();
-    // FIXME pixel ratio
-    var imageImage = imageStyle.getImage(1);
-    var imageOrigin = imageStyle.getOrigin();
-    var imageSize = imageStyle.getSize();
-    ol.DEBUG && console.assert(imageImage, 'imageImage must be truthy');
-    this.imageAnchorX_ = imageAnchor[0];
-    this.imageAnchorY_ = imageAnchor[1];
-    this.imageHeight_ = imageSize[1];
-    this.image_ = imageImage;
-    this.imageOpacity_ = imageStyle.getOpacity();
-    this.imageOriginX_ = imageOrigin[0];
-    this.imageOriginY_ = imageOrigin[1];
-    this.imageRotateWithView_ = imageStyle.getRotateWithView();
-    this.imageRotation_ = imageStyle.getRotation();
-    this.imageScale_ = imageStyle.getScale();
-    this.imageSnapToPixel_ = imageStyle.getSnapToPixel();
-    this.imageWidth_ = imageSize[0];
-  }
-};
-
-
-/**
- * Set the text style for subsequent draw operations.  Pass null to
- * remove the text style.
- *
- * @param {ol.style.Text} textStyle Text style.
- */
-ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) {
-  if (!textStyle) {
-    this.text_ = '';
-  } else {
-    var textFillStyle = textStyle.getFill();
-    if (!textFillStyle) {
-      this.textFillState_ = null;
-    } else {
-      var textFillStyleColor = textFillStyle.getColor();
-      this.textFillState_ = {
-        fillStyle: ol.colorlike.asColorLike(textFillStyleColor ?
-            textFillStyleColor : ol.render.canvas.defaultFillStyle)
-      };
-    }
-    var textStrokeStyle = textStyle.getStroke();
-    if (!textStrokeStyle) {
-      this.textStrokeState_ = null;
-    } else {
-      var textStrokeStyleColor = textStrokeStyle.getColor();
-      var textStrokeStyleLineCap = textStrokeStyle.getLineCap();
-      var textStrokeStyleLineDash = textStrokeStyle.getLineDash();
-      var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin();
-      var textStrokeStyleWidth = textStrokeStyle.getWidth();
-      var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit();
-      this.textStrokeState_ = {
-        lineCap: textStrokeStyleLineCap !== undefined ?
-            textStrokeStyleLineCap : ol.render.canvas.defaultLineCap,
-        lineDash: textStrokeStyleLineDash ?
-            textStrokeStyleLineDash : ol.render.canvas.defaultLineDash,
-        lineJoin: textStrokeStyleLineJoin !== undefined ?
-            textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
-        lineWidth: textStrokeStyleWidth !== undefined ?
-            textStrokeStyleWidth : ol.render.canvas.defaultLineWidth,
-        miterLimit: textStrokeStyleMiterLimit !== undefined ?
-            textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
-        strokeStyle: ol.colorlike.asColorLike(textStrokeStyleColor ?
-            textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle)
-      };
-    }
-    var textFont = textStyle.getFont();
-    var textOffsetX = textStyle.getOffsetX();
-    var textOffsetY = textStyle.getOffsetY();
-    var textRotateWithView = textStyle.getRotateWithView();
-    var textRotation = textStyle.getRotation();
-    var textScale = textStyle.getScale();
-    var textText = textStyle.getText();
-    var textTextAlign = textStyle.getTextAlign();
-    var textTextBaseline = textStyle.getTextBaseline();
-    this.textState_ = {
-      font: textFont !== undefined ?
-          textFont : ol.render.canvas.defaultFont,
-      textAlign: textTextAlign !== undefined ?
-          textTextAlign : ol.render.canvas.defaultTextAlign,
-      textBaseline: textTextBaseline !== undefined ?
-          textTextBaseline : ol.render.canvas.defaultTextBaseline
-    };
-    this.text_ = textText !== undefined ? textText : '';
-    this.textOffsetX_ =
-        textOffsetX !== undefined ? (this.pixelRatio_ * textOffsetX) : 0;
-    this.textOffsetY_ =
-        textOffsetY !== undefined ? (this.pixelRatio_ * textOffsetY) : 0;
-    this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false;
-    this.textRotation_ = textRotation !== undefined ? textRotation : 0;
-    this.textScale_ = this.pixelRatio_ * (textScale !== undefined ?
-        textScale : 1);
-  }
-};
-
-goog.provide('ol.renderer.Layer');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.Observable');
-goog.require('ol.Tile');
-goog.require('ol.asserts');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.functions');
-goog.require('ol.source.State');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.Observable}
- * @param {ol.layer.Layer} layer Layer.
- * @struct
- */
-ol.renderer.Layer = function(layer) {
-
-  ol.Observable.call(this);
-
-  /**
-   * @private
-   * @type {ol.layer.Layer}
-   */
-  this.layer_ = layer;
-
-
-};
-ol.inherits(ol.renderer.Layer, ol.Observable);
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {olx.FrameState} frameState Frame state.
- * @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T}
- *     callback Feature callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T
- */
-ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
-
-
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.FrameState} frameState Frame state.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T
- */
-ol.renderer.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
-  var coordinate = ol.transform.apply(
-      frameState.pixelToCoordinateTransform, pixel.slice());
-
-  var hasFeature = this.forEachFeatureAtCoordinate(
-      coordinate, frameState, ol.functions.TRUE, this);
-
-  if (hasFeature) {
-    return callback.call(thisArg, this.layer_, null);
-  } 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 = ol.functions.FALSE;
-
-
-/**
- * Create a function that adds loaded tiles to the tile lookup.
- * @param {ol.source.Tile} source Tile source.
- * @param {ol.proj.Projection} projection Projection of the tiles.
- * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded
- *     tiles by zoom level.
- * @return {function(number, ol.TileRange):boolean} A function that can be
- *     called with a zoom level and a tile range to add loaded tiles to the
- *     lookup.
- * @protected
- */
-ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
-  return (
-      /**
-       * @param {number} zoom Zoom level.
-       * @param {ol.TileRange} tileRange Tile range.
-       * @return {boolean} The tile range is fully loaded.
-       */
-      function(zoom, tileRange) {
-        function callback(tile) {
-          if (!tiles[zoom]) {
-            tiles[zoom] = {};
-          }
-          tiles[zoom][tile.tileCoord.toString()] = tile;
-        }
-        return source.forEachLoadedTile(projection, zoom, tileRange, callback);
-      });
-};
-
-
-/**
- * @return {ol.layer.Layer} Layer.
- */
-ol.renderer.Layer.prototype.getLayer = function() {
-  return this.layer_;
-};
-
-
-/**
- * Handle changes in image state.
- * @param {ol.events.Event} event Image change event.
- * @private
- */
-ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
-  var image = /** @type {ol.Image} */ (event.target);
-  if (image.getState() === ol.Image.State.LOADED) {
-    this.renderIfReadyAndVisible();
-  }
-};
-
-
-/**
- * Load the image if not already loaded, and register the image change
- * listener if needed.
- * @param {ol.ImageBase} image Image.
- * @return {boolean} `true` if the image is already loaded, `false`
- *     otherwise.
- * @protected
- */
-ol.renderer.Layer.prototype.loadImage = function(image) {
-  var imageState = image.getState();
-  if (imageState != ol.Image.State.LOADED &&
-      imageState != ol.Image.State.ERROR) {
-    // the image is either "idle" or "loading", register the change
-    // listener (a noop if the listener was already registered)
-    ol.DEBUG && console.assert(imageState == ol.Image.State.IDLE ||
-        imageState == ol.Image.State.LOADING,
-        'imageState is "idle" or "loading"');
-    ol.events.listen(image, ol.events.EventType.CHANGE,
-        this.handleImageChange_, this);
-  }
-  if (imageState == ol.Image.State.IDLE) {
-    image.load();
-    imageState = image.getState();
-    ol.DEBUG && console.assert(imageState == ol.Image.State.LOADING ||
-        imageState == ol.Image.State.LOADED,
-        'imageState is "loading" or "loaded"');
-  }
-  return imageState == ol.Image.State.LOADED;
-};
-
-
-/**
- * @protected
- */
-ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
-  var layer = this.getLayer();
-  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
-    this.changed();
-  }
-};
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Tile} tileSource Tile source.
- * @protected
- */
-ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) {
-  if (tileSource.canExpireCache()) {
-    /**
-     * @param {ol.source.Tile} tileSource Tile source.
-     * @param {ol.Map} map Map.
-     * @param {olx.FrameState} frameState Frame state.
-     */
-    var postRenderFunction = function(tileSource, map, frameState) {
-      var tileSourceKey = ol.getUid(tileSource).toString();
-      tileSource.expireCache(frameState.viewState.projection,
-                             frameState.usedTiles[tileSourceKey]);
-    }.bind(null, tileSource);
-
-    frameState.postRenderFunctions.push(
-      /** @type {ol.PostRenderFunction} */ (postRenderFunction)
-    );
-  }
-};
-
-
-/**
- * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
- *     set (target).
- * @param {Array.<ol.Attribution>} attributions Attributions (source).
- * @protected
- */
-ol.renderer.Layer.prototype.updateAttributions = function(attributionsSet, attributions) {
-  if (attributions) {
-    var attribution, i, ii;
-    for (i = 0, ii = attributions.length; i < ii; ++i) {
-      attribution = attributions[i];
-      attributionsSet[ol.getUid(attribution).toString()] = attribution;
-    }
-  }
-};
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Source} source Source.
- * @protected
- */
-ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
-  var logo = source.getLogo();
-  if (logo !== undefined) {
-    if (typeof logo === 'string') {
-      frameState.logos[logo] = '';
-    } else if (logo) {
-      ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
-      ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
-      frameState.logos[logo.src] = logo.href;
-    }
-  }
-};
-
-
-/**
- * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles.
- * @param {ol.source.Tile} tileSource Tile source.
- * @param {number} z Z.
- * @param {ol.TileRange} tileRange Tile range.
- * @protected
- */
-ol.renderer.Layer.prototype.updateUsedTiles = function(usedTiles, tileSource, z, tileRange) {
-  // FIXME should we use tilesToDrawByZ instead?
-  var tileSourceKey = ol.getUid(tileSource).toString();
-  var zKey = z.toString();
-  if (tileSourceKey in usedTiles) {
-    if (zKey in usedTiles[tileSourceKey]) {
-      usedTiles[tileSourceKey][zKey].extend(tileRange);
-    } else {
-      usedTiles[tileSourceKey][zKey] = tileRange;
-    }
-  } else {
-    usedTiles[tileSourceKey] = {};
-    usedTiles[tileSourceKey][zKey] = tileRange;
-  }
-};
-
-
-/**
- * Manage tile pyramid.
- * This function performs a number of functions related to the tiles at the
- * current zoom and lower zoom levels:
- * - registers idle tiles in frameState.wantedTiles so that they are not
- *   discarded by the tile queue
- * - enqueues missing tiles
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.source.Tile} tileSource Tile source.
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @param {ol.Extent} extent Extent.
- * @param {number} currentZ Current Z.
- * @param {number} preload Load low resolution tiles up to 'preload' levels.
- * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback.
- * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`.
- * @protected
- * @template T
- */
-ol.renderer.Layer.prototype.manageTilePyramid = function(
-    frameState, tileSource, tileGrid, pixelRatio, projection, extent,
-    currentZ, preload, opt_tileCallback, opt_this) {
-  var tileSourceKey = ol.getUid(tileSource).toString();
-  if (!(tileSourceKey in frameState.wantedTiles)) {
-    frameState.wantedTiles[tileSourceKey] = {};
-  }
-  var wantedTiles = frameState.wantedTiles[tileSourceKey];
-  var tileQueue = frameState.tileQueue;
-  var minZoom = tileGrid.getMinZoom();
-  var tile, tileRange, tileResolution, x, y, z;
-  for (z = currentZ; z >= minZoom; --z) {
-    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
-    tileResolution = tileGrid.getResolution(z);
-    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-        if (currentZ - z <= preload) {
-          tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-          if (tile.getState() == ol.Tile.State.IDLE) {
-            wantedTiles[tile.getKey()] = true;
-            if (!tileQueue.isKeyQueued(tile.getKey())) {
-              tileQueue.enqueue([tile, tileSourceKey,
-                tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
-            }
-          }
-          if (opt_tileCallback !== undefined) {
-            opt_tileCallback.call(opt_this, tile);
-          }
-        } else {
-          tileSource.useTile(z, x, y, projection);
-        }
-      }
-    }
-  }
-};
-
-goog.provide('ol.renderer.canvas.Layer');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.render.Event');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.Immediate');
-goog.require('ol.renderer.Layer');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.Layer}
- * @param {ol.layer.Layer} layer Layer.
- */
-ol.renderer.canvas.Layer = function(layer) {
-
-  ol.renderer.Layer.call(this, layer);
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.transform_ = ol.transform.create();
-
-};
-ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Extent} extent Clip extent.
- * @protected
- */
-ol.renderer.canvas.Layer.prototype.clip = function(context, frameState, extent) {
-  var pixelRatio = frameState.pixelRatio;
-  var width = frameState.size[0] * pixelRatio;
-  var height = frameState.size[1] * pixelRatio;
-  var rotation = frameState.viewState.rotation;
-  var topLeft = ol.extent.getTopLeft(/** @type {ol.Extent} */ (extent));
-  var topRight = ol.extent.getTopRight(/** @type {ol.Extent} */ (extent));
-  var bottomRight = ol.extent.getBottomRight(/** @type {ol.Extent} */ (extent));
-  var bottomLeft = ol.extent.getBottomLeft(/** @type {ol.Extent} */ (extent));
-
-  ol.transform.apply(frameState.coordinateToPixelTransform, topLeft);
-  ol.transform.apply(frameState.coordinateToPixelTransform, topRight);
-  ol.transform.apply(frameState.coordinateToPixelTransform, bottomRight);
-  ol.transform.apply(frameState.coordinateToPixelTransform, bottomLeft);
-
-  context.save();
-  ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
-  context.beginPath();
-  context.moveTo(topLeft[0] * pixelRatio, topLeft[1] * pixelRatio);
-  context.lineTo(topRight[0] * pixelRatio, topRight[1] * pixelRatio);
-  context.lineTo(bottomRight[0] * pixelRatio, bottomRight[1] * pixelRatio);
-  context.lineTo(bottomLeft[0] * pixelRatio, bottomLeft[1] * pixelRatio);
-  context.clip();
-  ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
-};
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @param {CanvasRenderingContext2D} context Context.
- */
-ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {
-
-  this.dispatchPreComposeEvent(context, frameState);
-
-  var image = this.getImage();
-  if (image) {
-
-    // clipped rendering if layer extent is set
-    var extent = layerState.extent;
-    var clipped = extent !== undefined;
-    if (clipped) {
-      this.clip(context, frameState, /** @type {ol.Extent} */ (extent));
-    }
-
-    var imageTransform = this.getImageTransform();
-    // for performance reasons, context.save / context.restore is not used
-    // to save and restore the transformation matrix and the opacity.
-    // see http://jsperf.com/context-save-restore-versus-variable
-    var alpha = context.globalAlpha;
-    context.globalAlpha = layerState.opacity;
-
-    // for performance reasons, context.setTransform is only used
-    // when the view is rotated. see http://jsperf.com/canvas-transform
-    var dx = imageTransform[4];
-    var dy = imageTransform[5];
-    var dw = image.width * imageTransform[0];
-    var dh = image.height * imageTransform[3];
-    context.drawImage(image, 0, 0, +image.width, +image.height,
-        Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
-    context.globalAlpha = alpha;
-
-    if (clipped) {
-      context.restore();
-    }
-  }
-
-  this.dispatchPostComposeEvent(context, frameState);
-
-};
-
-
-/**
- * @param {ol.render.Event.Type} type Event type.
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Transform=} opt_transform Transform.
- * @private
- */
-ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState, opt_transform) {
-  var layer = this.getLayer();
-  if (layer.hasListener(type)) {
-    var width = frameState.size[0] * frameState.pixelRatio;
-    var height = frameState.size[1] * frameState.pixelRatio;
-    var rotation = frameState.viewState.rotation;
-    ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
-    var transform = opt_transform !== undefined ?
-        opt_transform : this.getTransform(frameState, 0);
-    var render = new ol.render.canvas.Immediate(
-        context, frameState.pixelRatio, frameState.extent, transform,
-        frameState.viewState.rotation);
-    var composeEvent = new ol.render.Event(type, render, frameState,
-        context, null);
-    layer.dispatchEvent(composeEvent);
-    ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
-  }
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Transform=} opt_transform Transform.
- * @protected
- */
-ol.renderer.canvas.Layer.prototype.dispatchPostComposeEvent = function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.Event.Type.POSTCOMPOSE, context,
-      frameState, opt_transform);
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Transform=} opt_transform Transform.
- * @protected
- */
-ol.renderer.canvas.Layer.prototype.dispatchPreComposeEvent = function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, context,
-      frameState, opt_transform);
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.Transform=} opt_transform Transform.
- * @protected
- */
-ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = function(context, frameState, opt_transform) {
-  this.dispatchComposeEvent_(ol.render.Event.Type.RENDER, context,
-      frameState, opt_transform);
-};
-
-
-/**
- * @abstract
- * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
- */
-ol.renderer.canvas.Layer.prototype.getImage = function() {};
-
-
-/**
- * @abstract
- * @return {!ol.Transform} Image transform.
- */
-ol.renderer.canvas.Layer.prototype.getImageTransform = function() {};
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {number} offsetX Offset on the x-axis in view coordinates.
- * @protected
- * @return {!ol.Transform} Transform.
- */
-ol.renderer.canvas.Layer.prototype.getTransform = function(frameState, offsetX) {
-  var viewState = frameState.viewState;
-  var pixelRatio = frameState.pixelRatio;
-  var dx1 = pixelRatio * frameState.size[0] / 2;
-  var dy1 = pixelRatio * frameState.size[1] / 2;
-  var sx = pixelRatio / viewState.resolution;
-  var sy = -sx;
-  var angle = -viewState.rotation;
-  var dx2 = -viewState.center[0] + offsetX;
-  var dy2 = -viewState.center[1];
-  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2);
-};
-
-
-/**
- * @abstract
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @return {boolean} whether composeFrame should be called.
- */
-ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {};
-
-
-/**
- * @param {ol.Pixel} pixelOnMap Pixel.
- * @param {ol.Transform} imageTransformInv The transformation matrix
- *        to convert from a map pixel to a canvas pixel.
- * @return {ol.Pixel} The pixel.
- * @protected
- */
-ol.renderer.canvas.Layer.prototype.getPixelOnCanvas = function(pixelOnMap, imageTransformInv) {
-  return ol.transform.apply(imageTransformInv, pixelOnMap.slice());
-};
-
-goog.provide('ol.render.ReplayGroup');
-
-
-/**
- * Base class for replay groups.
- * @constructor
- */
-ol.render.ReplayGroup = function() {};
-
-
-/**
- * @abstract
- * @param {number|undefined} zIndex Z index.
- * @param {ol.render.ReplayType} replayType Replay type.
- * @return {ol.render.VectorContext} Replay.
- */
-ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {};
-
-
-/**
- * @abstract
- * @return {boolean} Is empty.
- */
-ol.render.ReplayGroup.prototype.isEmpty = function() {};
-
-goog.provide('ol.render.canvas.Instruction');
-
-/**
- * @enum {number}
- */
-ol.render.canvas.Instruction = {
-  BEGIN_GEOMETRY: 0,
-  BEGIN_PATH: 1,
-  CIRCLE: 2,
-  CLOSE_PATH: 3,
-  DRAW_IMAGE: 4,
-  DRAW_TEXT: 5,
-  END_GEOMETRY: 6,
-  FILL: 7,
-  MOVE_TO_LINE_TO: 8,
-  SET_FILL_STYLE: 9,
-  SET_STROKE_STYLE: 10,
-  SET_TEXT_STYLE: 11,
-  STROKE: 12
-};
-
-goog.provide('ol.render.canvas.Replay');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.colorlike');
-goog.require('ol.extent');
-goog.require('ol.extent.Relationship');
-goog.require('ol.geom.flat.transform');
-goog.require('ol.has');
-goog.require('ol.obj');
-goog.require('ol.render.VectorContext');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.Instruction');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.render.VectorContext}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @param {boolean} overlaps The replay can have overlapping geometries.
- * @struct
- */
-ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) {
-  ol.render.VectorContext.call(this);
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.tolerance = tolerance;
-
-  /**
-   * @protected
-   * @const
-   * @type {ol.Extent}
-   */
-  this.maxExtent = maxExtent;
-
-  /**
-   * @protected
-   * @type {boolean}
-   */
-  this.overlaps = overlaps;
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.maxLineWidth = 0;
-
-  /**
-   * @protected
-   * @const
-   * @type {number}
-   */
-  this.resolution = resolution;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.alignFill_ = false;
-
-  /**
-   * @private
-   * @type {Array.<*>}
-   */
-  this.beginGeometryInstruction1_ = null;
-
-  /**
-   * @private
-   * @type {Array.<*>}
-   */
-  this.beginGeometryInstruction2_ = null;
-
-  /**
-   * @protected
-   * @type {Array.<*>}
-   */
-  this.instructions = [];
-
-  /**
-   * @protected
-   * @type {Array.<number>}
-   */
-  this.coordinates = [];
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.renderedTransform_ = ol.transform.create();
-
-  /**
-   * @protected
-   * @type {Array.<*>}
-   */
-  this.hitDetectionInstructions = [];
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.pixelCoordinates_ = [];
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.tmpLocalTransform_ = ol.transform.create();
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.resetTransform_ = ol.transform.create();
-};
-ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {boolean} closed Last input coordinate equals first.
- * @param {boolean} skipFirst Skip first coordinate.
- * @protected
- * @return {number} My end.
- */
-ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, closed, skipFirst) {
-
-  var myEnd = this.coordinates.length;
-  var extent = this.getBufferedMaxExtent();
-  if (skipFirst) {
-    offset += stride;
-  }
-  var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
-  var nextCoord = [NaN, NaN];
-  var skipped = true;
-
-  var i, lastRel, nextRel;
-  for (i = offset + stride; i < end; i += stride) {
-    nextCoord[0] = flatCoordinates[i];
-    nextCoord[1] = flatCoordinates[i + 1];
-    nextRel = ol.extent.coordinateRelationship(extent, nextCoord);
-    if (nextRel !== lastRel) {
-      if (skipped) {
-        this.coordinates[myEnd++] = lastCoord[0];
-        this.coordinates[myEnd++] = lastCoord[1];
-      }
-      this.coordinates[myEnd++] = nextCoord[0];
-      this.coordinates[myEnd++] = nextCoord[1];
-      skipped = false;
-    } else if (nextRel === ol.extent.Relationship.INTERSECTING) {
-      this.coordinates[myEnd++] = nextCoord[0];
-      this.coordinates[myEnd++] = nextCoord[1];
-      skipped = false;
-    } else {
-      skipped = true;
-    }
-    lastCoord[0] = nextCoord[0];
-    lastCoord[1] = nextCoord[1];
-    lastRel = nextRel;
-  }
-
-  // Last coordinate equals first or only one point to append:
-  if ((closed && skipped) || i === offset + stride) {
-    this.coordinates[myEnd++] = lastCoord[0];
-    this.coordinates[myEnd++] = lastCoord[1];
-  }
-  return myEnd;
-};
-
-
-/**
- * @protected
- * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) {
-  this.beginGeometryInstruction1_ =
-      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
-  this.instructions.push(this.beginGeometryInstruction1_);
-  this.beginGeometryInstruction2_ =
-      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
-  this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
-};
-
-
-/**
- * @private
- * @param {CanvasRenderingContext2D} context Context.
- * @param {ol.Transform} transform Transform.
- * @param {number} rotation Rotation.
- */
-ol.render.canvas.Replay.prototype.fill_ = function(context, transform, rotation) {
-  if (this.alignFill_) {
-    context.translate(transform[4], transform[5]);
-    context.rotate(rotation);
-  }
-  context.fill();
-  if (this.alignFill_) {
-    context.rotate(-rotation);
-    context.translate(-transform[4], -transform[5]);
-  }
-};
-
-
-/**
- * @private
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Transform} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *     to skip.
- * @param {Array.<*>} instructions Instructions array.
- * @param {function((ol.Feature|ol.render.Feature)): T|undefined}
- *     featureCallback Feature callback.
- * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
- *     extent.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.canvas.Replay.prototype.replay_ = function(
-    context, pixelRatio, transform, viewRotation, skippedFeaturesHash,
-    instructions, featureCallback, opt_hitExtent) {
-  /** @type {Array.<number>} */
-  var pixelCoordinates;
-  if (ol.array.equals(transform, this.renderedTransform_)) {
-    pixelCoordinates = this.pixelCoordinates_;
-  } else {
-    pixelCoordinates = ol.geom.flat.transform.transform2D(
-        this.coordinates, 0, this.coordinates.length, 2,
-        transform, this.pixelCoordinates_);
-    ol.transform.setFromArray(this.renderedTransform_, transform);
-    ol.DEBUG && console.assert(pixelCoordinates === this.pixelCoordinates_,
-        'pixelCoordinates should be the same as this.pixelCoordinates_');
-  }
-  var skipFeatures = !ol.obj.isEmpty(skippedFeaturesHash);
-  var i = 0; // instruction index
-  var ii = instructions.length; // end of instructions
-  var d = 0; // data index
-  var dd; // end of per-instruction data
-  var localTransform = this.tmpLocalTransform_;
-  var resetTransform = this.resetTransform_;
-  var prevX, prevY, roundX, roundY;
-  var pendingFill = 0;
-  var pendingStroke = 0;
-  // When the batch size gets too big, performance decreases. 200 is a good
-  // balance between batch size and number of fill/stroke instructions.
-  var batchSize =
-      this.instructions != instructions || this.overlaps ? 0 : 200;
-  while (i < ii) {
-    var instruction = instructions[i];
-    var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
-    var feature, fill, stroke, text, x, y;
-    switch (type) {
-      case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
-        feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
-        if ((skipFeatures &&
-            skippedFeaturesHash[ol.getUid(feature).toString()]) ||
-            !feature.getGeometry()) {
-          i = /** @type {number} */ (instruction[2]);
-        } else if (opt_hitExtent !== undefined && !ol.extent.intersects(
-            opt_hitExtent, feature.getGeometry().getExtent())) {
-          i = /** @type {number} */ (instruction[2]) + 1;
-        } else {
-          ++i;
-        }
-        break;
-      case ol.render.canvas.Instruction.BEGIN_PATH:
-        if (pendingFill > batchSize) {
-          this.fill_(context, transform, viewRotation);
-          pendingFill = 0;
-        }
-        if (pendingStroke > batchSize) {
-          context.stroke();
-          pendingStroke = 0;
-        }
-        if (!pendingFill && !pendingStroke) {
-          context.beginPath();
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.CIRCLE:
-        ol.DEBUG && console.assert(typeof instruction[1] === 'number',
-            'second instruction should be a number');
-        d = /** @type {number} */ (instruction[1]);
-        var x1 = pixelCoordinates[d];
-        var y1 = pixelCoordinates[d + 1];
-        var x2 = pixelCoordinates[d + 2];
-        var y2 = pixelCoordinates[d + 3];
-        var dx = x2 - x1;
-        var dy = y2 - y1;
-        var r = Math.sqrt(dx * dx + dy * dy);
-        context.moveTo(x1 + r, y1);
-        context.arc(x1, y1, r, 0, 2 * Math.PI, true);
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.CLOSE_PATH:
-        context.closePath();
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.DRAW_IMAGE:
-        ol.DEBUG && console.assert(typeof instruction[1] === 'number',
-            'second instruction should be a number');
-        d = /** @type {number} */ (instruction[1]);
-        ol.DEBUG && console.assert(typeof instruction[2] === 'number',
-            'third instruction should be a number');
-        dd = /** @type {number} */ (instruction[2]);
-        var image =  /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */
-            (instruction[3]);
-        // Remaining arguments in DRAW_IMAGE are in alphabetical order
-        var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio;
-        var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio;
-        var height = /** @type {number} */ (instruction[6]);
-        var opacity = /** @type {number} */ (instruction[7]);
-        var originX = /** @type {number} */ (instruction[8]);
-        var originY = /** @type {number} */ (instruction[9]);
-        var rotateWithView = /** @type {boolean} */ (instruction[10]);
-        var rotation = /** @type {number} */ (instruction[11]);
-        var scale = /** @type {number} */ (instruction[12]);
-        var snapToPixel = /** @type {boolean} */ (instruction[13]);
-        var width = /** @type {number} */ (instruction[14]);
-        if (rotateWithView) {
-          rotation += viewRotation;
-        }
-        for (; d < dd; d += 2) {
-          x = pixelCoordinates[d] - anchorX;
-          y = pixelCoordinates[d + 1] - anchorY;
-          if (snapToPixel) {
-            x = Math.round(x);
-            y = Math.round(y);
-          }
-          if (scale != 1 || rotation !== 0) {
-            var centerX = x + anchorX;
-            var centerY = y + anchorY;
-            ol.transform.compose(localTransform,
-                centerX, centerY, scale, scale, rotation, -centerX, -centerY);
-            context.setTransform.apply(context, localTransform);
-          }
-          var alpha = context.globalAlpha;
-          if (opacity != 1) {
-            context.globalAlpha = alpha * opacity;
-          }
-
-          var w = (width + originX > image.width) ? image.width - originX : width;
-          var h = (height + originY > image.height) ? image.height - originY : height;
-
-          context.drawImage(image, originX, originY, w, h,
-              x, y, w * pixelRatio, h * pixelRatio);
-
-          if (opacity != 1) {
-            context.globalAlpha = alpha;
-          }
-          if (scale != 1 || rotation !== 0) {
-            context.setTransform.apply(context, resetTransform);
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.DRAW_TEXT:
-        ol.DEBUG && console.assert(typeof instruction[1] === 'number',
-            '2nd instruction should be a number');
-        d = /** @type {number} */ (instruction[1]);
-        ol.DEBUG && console.assert(typeof instruction[2] === 'number',
-            '3rd instruction should be a number');
-        dd = /** @type {number} */ (instruction[2]);
-        ol.DEBUG && console.assert(typeof instruction[3] === 'string',
-            '4th instruction should be a string');
-        text = /** @type {string} */ (instruction[3]);
-        ol.DEBUG && console.assert(typeof instruction[4] === 'number',
-            '5th instruction should be a number');
-        var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio;
-        ol.DEBUG && console.assert(typeof instruction[5] === 'number',
-            '6th instruction should be a number');
-        var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio;
-        ol.DEBUG && console.assert(typeof instruction[6] === 'number',
-            '7th instruction should be a number');
-        rotation = /** @type {number} */ (instruction[6]);
-        ol.DEBUG && console.assert(typeof instruction[7] === 'number',
-            '8th instruction should be a number');
-        scale = /** @type {number} */ (instruction[7]) * pixelRatio;
-        ol.DEBUG && console.assert(typeof instruction[8] === 'boolean',
-            '9th instruction should be a boolean');
-        fill = /** @type {boolean} */ (instruction[8]);
-        ol.DEBUG && console.assert(typeof instruction[9] === 'boolean',
-            '10th instruction should be a boolean');
-        stroke = /** @type {boolean} */ (instruction[9]);
-        rotateWithView = /** @type {boolean} */ (instruction[10]);
-        if (rotateWithView) {
-          rotation += viewRotation;
-        }
-        for (; d < dd; d += 2) {
-          x = pixelCoordinates[d] + offsetX;
-          y = pixelCoordinates[d + 1] + offsetY;
-          if (scale != 1 || rotation !== 0) {
-            ol.transform.compose(localTransform, x, y, scale, scale, rotation, -x, -y);
-            context.setTransform.apply(context, localTransform);
-          }
-
-          // Support multiple lines separated by \n
-          var lines = text.split('\n');
-          var numLines = lines.length;
-          var fontSize, lineY;
-          if (numLines > 1) {
-            // Estimate line height using width of capital M, and add padding
-            fontSize = Math.round(context.measureText('M').width * 1.5);
-            lineY = y - (((numLines - 1) / 2) * fontSize);
-          } else {
-            // No need to calculate line height/offset for a single line
-            fontSize = 0;
-            lineY = y;
-          }
-
-          for (var lineIndex = 0; lineIndex < numLines; lineIndex++) {
-            var line = lines[lineIndex];
-            if (stroke) {
-              context.strokeText(line, x, lineY);
-            }
-            if (fill) {
-              context.fillText(line, x, lineY);
-            }
-
-            // Move next line down by fontSize px
-            lineY = lineY + fontSize;
-          }
-
-          if (scale != 1 || rotation !== 0) {
-            context.setTransform.apply(context, resetTransform);
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.END_GEOMETRY:
-        if (featureCallback !== undefined) {
-          feature =
-              /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
-          var result = featureCallback(feature);
-          if (result) {
-            return result;
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.FILL:
-        if (batchSize) {
-          pendingFill++;
-        } else {
-          this.fill_(context, transform, viewRotation);
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
-        ol.DEBUG && console.assert(typeof instruction[1] === 'number',
-            '2nd instruction should be a number');
-        d = /** @type {number} */ (instruction[1]);
-        ol.DEBUG && console.assert(typeof instruction[2] === 'number',
-            '3rd instruction should be a number');
-        dd = /** @type {number} */ (instruction[2]);
-        x = pixelCoordinates[d];
-        y = pixelCoordinates[d + 1];
-        roundX = (x + 0.5) | 0;
-        roundY = (y + 0.5) | 0;
-        if (roundX !== prevX || roundY !== prevY) {
-          context.moveTo(x, y);
-          prevX = roundX;
-          prevY = roundY;
-        }
-        for (d += 2; d < dd; d += 2) {
-          x = pixelCoordinates[d];
-          y = pixelCoordinates[d + 1];
-          roundX = (x + 0.5) | 0;
-          roundY = (y + 0.5) | 0;
-          if (d == dd - 2 || roundX !== prevX || roundY !== prevY) {
-            context.lineTo(x, y);
-            prevX = roundX;
-            prevY = roundY;
-          }
-        }
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_FILL_STYLE:
-        ol.DEBUG && console.assert(
-            ol.colorlike.isColorLike(instruction[1]),
-            '2nd instruction should be a string, ' +
-            'CanvasPattern, or CanvasGradient');
-        this.alignFill_ = instruction[2];
-
-        if (pendingFill) {
-          this.fill_(context, transform, viewRotation);
-          pendingFill = 0;
-        }
-
-        context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]);
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
-        ol.DEBUG && console.assert(ol.colorlike.isColorLike(instruction[1]),
-            '2nd instruction should be a string, CanvasPattern, or CanvasGradient');
-        ol.DEBUG && console.assert(typeof instruction[2] === 'number',
-            '3rd instruction should be a number');
-        ol.DEBUG && console.assert(typeof instruction[3] === 'string',
-            '4rd instruction should be a string');
-        ol.DEBUG && console.assert(typeof instruction[4] === 'string',
-            '5th instruction should be a string');
-        ol.DEBUG && console.assert(typeof instruction[5] === 'number',
-            '6th instruction should be a number');
-        ol.DEBUG && console.assert(instruction[6],
-            '7th instruction should not be null');
-        var usePixelRatio = instruction[7] !== undefined ?
-            instruction[7] : true;
-        var lineWidth = /** @type {number} */ (instruction[2]);
-        if (pendingStroke) {
-          context.stroke();
-          pendingStroke = 0;
-        }
-        context.strokeStyle = /** @type {ol.ColorLike} */ (instruction[1]);
-        context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth;
-        context.lineCap = /** @type {string} */ (instruction[3]);
-        context.lineJoin = /** @type {string} */ (instruction[4]);
-        context.miterLimit = /** @type {number} */ (instruction[5]);
-        if (ol.has.CANVAS_LINE_DASH) {
-          context.setLineDash(/** @type {Array.<number>} */ (instruction[6]));
-        }
-        prevX = NaN;
-        prevY = NaN;
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.SET_TEXT_STYLE:
-        ol.DEBUG && console.assert(typeof instruction[1] === 'string',
-            '2nd instruction should be a string');
-        ol.DEBUG && console.assert(typeof instruction[2] === 'string',
-            '3rd instruction should be a string');
-        ol.DEBUG && console.assert(typeof instruction[3] === 'string',
-            '4th instruction should be a string');
-        context.font = /** @type {string} */ (instruction[1]);
-        context.textAlign = /** @type {string} */ (instruction[2]);
-        context.textBaseline = /** @type {string} */ (instruction[3]);
-        ++i;
-        break;
-      case ol.render.canvas.Instruction.STROKE:
-        if (batchSize) {
-          pendingStroke++;
-        } else {
-          context.stroke();
-        }
-        ++i;
-        break;
-      default:
-        ol.DEBUG && console.assert(false, 'Unknown canvas render instruction');
-        ++i; // consume the instruction anyway, to avoid an infinite loop
-        break;
-    }
-  }
-  if (pendingFill) {
-    this.fill_(context, transform, viewRotation);
-  }
-  if (pendingStroke) {
-    context.stroke();
-  }
-  // assert that all instructions were consumed
-  ol.DEBUG && console.assert(i == instructions.length,
-      'all instructions should be consumed');
-  return undefined;
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Transform} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *     to skip.
- */
-ol.render.canvas.Replay.prototype.replay = function(
-    context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
-  var instructions = this.instructions;
-  this.replay_(context, pixelRatio, transform, viewRotation,
-      skippedFeaturesHash, instructions, undefined, undefined);
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {ol.Transform} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *     to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T=} opt_featureCallback
- *     Feature callback.
- * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
- *     extent.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.canvas.Replay.prototype.replayHitDetection = function(
-    context, transform, viewRotation, skippedFeaturesHash,
-    opt_featureCallback, opt_hitExtent) {
-  var instructions = this.hitDetectionInstructions;
-  return this.replay_(context, 1, transform, viewRotation,
-      skippedFeaturesHash, instructions, opt_featureCallback, opt_hitExtent);
-};
-
-
-/**
- * Reverse the hit detection instructions.
- */
-ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions = function() {
-  var hitDetectionInstructions = this.hitDetectionInstructions;
-  // step 1 - reverse array
-  hitDetectionInstructions.reverse();
-  // step 2 - reverse instructions within geometry blocks
-  var i;
-  var n = hitDetectionInstructions.length;
-  var instruction;
-  var type;
-  var begin = -1;
-  for (i = 0; i < n; ++i) {
-    instruction = hitDetectionInstructions[i];
-    type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
-    if (type == ol.render.canvas.Instruction.END_GEOMETRY) {
-      ol.DEBUG && console.assert(begin == -1, 'begin should be -1');
-      begin = i;
-    } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) {
-      instruction[2] = i;
-      ol.DEBUG && console.assert(begin >= 0,
-          'begin should be larger than or equal to 0');
-      ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i);
-      begin = -1;
-    }
-  }
-};
-
-
-/**
- * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- */
-ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) {
-  ol.DEBUG && console.assert(this.beginGeometryInstruction1_,
-      'this.beginGeometryInstruction1_ should not be null');
-  this.beginGeometryInstruction1_[2] = this.instructions.length;
-  this.beginGeometryInstruction1_ = null;
-  ol.DEBUG && console.assert(this.beginGeometryInstruction2_,
-      'this.beginGeometryInstruction2_ should not be null');
-  this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length;
-  this.beginGeometryInstruction2_ = null;
-  var endGeometryInstruction =
-      [ol.render.canvas.Instruction.END_GEOMETRY, feature];
-  this.instructions.push(endGeometryInstruction);
-  this.hitDetectionInstructions.push(endGeometryInstruction);
-};
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.render.canvas.Replay.prototype.finish = ol.nullFunction;
-
-
-/**
- * Get the buffered rendering extent.  Rendering will be clipped to the extent
- * provided to the constructor.  To account for symbolizers that may intersect
- * this extent, we calculate a buffered extent (e.g. based on stroke width).
- * @return {ol.Extent} The buffered rendering extent.
- * @protected
- */
-ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() {
-  return this.maxExtent;
-};
-
-goog.provide('ol.render.canvas.ImageReplay');
-
-goog.require('ol');
-goog.require('ol.render.canvas.Instruction');
-goog.require('ol.render.canvas.Replay');
-
-
-/**
- * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @param {boolean} overlaps The replay can have overlapping geometries.
- * @struct
- */
-ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) {
-  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
-   */
-  this.hitDetectionImage_ = null;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.anchorX_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.anchorY_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.height_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.opacity_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.originX_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.originY_ = undefined;
-
-  /**
-   * @private
-   * @type {boolean|undefined}
-   */
-  this.rotateWithView_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.rotation_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.scale_ = undefined;
-
-  /**
-   * @private
-   * @type {boolean|undefined}
-   */
-  this.snapToPixel_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.width_ = undefined;
-
-};
-ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay);
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @private
- * @return {number} My end.
- */
-ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
-  return this.appendFlatCoordinates(
-      flatCoordinates, offset, end, stride, false, false);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) {
-  if (!this.image_) {
-    return;
-  }
-  ol.DEBUG && console.assert(this.anchorX_ !== undefined,
-      'this.anchorX_ should be defined');
-  ol.DEBUG && console.assert(this.anchorY_ !== undefined,
-      'this.anchorY_ should be defined');
-  ol.DEBUG && console.assert(this.height_ !== undefined,
-      'this.height_ should be defined');
-  ol.DEBUG && console.assert(this.opacity_ !== undefined,
-      'this.opacity_ should be defined');
-  ol.DEBUG && console.assert(this.originX_ !== undefined,
-      'this.originX_ should be defined');
-  ol.DEBUG && console.assert(this.originY_ !== undefined,
-      'this.originY_ should be defined');
-  ol.DEBUG && console.assert(this.rotateWithView_ !== undefined,
-      'this.rotateWithView_ should be defined');
-  ol.DEBUG && console.assert(this.rotation_ !== undefined,
-      'this.rotation_ should be defined');
-  ol.DEBUG && console.assert(this.scale_ !== undefined,
-      'this.scale_ should be defined');
-  ol.DEBUG && console.assert(this.width_ !== undefined,
-      'this.width_ should be defined');
-  this.beginGeometry(pointGeometry, feature);
-  var flatCoordinates = pointGeometry.getFlatCoordinates();
-  var stride = pointGeometry.getStride();
-  var myBegin = this.coordinates.length;
-  var myEnd = this.drawCoordinates_(
-      flatCoordinates, 0, flatCoordinates.length, stride);
-  this.instructions.push([
-    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
-    // Remaining arguments to DRAW_IMAGE are in alphabetical order
-    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
-    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
-    this.scale_, this.snapToPixel_, this.width_
-  ]);
-  this.hitDetectionInstructions.push([
-    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
-    this.hitDetectionImage_,
-    // Remaining arguments to DRAW_IMAGE are in alphabetical order
-    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
-    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
-    this.scale_, this.snapToPixel_, this.width_
-  ]);
-  this.endGeometry(pointGeometry, feature);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) {
-  if (!this.image_) {
-    return;
-  }
-  ol.DEBUG && console.assert(this.anchorX_ !== undefined,
-      'this.anchorX_ should be defined');
-  ol.DEBUG && console.assert(this.anchorY_ !== undefined,
-      'this.anchorY_ should be defined');
-  ol.DEBUG && console.assert(this.height_ !== undefined,
-      'this.height_ should be defined');
-  ol.DEBUG && console.assert(this.opacity_ !== undefined,
-      'this.opacity_ should be defined');
-  ol.DEBUG && console.assert(this.originX_ !== undefined,
-      'this.originX_ should be defined');
-  ol.DEBUG && console.assert(this.originY_ !== undefined,
-      'this.originY_ should be defined');
-  ol.DEBUG && console.assert(this.rotateWithView_ !== undefined,
-      'this.rotateWithView_ should be defined');
-  ol.DEBUG && console.assert(this.rotation_ !== undefined,
-      'this.rotation_ should be defined');
-  ol.DEBUG && console.assert(this.scale_ !== undefined,
-      'this.scale_ should be defined');
-  ol.DEBUG && console.assert(this.width_ !== undefined,
-      'this.width_ should be defined');
-  this.beginGeometry(multiPointGeometry, feature);
-  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
-  var stride = multiPointGeometry.getStride();
-  var myBegin = this.coordinates.length;
-  var myEnd = this.drawCoordinates_(
-      flatCoordinates, 0, flatCoordinates.length, stride);
-  this.instructions.push([
-    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
-    // Remaining arguments to DRAW_IMAGE are in alphabetical order
-    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
-    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
-    this.scale_, this.snapToPixel_, this.width_
-  ]);
-  this.hitDetectionInstructions.push([
-    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
-    this.hitDetectionImage_,
-    // Remaining arguments to DRAW_IMAGE are in alphabetical order
-    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
-    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
-    this.scale_, this.snapToPixel_, this.width_
-  ]);
-  this.endGeometry(multiPointGeometry, feature);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.ImageReplay.prototype.finish = function() {
-  this.reverseHitDetectionInstructions();
-  // FIXME this doesn't really protect us against further calls to draw*Geometry
-  this.anchorX_ = undefined;
-  this.anchorY_ = undefined;
-  this.hitDetectionImage_ = null;
-  this.image_ = null;
-  this.height_ = undefined;
-  this.scale_ = undefined;
-  this.opacity_ = undefined;
-  this.originX_ = undefined;
-  this.originY_ = undefined;
-  this.rotateWithView_ = undefined;
-  this.rotation_ = undefined;
-  this.snapToPixel_ = undefined;
-  this.width_ = undefined;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
-  ol.DEBUG && console.assert(imageStyle, 'imageStyle should not be null');
-  var anchor = imageStyle.getAnchor();
-  ol.DEBUG && console.assert(anchor, 'anchor should not be null');
-  var size = imageStyle.getSize();
-  ol.DEBUG && console.assert(size, 'size should not be null');
-  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
-  ol.DEBUG && console.assert(hitDetectionImage,
-      'hitDetectionImage should not be null');
-  var image = imageStyle.getImage(1);
-  ol.DEBUG && console.assert(image, 'image should not be null');
-  var origin = imageStyle.getOrigin();
-  ol.DEBUG && console.assert(origin, 'origin should not be null');
-  this.anchorX_ = anchor[0];
-  this.anchorY_ = anchor[1];
-  this.hitDetectionImage_ = hitDetectionImage;
-  this.image_ = image;
-  this.height_ = size[1];
-  this.opacity_ = imageStyle.getOpacity();
-  this.originX_ = origin[0];
-  this.originY_ = origin[1];
-  this.rotateWithView_ = imageStyle.getRotateWithView();
-  this.rotation_ = imageStyle.getRotation();
-  this.scale_ = imageStyle.getScale();
-  this.snapToPixel_ = imageStyle.getSnapToPixel();
-  this.width_ = size[0];
-};
-
-goog.provide('ol.render.canvas.LineStringReplay');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.colorlike');
-goog.require('ol.extent');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.Instruction');
-goog.require('ol.render.canvas.Replay');
-
-
-/**
- * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @param {boolean} overlaps The replay can have overlapping geometries.
- * @struct
- */
-ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) {
-
-  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.bufferedMaxExtent_ = null;
-
-  /**
-   * @private
-   * @type {{currentStrokeStyle: (ol.ColorLike|undefined),
-   *         currentLineCap: (string|undefined),
-   *         currentLineDash: Array.<number>,
-   *         currentLineJoin: (string|undefined),
-   *         currentLineWidth: (number|undefined),
-   *         currentMiterLimit: (number|undefined),
-   *         lastStroke: number,
-   *         strokeStyle: (ol.ColorLike|undefined),
-   *         lineCap: (string|undefined),
-   *         lineDash: Array.<number>,
-   *         lineJoin: (string|undefined),
-   *         lineWidth: (number|undefined),
-   *         miterLimit: (number|undefined)}|null}
-   */
-  this.state_ = {
-    currentStrokeStyle: undefined,
-    currentLineCap: undefined,
-    currentLineDash: null,
-    currentLineJoin: undefined,
-    currentLineWidth: undefined,
-    currentMiterLimit: undefined,
-    lastStroke: 0,
-    strokeStyle: undefined,
-    lineCap: undefined,
-    lineDash: null,
-    lineJoin: undefined,
-    lineWidth: undefined,
-    miterLimit: undefined
-  };
-
-};
-ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay);
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @private
- * @return {number} end.
- */
-ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = function(flatCoordinates, offset, end, stride) {
-  var myBegin = this.coordinates.length;
-  var myEnd = this.appendFlatCoordinates(
-      flatCoordinates, offset, end, stride, false, false);
-  var moveToLineToInstruction =
-      [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
-  this.instructions.push(moveToLineToInstruction);
-  this.hitDetectionInstructions.push(moveToLineToInstruction);
-  return end;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() {
-  if (!this.bufferedMaxExtent_) {
-    this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent);
-    if (this.maxLineWidth > 0) {
-      var width = this.resolution * (this.maxLineWidth + 1) / 2;
-      ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
-    }
-  }
-  return this.bufferedMaxExtent_;
-};
-
-
-/**
- * @private
- */
-ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() {
-  var state = this.state_;
-  var strokeStyle = state.strokeStyle;
-  var lineCap = state.lineCap;
-  var lineDash = state.lineDash;
-  var lineJoin = state.lineJoin;
-  var lineWidth = state.lineWidth;
-  var miterLimit = state.miterLimit;
-  ol.DEBUG && console.assert(strokeStyle !== undefined,
-      'strokeStyle should be defined');
-  ol.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined');
-  ol.DEBUG && console.assert(lineDash, 'lineDash should not be null');
-  ol.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined');
-  ol.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined');
-  ol.DEBUG && console.assert(miterLimit !== undefined, 'miterLimit should be defined');
-  if (state.currentStrokeStyle != strokeStyle ||
-      state.currentLineCap != lineCap ||
-      !ol.array.equals(state.currentLineDash, lineDash) ||
-      state.currentLineJoin != lineJoin ||
-      state.currentLineWidth != lineWidth ||
-      state.currentMiterLimit != miterLimit) {
-    if (state.lastStroke != this.coordinates.length) {
-      this.instructions.push(
-          [ol.render.canvas.Instruction.STROKE]);
-      state.lastStroke = this.coordinates.length;
-    }
-    this.instructions.push(
-        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-         strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash],
-        [ol.render.canvas.Instruction.BEGIN_PATH]);
-    state.currentStrokeStyle = strokeStyle;
-    state.currentLineCap = lineCap;
-    state.currentLineDash = lineDash;
-    state.currentLineJoin = lineJoin;
-    state.currentLineWidth = lineWidth;
-    state.currentMiterLimit = miterLimit;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) {
-  var state = this.state_;
-  ol.DEBUG && console.assert(state, 'state should not be null');
-  var strokeStyle = state.strokeStyle;
-  var lineWidth = state.lineWidth;
-  if (strokeStyle === undefined || lineWidth === undefined) {
-    return;
-  }
-  this.setStrokeStyle_();
-  this.beginGeometry(lineStringGeometry, feature);
-  this.hitDetectionInstructions.push(
-      [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-       state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
-       state.miterLimit, state.lineDash],
-      [ol.render.canvas.Instruction.BEGIN_PATH]);
-  var flatCoordinates = lineStringGeometry.getFlatCoordinates();
-  var stride = lineStringGeometry.getStride();
-  this.drawFlatCoordinates_(
-      flatCoordinates, 0, flatCoordinates.length, stride);
-  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
-  this.endGeometry(lineStringGeometry, feature);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {
-  var state = this.state_;
-  ol.DEBUG && console.assert(state, 'state should not be null');
-  var strokeStyle = state.strokeStyle;
-  var lineWidth = state.lineWidth;
-  if (strokeStyle === undefined || lineWidth === undefined) {
-    return;
-  }
-  this.setStrokeStyle_();
-  this.beginGeometry(multiLineStringGeometry, feature);
-  this.hitDetectionInstructions.push(
-      [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-       state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
-       state.miterLimit, state.lineDash],
-      [ol.render.canvas.Instruction.BEGIN_PATH]);
-  var ends = multiLineStringGeometry.getEnds();
-  var flatCoordinates = multiLineStringGeometry.getFlatCoordinates();
-  var stride = multiLineStringGeometry.getStride();
-  var offset = 0;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    offset = this.drawFlatCoordinates_(
-        flatCoordinates, offset, ends[i], stride);
-  }
-  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
-  this.endGeometry(multiLineStringGeometry, feature);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.LineStringReplay.prototype.finish = function() {
-  var state = this.state_;
-  ol.DEBUG && console.assert(state, 'state should not be null');
-  if (state.lastStroke != this.coordinates.length) {
-    this.instructions.push([ol.render.canvas.Instruction.STROKE]);
-  }
-  this.reverseHitDetectionInstructions();
-  this.state_ = null;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
-  ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null');
-  ol.DEBUG && console.assert(!fillStyle, 'fillStyle should be null');
-  ol.DEBUG && console.assert(strokeStyle, 'strokeStyle should not be null');
-  var strokeStyleColor = strokeStyle.getColor();
-  this.state_.strokeStyle = ol.colorlike.asColorLike(strokeStyleColor ?
-      strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
-  var strokeStyleLineCap = strokeStyle.getLineCap();
-  this.state_.lineCap = strokeStyleLineCap !== undefined ?
-      strokeStyleLineCap : ol.render.canvas.defaultLineCap;
-  var strokeStyleLineDash = strokeStyle.getLineDash();
-  this.state_.lineDash = strokeStyleLineDash ?
-      strokeStyleLineDash : ol.render.canvas.defaultLineDash;
-  var strokeStyleLineJoin = strokeStyle.getLineJoin();
-  this.state_.lineJoin = strokeStyleLineJoin !== undefined ?
-      strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
-  var strokeStyleWidth = strokeStyle.getWidth();
-  this.state_.lineWidth = strokeStyleWidth !== undefined ?
-      strokeStyleWidth : ol.render.canvas.defaultLineWidth;
-  var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
-  this.state_.miterLimit = strokeStyleMiterLimit !== undefined ?
-      strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-
-  if (this.state_.lineWidth > this.maxLineWidth) {
-    this.maxLineWidth = this.state_.lineWidth;
-    // invalidate the buffered max extent cache
-    this.bufferedMaxExtent_ = null;
-  }
-};
-
-goog.provide('ol.render.canvas.PolygonReplay');
-
-goog.require('ol');
-goog.require('ol.color');
-goog.require('ol.colorlike');
-goog.require('ol.extent');
-goog.require('ol.geom.flat.simplify');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.Instruction');
-goog.require('ol.render.canvas.Replay');
-
-
-/**
- * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @param {boolean} overlaps The replay can have overlapping geometries.
- * @struct
- */
-ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, overlaps) {
-
-  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.bufferedMaxExtent_ = null;
-
-  /**
-   * @private
-   * @type {{currentFillStyle: (ol.ColorLike|undefined),
-   *         currentStrokeStyle: (ol.ColorLike|undefined),
-   *         currentLineCap: (string|undefined),
-   *         currentLineDash: Array.<number>,
-   *         currentLineJoin: (string|undefined),
-   *         currentLineWidth: (number|undefined),
-   *         currentMiterLimit: (number|undefined),
-   *         fillStyle: (ol.ColorLike|undefined),
-   *         strokeStyle: (ol.ColorLike|undefined),
-   *         lineCap: (string|undefined),
-   *         lineDash: Array.<number>,
-   *         lineJoin: (string|undefined),
-   *         lineWidth: (number|undefined),
-   *         miterLimit: (number|undefined)}|null}
-   */
-  this.state_ = {
-    currentFillStyle: undefined,
-    currentStrokeStyle: undefined,
-    currentLineCap: undefined,
-    currentLineDash: null,
-    currentLineJoin: undefined,
-    currentLineWidth: undefined,
-    currentMiterLimit: undefined,
-    fillStyle: undefined,
-    strokeStyle: undefined,
-    lineCap: undefined,
-    lineDash: null,
-    lineJoin: undefined,
-    lineWidth: undefined,
-    miterLimit: undefined
-  };
-
-};
-ol.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @private
- * @return {number} End.
- */
-ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCoordinates, offset, ends, stride) {
-  var state = this.state_;
-  var fill = state.fillStyle !== undefined;
-  var stroke = state.strokeStyle != undefined;
-  var numEnds = ends.length;
-  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
-  this.instructions.push(beginPathInstruction);
-  this.hitDetectionInstructions.push(beginPathInstruction);
-  for (var i = 0; i < numEnds; ++i) {
-    var end = ends[i];
-    var myBegin = this.coordinates.length;
-    var myEnd = this.appendFlatCoordinates(
-        flatCoordinates, offset, end, stride, true, !stroke);
-    var moveToLineToInstruction =
-        [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
-    this.instructions.push(moveToLineToInstruction);
-    this.hitDetectionInstructions.push(moveToLineToInstruction);
-    if (stroke) {
-      // Performance optimization: only call closePath() when we have a stroke.
-      // Otherwise the ring is closed already (see appendFlatCoordinates above).
-      var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH];
-      this.instructions.push(closePathInstruction);
-      this.hitDetectionInstructions.push(closePathInstruction);
-    }
-    offset = end;
-  }
-  var fillInstruction = [ol.render.canvas.Instruction.FILL];
-  this.hitDetectionInstructions.push(fillInstruction);
-  if (fill) {
-    this.instructions.push(fillInstruction);
-  }
-  if (stroke) {
-    ol.DEBUG && console.assert(state.lineWidth !== undefined,
-        'state.lineWidth should be defined');
-    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
-    this.instructions.push(strokeInstruction);
-    this.hitDetectionInstructions.push(strokeInstruction);
-  }
-  return offset;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, feature) {
-  var state = this.state_;
-  ol.DEBUG && console.assert(state, 'state should not be null');
-  var fillStyle = state.fillStyle;
-  var strokeStyle = state.strokeStyle;
-  if (fillStyle === undefined && strokeStyle === undefined) {
-    return;
-  }
-  if (strokeStyle !== undefined) {
-    ol.DEBUG && console.assert(state.lineWidth !== undefined,
-        'state.lineWidth should be defined');
-  }
-  this.setFillStrokeStyles_();
-  this.beginGeometry(circleGeometry, feature);
-  // always fill the circle for hit detection
-  this.hitDetectionInstructions.push(
-      [ol.render.canvas.Instruction.SET_FILL_STYLE,
-       ol.color.asString(ol.render.canvas.defaultFillStyle)]);
-  if (state.strokeStyle !== undefined) {
-    this.hitDetectionInstructions.push(
-        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
-         state.miterLimit, state.lineDash]);
-  }
-  var flatCoordinates = circleGeometry.getFlatCoordinates();
-  var stride = circleGeometry.getStride();
-  var myBegin = this.coordinates.length;
-  this.appendFlatCoordinates(
-      flatCoordinates, 0, flatCoordinates.length, stride, false, false);
-  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
-  var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin];
-  this.instructions.push(beginPathInstruction, circleInstruction);
-  this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction);
-  var fillInstruction = [ol.render.canvas.Instruction.FILL];
-  this.hitDetectionInstructions.push(fillInstruction);
-  if (state.fillStyle !== undefined) {
-    this.instructions.push(fillInstruction);
-  }
-  if (state.strokeStyle !== undefined) {
-    ol.DEBUG && console.assert(state.lineWidth !== undefined,
-        'state.lineWidth should be defined');
-    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
-    this.instructions.push(strokeInstruction);
-    this.hitDetectionInstructions.push(strokeInstruction);
-  }
-  this.endGeometry(circleGeometry, feature);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) {
-  var state = this.state_;
-  ol.DEBUG && console.assert(state, 'state should not be null');
-  var strokeStyle = state.strokeStyle;
-  ol.DEBUG && console.assert(state.fillStyle !== undefined || strokeStyle !== undefined,
-      'fillStyle or strokeStyle should be defined');
-  if (strokeStyle !== undefined) {
-    ol.DEBUG && console.assert(state.lineWidth !== undefined,
-        'state.lineWidth should be defined');
-  }
-  this.setFillStrokeStyles_();
-  this.beginGeometry(polygonGeometry, feature);
-  // always fill the polygon for hit detection
-  this.hitDetectionInstructions.push(
-      [ol.render.canvas.Instruction.SET_FILL_STYLE,
-       ol.color.asString(ol.render.canvas.defaultFillStyle)]);
-  if (state.strokeStyle !== undefined) {
-    this.hitDetectionInstructions.push(
-        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
-         state.miterLimit, state.lineDash]);
-  }
-  var ends = polygonGeometry.getEnds();
-  var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
-  var stride = polygonGeometry.getStride();
-  this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
-  this.endGeometry(polygonGeometry, feature);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {
-  var state = this.state_;
-  ol.DEBUG && console.assert(state, 'state should not be null');
-  var fillStyle = state.fillStyle;
-  var strokeStyle = state.strokeStyle;
-  if (fillStyle === undefined && strokeStyle === undefined) {
-    return;
-  }
-  if (strokeStyle !== undefined) {
-    ol.DEBUG && console.assert(state.lineWidth !== undefined,
-        'state.lineWidth should be defined');
-  }
-  this.setFillStrokeStyles_();
-  this.beginGeometry(multiPolygonGeometry, feature);
-  // always fill the multi-polygon for hit detection
-  this.hitDetectionInstructions.push(
-      [ol.render.canvas.Instruction.SET_FILL_STYLE,
-       ol.color.asString(ol.render.canvas.defaultFillStyle)]);
-  if (state.strokeStyle !== undefined) {
-    this.hitDetectionInstructions.push(
-        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
-         state.miterLimit, state.lineDash]);
-  }
-  var endss = multiPolygonGeometry.getEndss();
-  var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates();
-  var stride = multiPolygonGeometry.getStride();
-  var offset = 0;
-  var i, ii;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    offset = this.drawFlatCoordinatess_(
-        flatCoordinates, offset, endss[i], stride);
-  }
-  this.endGeometry(multiPolygonGeometry, feature);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.PolygonReplay.prototype.finish = function() {
-  ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null');
-  this.reverseHitDetectionInstructions();
-  this.state_ = null;
-  // We want to preserve topology when drawing polygons.  Polygons are
-  // simplified using quantization and point elimination. However, we might
-  // have received a mix of quantized and non-quantized geometries, so ensure
-  // that all are quantized by quantizing all coordinates in the batch.
-  var tolerance = this.tolerance;
-  if (tolerance !== 0) {
-    var coordinates = this.coordinates;
-    var i, ii;
-    for (i = 0, ii = coordinates.length; i < ii; ++i) {
-      coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance);
-    }
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() {
-  if (!this.bufferedMaxExtent_) {
-    this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent);
-    if (this.maxLineWidth > 0) {
-      var width = this.resolution * (this.maxLineWidth + 1) / 2;
-      ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
-    }
-  }
-  return this.bufferedMaxExtent_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
-  ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null');
-  ol.DEBUG && console.assert(fillStyle || strokeStyle,
-      'fillStyle or strokeStyle should not be null');
-  var state = this.state_;
-  if (fillStyle) {
-    var fillStyleColor = fillStyle.getColor();
-    state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ?
-        fillStyleColor : ol.render.canvas.defaultFillStyle);
-  } else {
-    state.fillStyle = undefined;
-  }
-  if (strokeStyle) {
-    var strokeStyleColor = strokeStyle.getColor();
-    state.strokeStyle = ol.colorlike.asColorLike(strokeStyleColor ?
-        strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
-    var strokeStyleLineCap = strokeStyle.getLineCap();
-    state.lineCap = strokeStyleLineCap !== undefined ?
-        strokeStyleLineCap : ol.render.canvas.defaultLineCap;
-    var strokeStyleLineDash = strokeStyle.getLineDash();
-    state.lineDash = strokeStyleLineDash ?
-        strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
-    var strokeStyleLineJoin = strokeStyle.getLineJoin();
-    state.lineJoin = strokeStyleLineJoin !== undefined ?
-        strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
-    var strokeStyleWidth = strokeStyle.getWidth();
-    state.lineWidth = strokeStyleWidth !== undefined ?
-        strokeStyleWidth : ol.render.canvas.defaultLineWidth;
-    var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
-    state.miterLimit = strokeStyleMiterLimit !== undefined ?
-        strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-
-    if (state.lineWidth > this.maxLineWidth) {
-      this.maxLineWidth = state.lineWidth;
-      // invalidate the buffered max extent cache
-      this.bufferedMaxExtent_ = null;
-    }
-  } else {
-    state.strokeStyle = undefined;
-    state.lineCap = undefined;
-    state.lineDash = null;
-    state.lineJoin = undefined;
-    state.lineWidth = undefined;
-    state.miterLimit = undefined;
-  }
-};
-
-
-/**
- * @private
- */
-ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() {
-  var state = this.state_;
-  var fillStyle = state.fillStyle;
-  var strokeStyle = state.strokeStyle;
-  var lineCap = state.lineCap;
-  var lineDash = state.lineDash;
-  var lineJoin = state.lineJoin;
-  var lineWidth = state.lineWidth;
-  var miterLimit = state.miterLimit;
-  if (fillStyle !== undefined && state.currentFillStyle != fillStyle) {
-    this.instructions.push(
-        [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle, typeof fillStyle != 'string']);
-    state.currentFillStyle = state.fillStyle;
-  }
-  if (strokeStyle !== undefined) {
-    ol.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined');
-    ol.DEBUG && console.assert(lineDash, 'lineDash should not be null');
-    ol.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined');
-    ol.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined');
-    ol.DEBUG && console.assert(miterLimit !== undefined,
-        'miterLimit should be defined');
-    if (state.currentStrokeStyle != strokeStyle ||
-        state.currentLineCap != lineCap ||
-        state.currentLineDash != lineDash ||
-        state.currentLineJoin != lineJoin ||
-        state.currentLineWidth != lineWidth ||
-        state.currentMiterLimit != miterLimit) {
-      this.instructions.push(
-          [ol.render.canvas.Instruction.SET_STROKE_STYLE,
-           strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash]);
-      state.currentStrokeStyle = strokeStyle;
-      state.currentLineCap = lineCap;
-      state.currentLineDash = lineDash;
-      state.currentLineJoin = lineJoin;
-      state.currentLineWidth = lineWidth;
-      state.currentMiterLimit = miterLimit;
-    }
-  }
-};
-
-goog.provide('ol.render.canvas.TextReplay');
-
-goog.require('ol');
-goog.require('ol.colorlike');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.Instruction');
-goog.require('ol.render.canvas.Replay');
-
-
-/**
- * @constructor
- * @extends {ol.render.canvas.Replay}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Maximum extent.
- * @param {number} resolution Resolution.
- * @param {boolean} overlaps The replay can have overlapping geometries.
- * @struct
- */
-ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) {
-
-  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);
-
-  /**
-   * @private
-   * @type {?ol.CanvasFillState}
-   */
-  this.replayFillState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
-   */
-  this.replayStrokeState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasTextState}
-   */
-  this.replayTextState_ = null;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.text_ = '';
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetX_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textOffsetY_ = 0;
-
-  /**
-   * @private
-   * @type {boolean|undefined}
-   */
-  this.textRotateWithView_ = undefined;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textRotation_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textScale_ = 0;
-
-  /**
-   * @private
-   * @type {?ol.CanvasFillState}
-   */
-  this.textFillState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasStrokeState}
-   */
-  this.textStrokeState_ = null;
-
-  /**
-   * @private
-   * @type {?ol.CanvasTextState}
-   */
-  this.textState_ = null;
-
-};
-ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {
-  if (this.text_ === '' || !this.textState_ ||
-      (!this.textFillState_ && !this.textStrokeState_)) {
-    return;
-  }
-  if (this.textFillState_) {
-    this.setReplayFillState_(this.textFillState_);
-  }
-  if (this.textStrokeState_) {
-    this.setReplayStrokeState_(this.textStrokeState_);
-  }
-  this.setReplayTextState_(this.textState_);
-  this.beginGeometry(geometry, feature);
-  var myBegin = this.coordinates.length;
-  var myEnd =
-      this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false);
-  var fill = !!this.textFillState_;
-  var stroke = !!this.textStrokeState_;
-  var drawTextInstruction = [
-    ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_,
-    this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_,
-    fill, stroke, this.textRotateWithView_];
-  this.instructions.push(drawTextInstruction);
-  this.hitDetectionInstructions.push(drawTextInstruction);
-  this.endGeometry(geometry, feature);
-};
-
-
-/**
- * @param {ol.CanvasFillState} fillState Fill state.
- * @private
- */
-ol.render.canvas.TextReplay.prototype.setReplayFillState_ = function(fillState) {
-  var replayFillState = this.replayFillState_;
-  if (replayFillState &&
-      replayFillState.fillStyle == fillState.fillStyle) {
-    return;
-  }
-  var setFillStyleInstruction =
-      [ol.render.canvas.Instruction.SET_FILL_STYLE, fillState.fillStyle];
-  this.instructions.push(setFillStyleInstruction);
-  this.hitDetectionInstructions.push(setFillStyleInstruction);
-  if (!replayFillState) {
-    this.replayFillState_ = {
-      fillStyle: fillState.fillStyle
-    };
-  } else {
-    replayFillState.fillStyle = fillState.fillStyle;
-  }
-};
-
-
-/**
- * @param {ol.CanvasStrokeState} strokeState Stroke state.
- * @private
- */
-ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ = function(strokeState) {
-  var replayStrokeState = this.replayStrokeState_;
-  if (replayStrokeState &&
-      replayStrokeState.lineCap == strokeState.lineCap &&
-      replayStrokeState.lineDash == strokeState.lineDash &&
-      replayStrokeState.lineJoin == strokeState.lineJoin &&
-      replayStrokeState.lineWidth == strokeState.lineWidth &&
-      replayStrokeState.miterLimit == strokeState.miterLimit &&
-      replayStrokeState.strokeStyle == strokeState.strokeStyle) {
-    return;
-  }
-  var setStrokeStyleInstruction = [
-    ol.render.canvas.Instruction.SET_STROKE_STYLE, strokeState.strokeStyle,
-    strokeState.lineWidth, strokeState.lineCap, strokeState.lineJoin,
-    strokeState.miterLimit, strokeState.lineDash, false
-  ];
-  this.instructions.push(setStrokeStyleInstruction);
-  this.hitDetectionInstructions.push(setStrokeStyleInstruction);
-  if (!replayStrokeState) {
-    this.replayStrokeState_ = {
-      lineCap: strokeState.lineCap,
-      lineDash: strokeState.lineDash,
-      lineJoin: strokeState.lineJoin,
-      lineWidth: strokeState.lineWidth,
-      miterLimit: strokeState.miterLimit,
-      strokeStyle: strokeState.strokeStyle
-    };
-  } else {
-    replayStrokeState.lineCap = strokeState.lineCap;
-    replayStrokeState.lineDash = strokeState.lineDash;
-    replayStrokeState.lineJoin = strokeState.lineJoin;
-    replayStrokeState.lineWidth = strokeState.lineWidth;
-    replayStrokeState.miterLimit = strokeState.miterLimit;
-    replayStrokeState.strokeStyle = strokeState.strokeStyle;
-  }
-};
-
-
-/**
- * @param {ol.CanvasTextState} textState Text state.
- * @private
- */
-ol.render.canvas.TextReplay.prototype.setReplayTextState_ = function(textState) {
-  var replayTextState = this.replayTextState_;
-  if (replayTextState &&
-      replayTextState.font == textState.font &&
-      replayTextState.textAlign == textState.textAlign &&
-      replayTextState.textBaseline == textState.textBaseline) {
-    return;
-  }
-  var setTextStyleInstruction = [ol.render.canvas.Instruction.SET_TEXT_STYLE,
-    textState.font, textState.textAlign, textState.textBaseline];
-  this.instructions.push(setTextStyleInstruction);
-  this.hitDetectionInstructions.push(setTextStyleInstruction);
-  if (!replayTextState) {
-    this.replayTextState_ = {
-      font: textState.font,
-      textAlign: textState.textAlign,
-      textBaseline: textState.textBaseline
-    };
-  } else {
-    replayTextState.font = textState.font;
-    replayTextState.textAlign = textState.textAlign;
-    replayTextState.textBaseline = textState.textBaseline;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
-  if (!textStyle) {
-    this.text_ = '';
-  } else {
-    var textFillStyle = textStyle.getFill();
-    if (!textFillStyle) {
-      this.textFillState_ = null;
-    } else {
-      var textFillStyleColor = textFillStyle.getColor();
-      var fillStyle = ol.colorlike.asColorLike(textFillStyleColor ?
-          textFillStyleColor : ol.render.canvas.defaultFillStyle);
-      if (!this.textFillState_) {
-        this.textFillState_ = {
-          fillStyle: fillStyle
-        };
-      } else {
-        var textFillState = this.textFillState_;
-        textFillState.fillStyle = fillStyle;
-      }
-    }
-    var textStrokeStyle = textStyle.getStroke();
-    if (!textStrokeStyle) {
-      this.textStrokeState_ = null;
-    } else {
-      var textStrokeStyleColor = textStrokeStyle.getColor();
-      var textStrokeStyleLineCap = textStrokeStyle.getLineCap();
-      var textStrokeStyleLineDash = textStrokeStyle.getLineDash();
-      var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin();
-      var textStrokeStyleWidth = textStrokeStyle.getWidth();
-      var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit();
-      var lineCap = textStrokeStyleLineCap !== undefined ?
-          textStrokeStyleLineCap : ol.render.canvas.defaultLineCap;
-      var lineDash = textStrokeStyleLineDash ?
-          textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
-      var lineJoin = textStrokeStyleLineJoin !== undefined ?
-          textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
-      var lineWidth = textStrokeStyleWidth !== undefined ?
-          textStrokeStyleWidth : ol.render.canvas.defaultLineWidth;
-      var miterLimit = textStrokeStyleMiterLimit !== undefined ?
-          textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
-      var strokeStyle = ol.colorlike.asColorLike(textStrokeStyleColor ?
-          textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle);
-      if (!this.textStrokeState_) {
-        this.textStrokeState_ = {
-          lineCap: lineCap,
-          lineDash: lineDash,
-          lineJoin: lineJoin,
-          lineWidth: lineWidth,
-          miterLimit: miterLimit,
-          strokeStyle: strokeStyle
-        };
-      } else {
-        var textStrokeState = this.textStrokeState_;
-        textStrokeState.lineCap = lineCap;
-        textStrokeState.lineDash = lineDash;
-        textStrokeState.lineJoin = lineJoin;
-        textStrokeState.lineWidth = lineWidth;
-        textStrokeState.miterLimit = miterLimit;
-        textStrokeState.strokeStyle = strokeStyle;
-      }
-    }
-    var textFont = textStyle.getFont();
-    var textOffsetX = textStyle.getOffsetX();
-    var textOffsetY = textStyle.getOffsetY();
-    var textRotateWithView = textStyle.getRotateWithView();
-    var textRotation = textStyle.getRotation();
-    var textScale = textStyle.getScale();
-    var textText = textStyle.getText();
-    var textTextAlign = textStyle.getTextAlign();
-    var textTextBaseline = textStyle.getTextBaseline();
-    var font = textFont !== undefined ?
-        textFont : ol.render.canvas.defaultFont;
-    var textAlign = textTextAlign !== undefined ?
-        textTextAlign : ol.render.canvas.defaultTextAlign;
-    var textBaseline = textTextBaseline !== undefined ?
-        textTextBaseline : ol.render.canvas.defaultTextBaseline;
-    if (!this.textState_) {
-      this.textState_ = {
-        font: font,
-        textAlign: textAlign,
-        textBaseline: textBaseline
-      };
-    } else {
-      var textState = this.textState_;
-      textState.font = font;
-      textState.textAlign = textAlign;
-      textState.textBaseline = textBaseline;
-    }
-    this.text_ = textText !== undefined ? textText : '';
-    this.textOffsetX_ = textOffsetX !== undefined ? textOffsetX : 0;
-    this.textOffsetY_ = textOffsetY !== undefined ? textOffsetY : 0;
-    this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false;
-    this.textRotation_ = textRotation !== undefined ? textRotation : 0;
-    this.textScale_ = textScale !== undefined ? textScale : 1;
-  }
-};
-
-goog.provide('ol.render.ReplayType');
-
-
-/**
- * @enum {string}
- */
-ol.render.ReplayType = {
-  IMAGE: 'Image',
-  LINE_STRING: 'LineString',
-  POLYGON: 'Polygon',
-  TEXT: 'Text'
-};
-
-goog.provide('ol.render.replay');
-
-goog.require('ol.render.ReplayType');
-
-
-/**
- * @const
- * @type {Array.<ol.render.ReplayType>}
- */
-ol.render.replay.ORDER = [
-  ol.render.ReplayType.POLYGON,
-  ol.render.ReplayType.LINE_STRING,
-  ol.render.ReplayType.IMAGE,
-  ol.render.ReplayType.TEXT
-];
-
-goog.provide('ol.render.canvas.ReplayGroup');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.geom.flat.transform');
-goog.require('ol.obj');
-goog.require('ol.render.ReplayGroup');
-goog.require('ol.render.canvas.ImageReplay');
-goog.require('ol.render.canvas.LineStringReplay');
-goog.require('ol.render.canvas.PolygonReplay');
-goog.require('ol.render.canvas.TextReplay');
-goog.require('ol.render.replay');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.render.ReplayGroup}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Max extent.
- * @param {number} resolution Resolution.
- * @param {boolean} overlaps The replay group can have overlapping geometries.
- * @param {number=} opt_renderBuffer Optional rendering buffer.
- * @struct
- */
-ol.render.canvas.ReplayGroup = function(
-    tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) {
-  ol.render.ReplayGroup.call(this);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.tolerance_ = tolerance;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.maxExtent_ = maxExtent;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.overlaps_ = overlaps;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.resolution_ = resolution;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.renderBuffer_ = opt_renderBuffer;
-
-  /**
-   * @private
-   * @type {!Object.<string,
-   *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
-   */
-  this.replaysByZIndex_ = {};
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1);
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.hitDetectionTransform_ = ol.transform.create();
-
-};
-ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.render.canvas.ReplayGroup.prototype.finish = function() {
-  var zKey;
-  for (zKey in this.replaysByZIndex_) {
-    var replays = this.replaysByZIndex_[zKey];
-    var replayKey;
-    for (replayKey in replays) {
-      replays[replayKey].finish();
-    }
-  }
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *     to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
- *     callback.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
-    coordinate, resolution, rotation, skippedFeaturesHash, callback) {
-
-  var transform = ol.transform.compose(this.hitDetectionTransform_,
-      0.5, 0.5,
-      1 / resolution, -1 / resolution,
-      -rotation,
-      -coordinate[0], -coordinate[1]);
-
-  var context = this.hitDetectionContext_;
-  context.clearRect(0, 0, 1, 1);
-
-  /**
-   * @type {ol.Extent}
-   */
-  var hitExtent;
-  if (this.renderBuffer_ !== undefined) {
-    hitExtent = ol.extent.createEmpty();
-    ol.extent.extendCoordinate(hitExtent, coordinate);
-    ol.extent.buffer(hitExtent, resolution * this.renderBuffer_, hitExtent);
-  }
-
-  return this.replayHitDetection_(context, transform, rotation,
-      skippedFeaturesHash,
-      /**
-       * @param {ol.Feature|ol.render.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        var imageData = context.getImageData(0, 0, 1, 1).data;
-        if (imageData[3] > 0) {
-          var result = callback(feature);
-          if (result) {
-            return result;
-          }
-          context.clearRect(0, 0, 1, 1);
-        }
-      }, hitExtent);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {
-  var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
-  var replays = this.replaysByZIndex_[zIndexKey];
-  if (replays === undefined) {
-    replays = {};
-    this.replaysByZIndex_[zIndexKey] = replays;
-  }
-  var replay = replays[replayType];
-  if (replay === undefined) {
-    var Constructor = ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_[replayType];
-    ol.DEBUG && console.assert(Constructor !== undefined,
-        replayType +
-        ' constructor missing from ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_');
-    replay = new Constructor(this.tolerance_, this.maxExtent_,
-        this.resolution_, this.overlaps_);
-    replays[replayType] = replay;
-  }
-  return replay;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
-  return ol.obj.isEmpty(this.replaysByZIndex_);
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Transform} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *     to skip.
- * @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types
- *     to replay. Default is {@link ol.render.replay.ORDER}
- */
-ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio,
-    transform, viewRotation, skippedFeaturesHash, opt_replayTypes) {
-
-  /** @type {Array.<number>} */
-  var zs = Object.keys(this.replaysByZIndex_).map(Number);
-  zs.sort(ol.array.numberSafeCompareFunction);
-
-  // setup clipping so that the parts of over-simplified geometries are not
-  // visible outside the current extent when panning
-  var maxExtent = this.maxExtent_;
-  var minX = maxExtent[0];
-  var minY = maxExtent[1];
-  var maxX = maxExtent[2];
-  var maxY = maxExtent[3];
-  var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY];
-  ol.geom.flat.transform.transform2D(
-      flatClipCoords, 0, 8, 2, transform, flatClipCoords);
-  context.save();
-  context.beginPath();
-  context.moveTo(flatClipCoords[0], flatClipCoords[1]);
-  context.lineTo(flatClipCoords[2], flatClipCoords[3]);
-  context.lineTo(flatClipCoords[4], flatClipCoords[5]);
-  context.lineTo(flatClipCoords[6], flatClipCoords[7]);
-  context.clip();
-
-  var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER;
-  var i, ii, j, jj, replays, replay;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    replays = this.replaysByZIndex_[zs[i].toString()];
-    for (j = 0, jj = replayTypes.length; j < jj; ++j) {
-      replay = replays[replayTypes[j]];
-      if (replay !== undefined) {
-        replay.replay(context, pixelRatio, transform, viewRotation,
-            skippedFeaturesHash);
-      }
-    }
-  }
-
-  context.restore();
-};
-
-
-/**
- * @private
- * @param {CanvasRenderingContext2D} context Context.
- * @param {ol.Transform} transform Transform.
- * @param {number} viewRotation View rotation.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *     to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T} featureCallback
- *     Feature callback.
- * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
- *     extent.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
-    context, transform, viewRotation, skippedFeaturesHash,
-    featureCallback, opt_hitExtent) {
-  /** @type {Array.<number>} */
-  var zs = Object.keys(this.replaysByZIndex_).map(Number);
-  zs.sort(function(a, b) {
-    return b - a;
-  });
-
-  var i, ii, j, replays, replay, result;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    replays = this.replaysByZIndex_[zs[i].toString()];
-    for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) {
-      replay = replays[ol.render.replay.ORDER[j]];
-      if (replay !== undefined) {
-        result = replay.replayHitDetection(context, transform, viewRotation,
-            skippedFeaturesHash, featureCallback, opt_hitExtent);
-        if (result) {
-          return result;
-        }
-      }
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @const
- * @private
- * @type {Object.<ol.render.ReplayType,
- *                function(new: ol.render.canvas.Replay, number, ol.Extent,
- *                number, boolean)>}
- */
-ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
-  'Image': ol.render.canvas.ImageReplay,
-  'LineString': ol.render.canvas.LineStringReplay,
-  'Polygon': ol.render.canvas.PolygonReplay,
-  'Text': ol.render.canvas.TextReplay
-};
-
-goog.provide('ol.renderer.vector');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.render.ReplayType');
-
-
-/**
- * @param {ol.Feature|ol.render.Feature} feature1 Feature 1.
- * @param {ol.Feature|ol.render.Feature} feature2 Feature 2.
- * @return {number} Order.
- */
-ol.renderer.vector.defaultOrder = function(feature1, feature2) {
-  return ol.getUid(feature1) - ol.getUid(feature2);
-};
-
-
-/**
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @return {number} Squared pixel tolerance.
- */
-ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) {
-  var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
-  return tolerance * tolerance;
-};
-
-
-/**
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @return {number} Pixel tolerance.
- */
-ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
-  return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Circle} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style, feature) {
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (fillStyle || strokeStyle) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.drawCircle(geometry, feature);
-  }
-  var textStyle = style.getText();
-  if (textStyle) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {function(this: T, ol.events.Event)} listener Listener function.
- * @param {T} thisArg Value to use as `this` when executing `listener`.
- * @return {boolean} `true` if style is loading.
- * @template T
- */
-ol.renderer.vector.renderFeature = function(
-    replayGroup, feature, style, squaredTolerance, listener, thisArg) {
-  var loading = false;
-  var imageStyle, imageState;
-  imageStyle = style.getImage();
-  if (imageStyle) {
-    imageState = imageStyle.getImageState();
-    if (imageState == ol.Image.State.LOADED ||
-        imageState == ol.Image.State.ERROR) {
-      imageStyle.unlistenImageChange(listener, thisArg);
-    } else {
-      if (imageState == ol.Image.State.IDLE) {
-        imageStyle.load();
-      }
-      imageState = imageStyle.getImageState();
-      ol.DEBUG && console.assert(imageState == ol.Image.State.LOADING,
-          'imageState should be LOADING');
-      imageStyle.listenImageChange(listener, thisArg);
-      loading = true;
-    }
-  }
-  ol.renderer.vector.renderFeature_(replayGroup, feature, style,
-      squaredTolerance);
-  return loading;
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @param {ol.style.Style} style Style.
- * @param {number} squaredTolerance Squared tolerance.
- * @private
- */
-ol.renderer.vector.renderFeature_ = function(
-    replayGroup, feature, style, squaredTolerance) {
-  var geometry = style.getGeometryFunction()(feature);
-  if (!geometry) {
-    return;
-  }
-  var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
-  var geometryRenderer =
-      ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
-  geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderGeometryCollectionGeometry_ = function(replayGroup, geometry, style, feature) {
-  var geometries = geometry.getGeometriesArray();
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    var geometryRenderer =
-        ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()];
-    geometryRenderer(replayGroup, geometries[i], style, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.LineString|ol.render.Feature} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
-  var strokeStyle = style.getStroke();
-  if (strokeStyle) {
-    var lineStringReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
-    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
-    lineStringReplay.drawLineString(geometry, feature);
-  }
-  var textStyle = style.getText();
-  if (textStyle) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.MultiLineString|ol.render.Feature} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
-  var strokeStyle = style.getStroke();
-  if (strokeStyle) {
-    var lineStringReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
-    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
-    lineStringReplay.drawMultiLineString(geometry, feature);
-  }
-  var textStyle = style.getText();
-  if (textStyle) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    var flatMidpointCoordinates = geometry.getFlatMidpoints();
-    textReplay.drawText(flatMidpointCoordinates, 0,
-        flatMidpointCoordinates.length, 2, geometry, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (strokeStyle || fillStyle) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.drawMultiPolygon(geometry, feature);
-  }
-  var textStyle = style.getText();
-  if (textStyle) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    var flatInteriorPointCoordinates = geometry.getFlatInteriorPoints();
-    textReplay.drawText(flatInteriorPointCoordinates, 0,
-        flatInteriorPointCoordinates.length, 2, geometry, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Point|ol.render.Feature} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) {
-  var imageStyle = style.getImage();
-  if (imageStyle) {
-    if (imageStyle.getImageState() != ol.Image.State.LOADED) {
-      return;
-    }
-    var imageReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.IMAGE);
-    imageReplay.setImageStyle(imageStyle);
-    imageReplay.drawPoint(geometry, feature);
-  }
-  var textStyle = style.getText();
-  if (textStyle) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(geometry.getFlatCoordinates(), 0, 2, 2, geometry,
-        feature);
-  }
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.MultiPoint|ol.render.Feature} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) {
-  var imageStyle = style.getImage();
-  if (imageStyle) {
-    if (imageStyle.getImageState() != ol.Image.State.LOADED) {
-      return;
-    }
-    var imageReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.IMAGE);
-    imageReplay.setImageStyle(imageStyle);
-    imageReplay.drawMultiPoint(geometry, feature);
-  }
-  var textStyle = style.getText();
-  if (textStyle) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    var flatCoordinates = geometry.getFlatCoordinates();
-    textReplay.drawText(flatCoordinates, 0, flatCoordinates.length,
-        geometry.getStride(), geometry, feature);
-  }
-};
-
-
-/**
- * @param {ol.render.ReplayGroup} replayGroup Replay group.
- * @param {ol.geom.Polygon|ol.render.Feature} geometry Geometry.
- * @param {ol.style.Style} style Style.
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @private
- */
-ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  if (fillStyle || strokeStyle) {
-    var polygonReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.POLYGON);
-    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
-    polygonReplay.drawPolygon(geometry, feature);
-  }
-  var textStyle = style.getText();
-  if (textStyle) {
-    var textReplay = replayGroup.getReplay(
-        style.getZIndex(), ol.render.ReplayType.TEXT);
-    textReplay.setTextStyle(textStyle);
-    textReplay.drawText(
-        geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature);
-  }
-};
-
-
-/**
- * @const
- * @private
- * @type {Object.<ol.geom.GeometryType,
- *                function(ol.render.ReplayGroup, ol.geom.Geometry,
- *                         ol.style.Style, Object)>}
- */
-ol.renderer.vector.GEOMETRY_RENDERERS_ = {
-  'Point': ol.renderer.vector.renderPointGeometry_,
-  'LineString': ol.renderer.vector.renderLineStringGeometry_,
-  'Polygon': ol.renderer.vector.renderPolygonGeometry_,
-  'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_,
-  'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_,
-  'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_,
-  'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_,
-  'Circle': ol.renderer.vector.renderCircleGeometry_
-};
-
-goog.provide('ol.ImageCanvas');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.ImageBase');
-
-
-/**
- * @constructor
- * @extends {ol.ImageBase}
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {Array.<ol.Attribution>} attributions Attributions.
- * @param {HTMLCanvasElement} canvas Canvas.
- * @param {ol.ImageCanvasLoader=} opt_loader Optional loader function to
- *     support asynchronous canvas drawing.
- */
-ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions,
-    canvas, opt_loader) {
-
-  /**
-   * Optional canvas loader function.
-   * @type {?ol.ImageCanvasLoader}
-   * @private
-   */
-  this.loader_ = opt_loader !== undefined ? opt_loader : null;
-
-  var state = opt_loader !== undefined ?
-      ol.Image.State.IDLE : ol.Image.State.LOADED;
-
-  ol.ImageBase.call(this, extent, resolution, pixelRatio, state, attributions);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = canvas;
-
-  /**
-   * @private
-   * @type {Error}
-   */
-  this.error_ = null;
-
-};
-ol.inherits(ol.ImageCanvas, ol.ImageBase);
-
-
-/**
- * Get any error associated with asynchronous rendering.
- * @return {Error} Any error that occurred during rendering.
- */
-ol.ImageCanvas.prototype.getError = function() {
-  return this.error_;
-};
-
-
-/**
- * Handle async drawing complete.
- * @param {Error} err Any error during drawing.
- * @private
- */
-ol.ImageCanvas.prototype.handleLoad_ = function(err) {
-  if (err) {
-    this.error_ = err;
-    this.state = ol.Image.State.ERROR;
-  } else {
-    this.state = ol.Image.State.LOADED;
-  }
-  this.changed();
-};
-
-
-/**
- * Trigger drawing on canvas.
- */
-ol.ImageCanvas.prototype.load = function() {
-  if (this.state == ol.Image.State.IDLE) {
-    ol.DEBUG && console.assert(this.loader_, 'this.loader_ must be set');
-    this.state = ol.Image.State.LOADING;
-    this.changed();
-    this.loader_(this.handleLoad_.bind(this));
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.ImageCanvas.prototype.getImage = function(opt_context) {
-  return this.canvas_;
-};
-
-goog.provide('ol.reproj');
-
-goog.require('ol');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.proj');
-
-
-/**
- * We need to employ more sophisticated solution
- * if the web browser antialiases clipping edges on canvas.
- *
- * Currently only Chrome does not antialias the edges, but this is probably
- * going to be "fixed" in the future: http://crbug.com/424291
- *
- * @type {boolean}
- * @private
- */
-ol.reproj.browserAntialiasesClip_ = (function() {
-  // Adapted from http://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
-  var isOpera = navigator.userAgent.indexOf('OPR') > -1;
-  var isIEedge = navigator.userAgent.indexOf('Edge') > -1;
-  return !(
-    !navigator.userAgent.match('CriOS') &&  // Not Chrome on iOS
-    'chrome' in window && // Has chrome in window
-    navigator.vendor === 'Google Inc.' && // Vendor is Google.
-    isOpera == false && // Not Opera
-    isIEedge == false // Not Edge
-  );
-})();
-
-
-/**
- * Calculates ideal resolution to use from the source in order to achieve
- * pixel mapping as close as possible to 1:1 during reprojection.
- * The resolution is calculated regardless of what resolutions
- * are actually available in the dataset (TileGrid, Image, ...).
- *
- * @param {ol.proj.Projection} sourceProj Source projection.
- * @param {ol.proj.Projection} targetProj Target projection.
- * @param {ol.Coordinate} targetCenter Target center.
- * @param {number} targetResolution Target resolution.
- * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
- */
-ol.reproj.calculateSourceResolution = function(sourceProj, targetProj,
-    targetCenter, targetResolution) {
-
-  var sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj);
-
-  // calculate the ideal resolution of the source data
-  var sourceResolution =
-      targetProj.getPointResolution(targetResolution, targetCenter);
-
-  var targetMetersPerUnit = targetProj.getMetersPerUnit();
-  if (targetMetersPerUnit !== undefined) {
-    sourceResolution *= targetMetersPerUnit;
-  }
-  var sourceMetersPerUnit = sourceProj.getMetersPerUnit();
-  if (sourceMetersPerUnit !== undefined) {
-    sourceResolution /= sourceMetersPerUnit;
-  }
-
-  // Based on the projection properties, the point resolution at the specified
-  // coordinates may be slightly different. We need to reverse-compensate this
-  // in order to achieve optimal results.
-
-  var compensationFactor =
-      sourceProj.getPointResolution(sourceResolution, sourceCenter) /
-      sourceResolution;
-
-  if (isFinite(compensationFactor) && compensationFactor > 0) {
-    sourceResolution /= compensationFactor;
-  }
-
-  return sourceResolution;
-};
-
-
-/**
- * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap
- * in order to mask gaps caused by antialiasing.
- *
- * @param {number} centroidX Centroid of the triangle (x coordinate in pixels).
- * @param {number} centroidY Centroid of the triangle (y coordinate in pixels).
- * @param {number} x X coordinate of the point (in pixels).
- * @param {number} y Y coordinate of the point (in pixels).
- * @return {ol.Coordinate} New point 1 px farther from the centroid.
- * @private
- */
-ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, x, y) {
-  var dX = x - centroidX, dY = y - centroidY;
-  var distance = Math.sqrt(dX * dX + dY * dY);
-  return [Math.round(x + dX / distance), Math.round(y + dY / distance)];
-};
-
-
-/**
- * Renders the source data into new canvas based on the triangulation.
- *
- * @param {number} width Width of the canvas.
- * @param {number} height Height of the canvas.
- * @param {number} pixelRatio Pixel ratio.
- * @param {number} sourceResolution Source resolution.
- * @param {ol.Extent} sourceExtent Extent of the data source.
- * @param {number} targetResolution Target resolution.
- * @param {ol.Extent} targetExtent Target extent.
- * @param {ol.reproj.Triangulation} triangulation Calculated triangulation.
- * @param {Array.<{extent: ol.Extent,
- *                 image: (HTMLCanvasElement|Image|HTMLVideoElement)}>} sources
- *             Array of sources.
- * @param {number} gutter Gutter of the sources.
- * @param {boolean=} opt_renderEdges Render reprojection edges.
- * @return {HTMLCanvasElement} Canvas with reprojected data.
- */
-ol.reproj.render = function(width, height, pixelRatio,
-    sourceResolution, sourceExtent, targetResolution, targetExtent,
-    triangulation, sources, gutter, opt_renderEdges) {
-
-  var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width),
-                                             Math.round(pixelRatio * height));
-
-  if (sources.length === 0) {
-    return context.canvas;
-  }
-
-  context.scale(pixelRatio, pixelRatio);
-
-  var sourceDataExtent = ol.extent.createEmpty();
-  sources.forEach(function(src, i, arr) {
-    ol.extent.extend(sourceDataExtent, src.extent);
-  });
-
-  var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent);
-  var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent);
-  var stitchContext = ol.dom.createCanvasContext2D(
-      Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
-      Math.round(pixelRatio * canvasHeightInUnits / sourceResolution));
-
-  var stitchScale = pixelRatio / sourceResolution;
-
-  sources.forEach(function(src, i, arr) {
-    var xPos = src.extent[0] - sourceDataExtent[0];
-    var yPos = -(src.extent[3] - sourceDataExtent[3]);
-    var srcWidth = ol.extent.getWidth(src.extent);
-    var srcHeight = ol.extent.getHeight(src.extent);
-
-    stitchContext.drawImage(
-        src.image,
-        gutter, gutter,
-        src.image.width - 2 * gutter, src.image.height - 2 * gutter,
-        xPos * stitchScale, yPos * stitchScale,
-        srcWidth * stitchScale, srcHeight * stitchScale);
-  });
-
-  var targetTopLeft = ol.extent.getTopLeft(targetExtent);
-
-  triangulation.getTriangles().forEach(function(triangle, i, arr) {
-    /* Calculate affine transform (src -> dst)
-     * Resulting matrix can be used to transform coordinate
-     * from `sourceProjection` to destination pixels.
-     *
-     * To optimize number of context calls and increase numerical stability,
-     * we also do the following operations:
-     * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1)
-     * here before solving the linear system so [ui, vi] are pixel coordinates.
-     *
-     * Src points: xi, yi
-     * Dst points: ui, vi
-     * Affine coefficients: aij
-     *
-     * | x0 y0 1  0  0 0 |   |a00|   |u0|
-     * | x1 y1 1  0  0 0 |   |a01|   |u1|
-     * | x2 y2 1  0  0 0 | x |a02| = |u2|
-     * |  0  0 0 x0 y0 1 |   |a10|   |v0|
-     * |  0  0 0 x1 y1 1 |   |a11|   |v1|
-     * |  0  0 0 x2 y2 1 |   |a12|   |v2|
-     */
-    var source = triangle.source, target = triangle.target;
-    var x0 = source[0][0], y0 = source[0][1],
-        x1 = source[1][0], y1 = source[1][1],
-        x2 = source[2][0], y2 = source[2][1];
-    var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution,
-        v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
-    var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution,
-        v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
-    var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
-        v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;
-
-    // Shift all the source points to improve numerical stability
-    // of all the subsequent calculations. The [x0, y0] is used here.
-    // This is also used to simplify the linear system.
-    var sourceNumericalShiftX = x0, sourceNumericalShiftY = y0;
-    x0 = 0;
-    y0 = 0;
-    x1 -= sourceNumericalShiftX;
-    y1 -= sourceNumericalShiftY;
-    x2 -= sourceNumericalShiftX;
-    y2 -= sourceNumericalShiftY;
-
-    var augmentedMatrix = [
-      [x1, y1, 0, 0, u1 - u0],
-      [x2, y2, 0, 0, u2 - u0],
-      [0, 0, x1, y1, v1 - v0],
-      [0, 0, x2, y2, v2 - v0]
-    ];
-    var affineCoefs = ol.math.solveLinearSystem(augmentedMatrix);
-    if (!affineCoefs) {
-      return;
-    }
-
-    context.save();
-    context.beginPath();
-    if (ol.reproj.browserAntialiasesClip_) {
-      var centroidX = (u0 + u1 + u2) / 3, centroidY = (v0 + v1 + v2) / 3;
-      var p0 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u0, v0);
-      var p1 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u1, v1);
-      var p2 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u2, v2);
-
-      context.moveTo(p1[0], p1[1]);
-      context.lineTo(p0[0], p0[1]);
-      context.lineTo(p2[0], p2[1]);
-    } else {
-      context.moveTo(u1, v1);
-      context.lineTo(u0, v0);
-      context.lineTo(u2, v2);
-    }
-    context.clip();
-
-    context.transform(
-        affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);
-
-    context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
-                      sourceDataExtent[3] - sourceNumericalShiftY);
-
-    context.scale(sourceResolution / pixelRatio,
-                  -sourceResolution / pixelRatio);
-
-    context.drawImage(stitchContext.canvas, 0, 0);
-    context.restore();
-  });
-
-  if (opt_renderEdges) {
-    context.save();
-
-    context.strokeStyle = 'black';
-    context.lineWidth = 1;
-
-    triangulation.getTriangles().forEach(function(triangle, i, arr) {
-      var target = triangle.target;
-      var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution,
-          v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
-      var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution,
-          v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
-      var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
-          v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;
-
-      context.beginPath();
-      context.moveTo(u1, v1);
-      context.lineTo(u0, v0);
-      context.lineTo(u2, v2);
-      context.closePath();
-      context.stroke();
-    });
-
-    context.restore();
-  }
-  return context.canvas;
-};
-
-goog.provide('ol.reproj.Triangulation');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Class containing triangulation of the given target extent.
- * Used for determining source data and the reprojection itself.
- *
- * @param {ol.proj.Projection} sourceProj Source projection.
- * @param {ol.proj.Projection} targetProj Target projection.
- * @param {ol.Extent} targetExtent Target extent to triangulate.
- * @param {ol.Extent} maxSourceExtent Maximal source extent that can be used.
- * @param {number} errorThreshold Acceptable error (in source units).
- * @constructor
- */
-ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
-    maxSourceExtent, errorThreshold) {
-
-  /**
-   * @type {ol.proj.Projection}
-   * @private
-   */
-  this.sourceProj_ = sourceProj;
-
-  /**
-   * @type {ol.proj.Projection}
-   * @private
-   */
-  this.targetProj_ = targetProj;
-
-  /** @type {!Object.<string, ol.Coordinate>} */
-  var transformInvCache = {};
-  var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_);
-
-  /**
-   * @param {ol.Coordinate} c A coordinate.
-   * @return {ol.Coordinate} Transformed coordinate.
-   * @private
-   */
-  this.transformInv_ = function(c) {
-    var key = c[0] + '/' + c[1];
-    if (!transformInvCache[key]) {
-      transformInvCache[key] = transformInv(c);
-    }
-    return transformInvCache[key];
-  };
-
-  /**
-   * @type {ol.Extent}
-   * @private
-   */
-  this.maxSourceExtent_ = maxSourceExtent;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.errorThresholdSquared_ = errorThreshold * errorThreshold;
-
-  /**
-   * @type {Array.<ol.ReprojTriangle>}
-   * @private
-   */
-  this.triangles_ = [];
-
-  /**
-   * Indicates that the triangulation crosses edge of the source projection.
-   * @type {boolean}
-   * @private
-   */
-  this.wrapsXInSource_ = false;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.canWrapXInSource_ = this.sourceProj_.canWrapX() &&
-      !!maxSourceExtent &&
-      !!this.sourceProj_.getExtent() &&
-      (ol.extent.getWidth(maxSourceExtent) ==
-       ol.extent.getWidth(this.sourceProj_.getExtent()));
-
-  /**
-   * @type {?number}
-   * @private
-   */
-  this.sourceWorldWidth_ = this.sourceProj_.getExtent() ?
-      ol.extent.getWidth(this.sourceProj_.getExtent()) : null;
-
-  /**
-   * @type {?number}
-   * @private
-   */
-  this.targetWorldWidth_ = this.targetProj_.getExtent() ?
-      ol.extent.getWidth(this.targetProj_.getExtent()) : null;
-
-  var destinationTopLeft = ol.extent.getTopLeft(targetExtent);
-  var destinationTopRight = ol.extent.getTopRight(targetExtent);
-  var destinationBottomRight = ol.extent.getBottomRight(targetExtent);
-  var destinationBottomLeft = ol.extent.getBottomLeft(targetExtent);
-  var sourceTopLeft = this.transformInv_(destinationTopLeft);
-  var sourceTopRight = this.transformInv_(destinationTopRight);
-  var sourceBottomRight = this.transformInv_(destinationBottomRight);
-  var sourceBottomLeft = this.transformInv_(destinationBottomLeft);
-
-  this.addQuad_(
-      destinationTopLeft, destinationTopRight,
-      destinationBottomRight, destinationBottomLeft,
-      sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
-      ol.RASTER_REPROJECTION_MAX_SUBDIVISION);
-
-  if (this.wrapsXInSource_) {
-    // Fix coordinates (ol.proj returns wrapped coordinates, "unwrap" here).
-    // This significantly simplifies the rest of the reprojection process.
-
-    ol.DEBUG && console.assert(this.sourceWorldWidth_ !== null);
-    var leftBound = Infinity;
-    this.triangles_.forEach(function(triangle, i, arr) {
-      leftBound = Math.min(leftBound,
-          triangle.source[0][0], triangle.source[1][0], triangle.source[2][0]);
-    });
-
-    // Shift triangles to be as close to `leftBound` as possible
-    // (if the distance is more than `worldWidth / 2` it can be closer.
-    this.triangles_.forEach(function(triangle) {
-      if (Math.max(triangle.source[0][0], triangle.source[1][0],
-          triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) {
-        var newTriangle = [[triangle.source[0][0], triangle.source[0][1]],
-                           [triangle.source[1][0], triangle.source[1][1]],
-                           [triangle.source[2][0], triangle.source[2][1]]];
-        if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) {
-          newTriangle[0][0] -= this.sourceWorldWidth_;
-        }
-        if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) {
-          newTriangle[1][0] -= this.sourceWorldWidth_;
-        }
-        if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) {
-          newTriangle[2][0] -= this.sourceWorldWidth_;
-        }
-
-        // Rarely (if the extent contains both the dateline and prime meridian)
-        // the shift can in turn break some triangles.
-        // Detect this here and don't shift in such cases.
-        var minX = Math.min(
-            newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
-        var maxX = Math.max(
-            newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
-        if ((maxX - minX) < this.sourceWorldWidth_ / 2) {
-          triangle.source = newTriangle;
-        }
-      }
-    }, this);
-  }
-
-  transformInvCache = {};
-};
-
-
-/**
- * Adds triangle to the triangulation.
- * @param {ol.Coordinate} a The target a coordinate.
- * @param {ol.Coordinate} b The target b coordinate.
- * @param {ol.Coordinate} c The target c coordinate.
- * @param {ol.Coordinate} aSrc The source a coordinate.
- * @param {ol.Coordinate} bSrc The source b coordinate.
- * @param {ol.Coordinate} cSrc The source c coordinate.
- * @private
- */
-ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c,
-    aSrc, bSrc, cSrc) {
-  this.triangles_.push({
-    source: [aSrc, bSrc, cSrc],
-    target: [a, b, c]
-  });
-};
-
-
-/**
- * Adds quad (points in clock-wise order) to the triangulation
- * (and reprojects the vertices) if valid.
- * Performs quad subdivision if needed to increase precision.
- *
- * @param {ol.Coordinate} a The target a coordinate.
- * @param {ol.Coordinate} b The target b coordinate.
- * @param {ol.Coordinate} c The target c coordinate.
- * @param {ol.Coordinate} d The target d coordinate.
- * @param {ol.Coordinate} aSrc The source a coordinate.
- * @param {ol.Coordinate} bSrc The source b coordinate.
- * @param {ol.Coordinate} cSrc The source c coordinate.
- * @param {ol.Coordinate} dSrc The source d coordinate.
- * @param {number} maxSubdivision Maximal allowed subdivision of the quad.
- * @private
- */
-ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d,
-    aSrc, bSrc, cSrc, dSrc, maxSubdivision) {
-
-  var sourceQuadExtent = ol.extent.boundingExtent([aSrc, bSrc, cSrc, dSrc]);
-  var sourceCoverageX = this.sourceWorldWidth_ ?
-      ol.extent.getWidth(sourceQuadExtent) / this.sourceWorldWidth_ : null;
-  var sourceWorldWidth = /** @type {number} */ (this.sourceWorldWidth_);
-
-  // when the quad is wrapped in the source projection
-  // it covers most of the projection extent, but not fully
-  var wrapsX = this.sourceProj_.canWrapX() &&
-               sourceCoverageX > 0.5 && sourceCoverageX < 1;
-
-  var needsSubdivision = false;
-
-  if (maxSubdivision > 0) {
-    if (this.targetProj_.isGlobal() && this.targetWorldWidth_) {
-      var targetQuadExtent = ol.extent.boundingExtent([a, b, c, d]);
-      var targetCoverageX =
-          ol.extent.getWidth(targetQuadExtent) / this.targetWorldWidth_;
-      needsSubdivision |=
-          targetCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH;
-    }
-    if (!wrapsX && this.sourceProj_.isGlobal() && sourceCoverageX) {
-      needsSubdivision |=
-          sourceCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH;
-    }
-  }
-
-  if (!needsSubdivision && this.maxSourceExtent_) {
-    if (!ol.extent.intersects(sourceQuadExtent, this.maxSourceExtent_)) {
-      // whole quad outside source projection extent -> ignore
-      return;
-    }
-  }
-
-  if (!needsSubdivision) {
-    if (!isFinite(aSrc[0]) || !isFinite(aSrc[1]) ||
-        !isFinite(bSrc[0]) || !isFinite(bSrc[1]) ||
-        !isFinite(cSrc[0]) || !isFinite(cSrc[1]) ||
-        !isFinite(dSrc[0]) || !isFinite(dSrc[1])) {
-      if (maxSubdivision > 0) {
-        needsSubdivision = true;
-      } else {
-        return;
-      }
-    }
-  }
-
-  if (maxSubdivision > 0) {
-    if (!needsSubdivision) {
-      var center = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2];
-      var centerSrc = this.transformInv_(center);
-
-      var dx;
-      if (wrapsX) {
-        var centerSrcEstimX =
-            (ol.math.modulo(aSrc[0], sourceWorldWidth) +
-             ol.math.modulo(cSrc[0], sourceWorldWidth)) / 2;
-        dx = centerSrcEstimX -
-            ol.math.modulo(centerSrc[0], sourceWorldWidth);
-      } else {
-        dx = (aSrc[0] + cSrc[0]) / 2 - centerSrc[0];
-      }
-      var dy = (aSrc[1] + cSrc[1]) / 2 - centerSrc[1];
-      var centerSrcErrorSquared = dx * dx + dy * dy;
-      needsSubdivision = centerSrcErrorSquared > this.errorThresholdSquared_;
-    }
-    if (needsSubdivision) {
-      if (Math.abs(a[0] - c[0]) <= Math.abs(a[1] - c[1])) {
-        // split horizontally (top & bottom)
-        var bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2];
-        var bcSrc = this.transformInv_(bc);
-        var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2];
-        var daSrc = this.transformInv_(da);
-
-        this.addQuad_(
-            a, b, bc, da, aSrc, bSrc, bcSrc, daSrc, maxSubdivision - 1);
-        this.addQuad_(
-            da, bc, c, d, daSrc, bcSrc, cSrc, dSrc, maxSubdivision - 1);
-      } else {
-        // split vertically (left & right)
-        var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
-        var abSrc = this.transformInv_(ab);
-        var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2];
-        var cdSrc = this.transformInv_(cd);
-
-        this.addQuad_(
-            a, ab, cd, d, aSrc, abSrc, cdSrc, dSrc, maxSubdivision - 1);
-        this.addQuad_(
-            ab, b, c, cd, abSrc, bSrc, cSrc, cdSrc, maxSubdivision - 1);
-      }
-      return;
-    }
-  }
-
-  if (wrapsX) {
-    if (!this.canWrapXInSource_) {
-      return;
-    }
-    this.wrapsXInSource_ = true;
-  }
-
-  this.addTriangle_(a, c, d, aSrc, cSrc, dSrc);
-  this.addTriangle_(a, b, c, aSrc, bSrc, cSrc);
-};
-
-
-/**
- * Calculates extent of the 'source' coordinates from all the triangles.
- *
- * @return {ol.Extent} Calculated extent.
- */
-ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
-  var extent = ol.extent.createEmpty();
-
-  this.triangles_.forEach(function(triangle, i, arr) {
-    var src = triangle.source;
-    ol.extent.extendCoordinate(extent, src[0]);
-    ol.extent.extendCoordinate(extent, src[1]);
-    ol.extent.extendCoordinate(extent, src[2]);
-  });
-
-  return extent;
-};
-
-
-/**
- * @return {Array.<ol.ReprojTriangle>} Array of the calculated triangles.
- */
-ol.reproj.Triangulation.prototype.getTriangles = function() {
-  return this.triangles_;
-};
-
-goog.provide('ol.reproj.Image');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.ImageBase');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.reproj');
-goog.require('ol.reproj.Triangulation');
-
-
-/**
- * @classdesc
- * Class encapsulating single reprojected image.
- * See {@link ol.source.Image}.
- *
- * @constructor
- * @extends {ol.ImageBase}
- * @param {ol.proj.Projection} sourceProj Source projection (of the data).
- * @param {ol.proj.Projection} targetProj Target projection.
- * @param {ol.Extent} targetExtent Target extent.
- * @param {number} targetResolution Target resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.ReprojImageFunctionType} getImageFunction
- *     Function returning source images (extent, resolution, pixelRatio).
- */
-ol.reproj.Image = function(sourceProj, targetProj,
-    targetExtent, targetResolution, pixelRatio, getImageFunction) {
-
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.targetProj_ = targetProj;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.maxSourceExtent_ = sourceProj.getExtent();
-  var maxTargetExtent = targetProj.getExtent();
-
-  var limitedTargetExtent = maxTargetExtent ?
-      ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
-
-  var targetCenter = ol.extent.getCenter(limitedTargetExtent);
-  var sourceResolution = ol.reproj.calculateSourceResolution(
-      sourceProj, targetProj, targetCenter, targetResolution);
-
-  var errorThresholdInPixels = ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
-
-  /**
-   * @private
-   * @type {!ol.reproj.Triangulation}
-   */
-  this.triangulation_ = new ol.reproj.Triangulation(
-      sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_,
-      sourceResolution * errorThresholdInPixels);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.targetResolution_ = targetResolution;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.targetExtent_ = targetExtent;
-
-  var sourceExtent = this.triangulation_.calculateSourceExtent();
-
-  /**
-   * @private
-   * @type {ol.ImageBase}
-   */
-  this.sourceImage_ =
-      getImageFunction(sourceExtent, sourceResolution, pixelRatio);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.sourcePixelRatio_ =
-      this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = null;
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.sourceListenerKey_ = null;
-
-
-  var state = ol.Image.State.LOADED;
-  var attributions = [];
-
-  if (this.sourceImage_) {
-    state = ol.Image.State.IDLE;
-    attributions = this.sourceImage_.getAttributions();
-  }
-
-  ol.ImageBase.call(this, targetExtent, targetResolution, this.sourcePixelRatio_,
-            state, attributions);
-};
-ol.inherits(ol.reproj.Image, ol.ImageBase);
-
-
-/**
- * @inheritDoc
- */
-ol.reproj.Image.prototype.disposeInternal = function() {
-  if (this.state == ol.Image.State.LOADING) {
-    this.unlistenSource_();
-  }
-  ol.ImageBase.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.reproj.Image.prototype.getImage = function(opt_context) {
-  return this.canvas_;
-};
-
-
-/**
- * @return {ol.proj.Projection} Projection.
- */
-ol.reproj.Image.prototype.getProjection = function() {
-  return this.targetProj_;
-};
-
-
-/**
- * @private
- */
-ol.reproj.Image.prototype.reproject_ = function() {
-  var sourceState = this.sourceImage_.getState();
-  if (sourceState == ol.Image.State.LOADED) {
-    var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_;
-    var height =
-        ol.extent.getHeight(this.targetExtent_) / this.targetResolution_;
-
-    this.canvas_ = ol.reproj.render(width, height, this.sourcePixelRatio_,
-        this.sourceImage_.getResolution(), this.maxSourceExtent_,
-        this.targetResolution_, this.targetExtent_, this.triangulation_, [{
-          extent: this.sourceImage_.getExtent(),
-          image: this.sourceImage_.getImage()
-        }], 0);
-  }
-  this.state = sourceState;
-  this.changed();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.reproj.Image.prototype.load = function() {
-  if (this.state == ol.Image.State.IDLE) {
-    this.state = ol.Image.State.LOADING;
-    this.changed();
-
-    var sourceState = this.sourceImage_.getState();
-    if (sourceState == ol.Image.State.LOADED ||
-        sourceState == ol.Image.State.ERROR) {
-      this.reproject_();
-    } else {
-      this.sourceListenerKey_ = ol.events.listen(this.sourceImage_,
-          ol.events.EventType.CHANGE, function(e) {
-            var sourceState = this.sourceImage_.getState();
-            if (sourceState == ol.Image.State.LOADED ||
-                sourceState == ol.Image.State.ERROR) {
-              this.unlistenSource_();
-              this.reproject_();
-            }
-          }, this);
-      this.sourceImage_.load();
-    }
-  }
-};
-
-
-/**
- * @private
- */
-ol.reproj.Image.prototype.unlistenSource_ = function() {
-  ol.DEBUG && console.assert(this.sourceListenerKey_,
-      'this.sourceListenerKey_ should not be null');
-  ol.events.unlistenByKey(/** @type {!ol.EventsKey} */ (this.sourceListenerKey_));
-  this.sourceListenerKey_ = null;
-};
-
-goog.provide('ol.source.Source');
-
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.Object');
-goog.require('ol.proj');
-goog.require('ol.source.State');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for {@link ol.layer.Layer} sources.
- *
- * A generic `change` event is triggered when the state of the source changes.
- *
- * @constructor
- * @extends {ol.Object}
- * @param {ol.SourceSourceOptions} options Source options.
- * @api stable
- */
-ol.source.Source = function(options) {
-
-  ol.Object.call(this);
-
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.projection_ = ol.proj.get(options.projection);
-
-  /**
-   * @private
-   * @type {Array.<ol.Attribution>}
-   */
-  this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions);
-
-  /**
-   * @private
-   * @type {string|olx.LogoOptions|undefined}
-   */
-  this.logo_ = options.logo;
-
-  /**
-   * @private
-   * @type {ol.source.State}
-   */
-  this.state_ = options.state !== undefined ?
-      options.state : ol.source.State.READY;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false;
-
-};
-ol.inherits(ol.source.Source, ol.Object);
-
-/**
- * Turns various ways of defining an attribution to an array of `ol.Attributions`.
- *
- * @param {ol.AttributionLike|undefined}
- *     attributionLike The attributions as string, array of strings,
- *     `ol.Attribution`, array of `ol.Attribution` or undefined.
- * @return {Array.<ol.Attribution>} The array of `ol.Attribution` or null if
- *     `undefined` was given.
- */
-ol.source.Source.toAttributionsArray_ = function(attributionLike) {
-  if (typeof attributionLike === 'string') {
-    return [new ol.Attribution({html: attributionLike})];
-  } else if (attributionLike instanceof ol.Attribution) {
-    return [attributionLike];
-  } else if (Array.isArray(attributionLike)) {
-    var len = attributionLike.length;
-    var attributions = new Array(len);
-    for (var i = 0; i < len; i++) {
-      var item = attributionLike[i];
-      if (typeof item === 'string') {
-        attributions[i] = new ol.Attribution({html: item});
-      } else {
-        attributions[i] = item;
-      }
-    }
-    return attributions;
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
- * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
- *     callback.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction;
-
-
-/**
- * Get the attributions of the source.
- * @return {Array.<ol.Attribution>} Attributions.
- * @api stable
- */
-ol.source.Source.prototype.getAttributions = function() {
-  return this.attributions_;
-};
-
-
-/**
- * Get the logo of the source.
- * @return {string|olx.LogoOptions|undefined} Logo.
- * @api stable
- */
-ol.source.Source.prototype.getLogo = function() {
-  return this.logo_;
-};
-
-
-/**
- * Get the projection of the source.
- * @return {ol.proj.Projection} Projection.
- * @api
- */
-ol.source.Source.prototype.getProjection = function() {
-  return this.projection_;
-};
-
-
-/**
- * @abstract
- * @return {Array.<number>|undefined} Resolutions.
- */
-ol.source.Source.prototype.getResolutions = function() {};
-
-
-/**
- * Get the state of the source, see {@link ol.source.State} for possible states.
- * @return {ol.source.State} State.
- * @api
- */
-ol.source.Source.prototype.getState = function() {
-  return this.state_;
-};
-
-
-/**
- * @return {boolean|undefined} Wrap X.
- */
-ol.source.Source.prototype.getWrapX = function() {
-  return this.wrapX_;
-};
-
-
-/**
- * Refreshes the source and finally dispatches a 'change' event.
- * @api
- */
-ol.source.Source.prototype.refresh = function() {
-  this.changed();
-};
-
-
-/**
- * Set the attributions of the source.
- * @param {ol.AttributionLike|undefined} attributions Attributions.
- *     Can be passed as `string`, `Array<string>`, `{@link ol.Attribution}`,
- *     `Array<{@link ol.Attribution}>` or `undefined`.
- * @api
- */
-ol.source.Source.prototype.setAttributions = function(attributions) {
-  this.attributions_ = ol.source.Source.toAttributionsArray_(attributions);
-  this.changed();
-};
-
-
-/**
- * Set the logo of the source.
- * @param {string|olx.LogoOptions|undefined} logo Logo.
- */
-ol.source.Source.prototype.setLogo = function(logo) {
-  this.logo_ = logo;
-};
-
-
-/**
- * Set the state of the source.
- * @param {ol.source.State} state State.
- * @protected
- */
-ol.source.Source.prototype.setState = function(state) {
-  this.state_ = state;
-  this.changed();
-};
-
-goog.provide('ol.source.Image');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.array');
-goog.require('ol.events.Event');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.reproj.Image');
-goog.require('ol.source.Source');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for sources providing a single image.
- *
- * @constructor
- * @extends {ol.source.Source}
- * @param {ol.SourceImageOptions} options Single image source options.
- * @api
- */
-ol.source.Image = function(options) {
-
-  ol.source.Source.call(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    projection: options.projection,
-    state: options.state
-  });
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.resolutions_ = options.resolutions !== undefined ?
-      options.resolutions : null;
-  ol.DEBUG && console.assert(!this.resolutions_ ||
-      ol.array.isSorted(this.resolutions_,
-          function(a, b) {
-            return b - a;
-          }, true), 'resolutions must be null or sorted in descending order');
-
-
-  /**
-   * @private
-   * @type {ol.reproj.Image}
-   */
-  this.reprojectedImage_ = null;
-
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.reprojectedRevision_ = 0;
-
-};
-ol.inherits(ol.source.Image, ol.source.Source);
-
-
-/**
- * @return {Array.<number>} Resolutions.
- */
-ol.source.Image.prototype.getResolutions = function() {
-  return this.resolutions_;
-};
-
-
-/**
- * @protected
- * @param {number} resolution Resolution.
- * @return {number} Resolution.
- */
-ol.source.Image.prototype.findNearestResolution = function(resolution) {
-  if (this.resolutions_) {
-    var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0);
-    resolution = this.resolutions_[idx];
-  }
-  return resolution;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.ImageBase} Single image.
- */
-ol.source.Image.prototype.getImage = function(extent, resolution, pixelRatio, projection) {
-  var sourceProjection = this.getProjection();
-  if (!ol.ENABLE_RASTER_REPROJECTION ||
-      !sourceProjection ||
-      !projection ||
-      ol.proj.equivalent(sourceProjection, projection)) {
-    if (sourceProjection) {
-      projection = sourceProjection;
-    }
-    return this.getImageInternal(extent, resolution, pixelRatio, projection);
-  } else {
-    if (this.reprojectedImage_) {
-      if (this.reprojectedRevision_ == this.getRevision() &&
-          ol.proj.equivalent(
-              this.reprojectedImage_.getProjection(), projection) &&
-          this.reprojectedImage_.getResolution() == resolution &&
-          this.reprojectedImage_.getPixelRatio() == pixelRatio &&
-          ol.extent.equals(this.reprojectedImage_.getExtent(), extent)) {
-        return this.reprojectedImage_;
-      }
-      this.reprojectedImage_.dispose();
-      this.reprojectedImage_ = null;
-    }
-
-    this.reprojectedImage_ = new ol.reproj.Image(
-        sourceProjection, projection, extent, resolution, pixelRatio,
-        function(extent, resolution, pixelRatio) {
-          return this.getImageInternal(extent, resolution,
-              pixelRatio, sourceProjection);
-        }.bind(this));
-    this.reprojectedRevision_ = this.getRevision();
-
-    return this.reprojectedImage_;
-  }
-};
-
-
-/**
- * @abstract
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.ImageBase} Single image.
- * @protected
- */
-ol.source.Image.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {};
-
-
-/**
- * Handle image change events.
- * @param {ol.events.Event} event Event.
- * @protected
- */
-ol.source.Image.prototype.handleImageChange = function(event) {
-  var image = /** @type {ol.Image} */ (event.target);
-  switch (image.getState()) {
-    case ol.Image.State.LOADING:
-      this.dispatchEvent(
-          new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADSTART,
-              image));
-      break;
-    case ol.Image.State.LOADED:
-      this.dispatchEvent(
-          new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADEND,
-              image));
-      break;
-    case ol.Image.State.ERROR:
-      this.dispatchEvent(
-          new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADERROR,
-              image));
-      break;
-    default:
-      // pass
-  }
-};
-
-
-/**
- * Default image load function for image sources that use ol.Image image
- * instances.
- * @param {ol.Image} image Image.
- * @param {string} src Source.
- */
-ol.source.Image.defaultImageLoadFunction = function(image, src) {
-  image.getImage().src = src;
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.source.Image} instances are instances of this
- * type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.source.ImageEvent}
- * @param {string} type Type.
- * @param {ol.Image} image The image.
- */
-ol.source.Image.Event = function(type, image) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The image related to the event.
-   * @type {ol.Image}
-   * @api
-   */
-  this.image = image;
-
-};
-ol.inherits(ol.source.Image.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.source.Image.EventType = {
-
-  /**
-   * Triggered when an image starts loading.
-   * @event ol.source.Image.Event#imageloadstart
-   * @api
-   */
-  IMAGELOADSTART: 'imageloadstart',
-
-  /**
-   * Triggered when an image finishes loading.
-   * @event ol.source.Image.Event#imageloadend
-   * @api
-   */
-  IMAGELOADEND: 'imageloadend',
-
-  /**
-   * Triggered if image loading results in an error.
-   * @event ol.source.Image.Event#imageloaderror
-   * @api
-   */
-  IMAGELOADERROR: 'imageloaderror'
-
-};
-
-goog.provide('ol.source.ImageCanvas');
-
-goog.require('ol');
-goog.require('ol.ImageCanvas');
-goog.require('ol.extent');
-goog.require('ol.source.Image');
-
-
-/**
- * @classdesc
- * Base class for image sources where a canvas element is the image.
- *
- * @constructor
- * @extends {ol.source.Image}
- * @param {olx.source.ImageCanvasOptions} options Constructor options.
- * @api
- */
-ol.source.ImageCanvas = function(options) {
-
-  ol.source.Image.call(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection,
-    resolutions: options.resolutions,
-    state: options.state
-  });
-
-  /**
-   * @private
-   * @type {ol.CanvasFunctionType}
-   */
-  this.canvasFunction_ = options.canvasFunction;
-
-  /**
-   * @private
-   * @type {ol.ImageCanvas}
-   */
-  this.canvas_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.ratio_ = options.ratio !== undefined ?
-      options.ratio : 1.5;
-
-};
-ol.inherits(ol.source.ImageCanvas, ol.source.Image);
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageCanvas.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
-  resolution = this.findNearestResolution(resolution);
-
-  var canvas = this.canvas_;
-  if (canvas &&
-      this.renderedRevision_ == this.getRevision() &&
-      canvas.getResolution() == resolution &&
-      canvas.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(canvas.getExtent(), extent)) {
-    return canvas;
-  }
-
-  extent = extent.slice();
-  ol.extent.scaleFromCenter(extent, this.ratio_);
-  var width = ol.extent.getWidth(extent) / resolution;
-  var height = ol.extent.getHeight(extent) / resolution;
-  var size = [width * pixelRatio, height * pixelRatio];
-
-  var canvasElement = this.canvasFunction_(
-      extent, resolution, pixelRatio, size, projection);
-  if (canvasElement) {
-    canvas = new ol.ImageCanvas(extent, resolution, pixelRatio,
-        this.getAttributions(), canvasElement);
-  }
-  this.canvas_ = canvas;
-  this.renderedRevision_ = this.getRevision();
-
-  return canvas;
-};
-
-goog.provide('ol.source.ImageVector');
-
-goog.require('ol');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.renderer.vector');
-goog.require('ol.source.ImageCanvas');
-goog.require('ol.style.Style');
-goog.require('ol.transform');
-
-
-/**
- * @classdesc
- * An image source whose images are canvas elements into which vector features
- * read from a vector source (`ol.source.Vector`) are drawn. An
- * `ol.source.ImageVector` object is to be used as the `source` of an image
- * layer (`ol.layer.Image`). Image layers are rotated, scaled, and translated,
- * as opposed to being re-rendered, during animations and interactions. So, like
- * any other image layer, an image layer configured with an
- * `ol.source.ImageVector` will exhibit this behaviour. This is in contrast to a
- * vector layer, where vector features are re-drawn during animations and
- * interactions.
- *
- * @constructor
- * @extends {ol.source.ImageCanvas}
- * @param {olx.source.ImageVectorOptions} options Options.
- * @api
- */
-ol.source.ImageVector = function(options) {
-
-  /**
-   * @private
-   * @type {ol.source.Vector}
-   */
-  this.source_ = options.source;
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.transform_ = ol.transform.create();
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.canvasContext_ = ol.dom.createCanvasContext2D();
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.canvasSize_ = [0, 0];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderBuffer_ = options.renderBuffer == undefined ? 100 : options.renderBuffer;
-
-  /**
-   * @private
-   * @type {ol.render.canvas.ReplayGroup}
-   */
-  this.replayGroup_ = null;
-
-  ol.source.ImageCanvas.call(this, {
-    attributions: options.attributions,
-    canvasFunction: this.canvasFunctionInternal_.bind(this),
-    logo: options.logo,
-    projection: options.projection,
-    ratio: options.ratio,
-    resolutions: options.resolutions,
-    state: this.source_.getState()
-  });
-
-  /**
-   * User provided style.
-   * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
-   * @private
-   */
-  this.style_ = null;
-
-  /**
-   * Style function for use within the library.
-   * @type {ol.StyleFunction|undefined}
-   * @private
-   */
-  this.styleFunction_ = undefined;
-
-  this.setStyle(options.style);
-
-  ol.events.listen(this.source_, ol.events.EventType.CHANGE,
-      this.handleSourceChange_, this);
-
-};
-ol.inherits(ol.source.ImageVector, ol.source.ImageCanvas);
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Size} size Size.
- * @param {ol.proj.Projection} projection Projection;
- * @return {HTMLCanvasElement} Canvas element.
- * @private
- */
-ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resolution, pixelRatio, size, projection) {
-
-  var replayGroup = new ol.render.canvas.ReplayGroup(
-      ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
-      resolution, this.source_.getOverlaps(), this.renderBuffer_);
-
-  this.source_.loadFeatures(extent, resolution, projection);
-
-  var loading = false;
-  this.source_.forEachFeatureInExtent(extent,
-      /**
-       * @param {ol.Feature} feature Feature.
-       */
-      function(feature) {
-        loading = loading ||
-            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
-      }, this);
-  replayGroup.finish();
-
-  if (loading) {
-    return null;
-  }
-
-  if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) {
-    this.canvasContext_.canvas.width = size[0];
-    this.canvasContext_.canvas.height = size[1];
-    this.canvasSize_[0] = size[0];
-    this.canvasSize_[1] = size[1];
-  } else {
-    this.canvasContext_.clearRect(0, 0, size[0], size[1]);
-  }
-
-  var transform = this.getTransform_(ol.extent.getCenter(extent),
-      resolution, pixelRatio, size);
-  replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {});
-
-  this.replayGroup_ = replayGroup;
-
-  return this.canvasContext_.canvas;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
-    coordinate, resolution, rotation, skippedFeatureUids, callback) {
-  if (!this.replayGroup_) {
-    return undefined;
-  } else {
-    /** @type {Object.<string, boolean>} */
-    var features = {};
-    return this.replayGroup_.forEachFeatureAtCoordinate(
-        coordinate, resolution, 0, skippedFeatureUids,
-        /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @return {?} Callback result.
-         */
-        function(feature) {
-          var key = ol.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback(feature);
-          }
-        });
-  }
-};
-
-
-/**
- * Get a reference to the wrapped source.
- * @return {ol.source.Vector} Source.
- * @api
- */
-ol.source.ImageVector.prototype.getSource = function() {
-  return this.source_;
-};
-
-
-/**
- * Get the style for features.  This returns whatever was passed to the `style`
- * option at construction or to the `setStyle` method.
- * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
- *     Layer style.
- * @api stable
- */
-ol.source.ImageVector.prototype.getStyle = function() {
-  return this.style_;
-};
-
-
-/**
- * Get the style function.
- * @return {ol.StyleFunction|undefined} Layer style function.
- * @api stable
- */
-ol.source.ImageVector.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
-};
-
-
-/**
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Size} size Size.
- * @return {!ol.Transform} Transform.
- * @private
- */
-ol.source.ImageVector.prototype.getTransform_ = function(center, resolution, pixelRatio, size) {
-  var dx1 = size[0] / 2;
-  var dy1 = size[1] / 2;
-  var sx = pixelRatio / resolution;
-  var sy = -sx;
-  var dx2 = -center[0];
-  var dy2 = -center[1];
-
-  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2);
-};
-
-
-/**
- * Handle changes in image style state.
- * @param {ol.events.Event} event Image style change event.
- * @private
- */
-ol.source.ImageVector.prototype.handleImageChange_ = function(event) {
-  this.changed();
-};
-
-
-/**
- * @private
- */
-ol.source.ImageVector.prototype.handleSourceChange_ = function() {
-  // setState will trigger a CHANGE event, so we always rely
-  // change events by calling setState.
-  this.setState(this.source_.getState());
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
- * @return {boolean} `true` if an image is loading.
- * @private
- */
-ol.source.ImageVector.prototype.renderFeature_ = function(feature, resolution, pixelRatio, replayGroup) {
-  var styles;
-  var styleFunction = feature.getStyleFunction();
-  if (styleFunction) {
-    styles = styleFunction.call(feature, resolution);
-  } else if (this.styleFunction_) {
-    styles = this.styleFunction_(feature, resolution);
-  }
-  if (!styles) {
-    return false;
-  }
-  var i, ii, loading = false;
-  if (!Array.isArray(styles)) {
-    styles = [styles];
-  }
-  for (i = 0, ii = styles.length; i < ii; ++i) {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles[i],
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleImageChange_, this) || loading;
-  }
-  return loading;
-};
-
-
-/**
- * Set the style for features.  This can be a single style object, an array
- * of styles, or a function that takes a feature and resolution and returns
- * an array of styles. If it is `undefined` the default style is used. If
- * it is `null` the layer has no style (a `null` style), so only features
- * that have their own styles will be rendered in the layer. See
- * {@link ol.style} for information on the default style.
- * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined}
- *     style Layer style.
- * @api stable
- */
-ol.source.ImageVector.prototype.setStyle = function(style) {
-  this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction;
-  this.styleFunction_ = !style ?
-      undefined : ol.style.Style.createFunction(this.style_);
-  this.changed();
-};
-
-goog.provide('ol.renderer.canvas.ImageLayer');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.functions');
-goog.require('ol.proj');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.source.ImageVector');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.canvas.Layer}
- * @param {ol.layer.Image} imageLayer Single image layer.
- */
-ol.renderer.canvas.ImageLayer = function(imageLayer) {
-
-  ol.renderer.canvas.Layer.call(this, imageLayer);
-
-  /**
-   * @private
-   * @type {?ol.ImageBase}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.imageTransform_ = ol.transform.create();
-
-  /**
-   * @private
-   * @type {?ol.Transform}
-   */
-  this.imageTransformInv_ = null;
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.hitCanvasContext_ = null;
-
-};
-ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer);
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
-  var layer = this.getLayer();
-  var source = layer.getSource();
-  var resolution = frameState.viewState.resolution;
-  var rotation = frameState.viewState.rotation;
-  var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtCoordinate(
-      coordinate, resolution, rotation, skippedFeatureUids,
-      /**
-       * @param {ol.Feature|ol.render.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        return callback.call(thisArg, feature, layer);
-      });
-};
-
-
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
- */
-ol.renderer.canvas.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
-  if (!this.getImage()) {
-    return undefined;
-  }
-
-  if (this.getLayer().getSource() instanceof ol.source.ImageVector) {
-    // for ImageVector sources use the original hit-detection logic,
-    // so that for example also transparent polygons are detected
-    var coordinate = ol.transform.apply(
-        frameState.pixelToCoordinateTransform, pixel.slice());
-    var hasFeature = this.forEachFeatureAtCoordinate(
-        coordinate, frameState, ol.functions.TRUE, this);
-
-    if (hasFeature) {
-      return callback.call(thisArg, this.getLayer(), null);
-    } else {
-      return undefined;
-    }
-  } else {
-    // for all other image sources directly check the image
-    if (!this.imageTransformInv_) {
-      this.imageTransformInv_ = ol.transform.invert(this.imageTransform_.slice());
-    }
-
-    var pixelOnCanvas =
-        this.getPixelOnCanvas(pixel, this.imageTransformInv_);
-
-    if (!this.hitCanvasContext_) {
-      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
-    }
-
-    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
-    this.hitCanvasContext_.drawImage(
-        this.getImage(), pixelOnCanvas[0], pixelOnCanvas[1], 1, 1, 0, 0, 1, 1);
-
-    var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
-    if (imageData[3] > 0) {
-      return callback.call(thisArg, this.getLayer(),  imageData);
-    } else {
-      return undefined;
-    }
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
-  return !this.image_ ? null : this.image_.getImage();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() {
-  return this.imageTransform_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) {
-
-  var pixelRatio = frameState.pixelRatio;
-  var viewState = frameState.viewState;
-  var viewCenter = viewState.center;
-  var viewResolution = viewState.resolution;
-
-  var image;
-  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
-  var imageSource = imageLayer.getSource();
-
-  var hints = frameState.viewHints;
-
-  var renderedExtent = frameState.extent;
-  if (layerState.extent !== undefined) {
-    renderedExtent = ol.extent.getIntersection(
-        renderedExtent, layerState.extent);
-  }
-
-  if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] &&
-      !ol.extent.isEmpty(renderedExtent)) {
-    var projection = viewState.projection;
-    if (!ol.ENABLE_RASTER_REPROJECTION) {
-      var sourceProjection = imageSource.getProjection();
-      if (sourceProjection) {
-        ol.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection),
-            'projection and sourceProjection are equivalent');
-        projection = sourceProjection;
-      }
-    }
-    image = imageSource.getImage(
-        renderedExtent, viewResolution, pixelRatio, projection);
-    if (image) {
-      var loaded = this.loadImage(image);
-      if (loaded) {
-        this.image_ = image;
-      }
-    }
-  }
-
-  if (this.image_) {
-    image = this.image_;
-    var imageExtent = image.getExtent();
-    var imageResolution = image.getResolution();
-    var imagePixelRatio = image.getPixelRatio();
-    var scale = pixelRatio * imageResolution /
-        (viewResolution * imagePixelRatio);
-    var transform = ol.transform.reset(this.imageTransform_);
-    ol.transform.translate(transform,
-        pixelRatio * frameState.size[0] / 2,
-        pixelRatio * frameState.size[1] / 2);
-    ol.transform.scale(transform, scale, scale);
-    ol.transform.translate(transform,
-        imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
-        imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
-    this.imageTransformInv_ = null;
-    this.updateAttributions(frameState.attributions, image.getAttributions());
-    this.updateLogos(frameState, imageSource);
-  }
-
-  return !!this.image_;
-};
-
-// FIXME find correct globalCompositeOperation
-
-goog.provide('ol.renderer.canvas.TileLayer');
-
-goog.require('ol');
-goog.require('ol.transform');
-goog.require('ol.TileRange');
-goog.require('ol.Tile');
-goog.require('ol.array');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.render.canvas');
-goog.require('ol.render.Event');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.size');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.canvas.Layer}
- * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer.
- */
-ol.renderer.canvas.TileLayer = function(tileLayer) {
-
-  ol.renderer.canvas.Layer.call(this, tileLayer);
-
-  /**
-   * @protected
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context = ol.dom.createCanvasContext2D();
-
-  /**
-   * @protected
-   * @type {!Array.<ol.Tile|undefined>}
-   */
-  this.renderedTiles = [];
-
-  /**
-   * @protected
-   * @type {ol.Extent}
-   */
-  this.tmpExtent = ol.extent.createEmpty();
-
-  /**
-   * @private
-   * @type {ol.TileCoord}
-   */
-  this.tmpTileCoord_ = [0, 0, 0];
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.imageTransform_ = ol.transform.create();
-
-  /**
-   * @protected
-   * @type {number}
-   */
-  this.zDirection = 0;
-
-};
-ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer);
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.TileLayer.prototype.composeFrame = function(
-    frameState, layerState, context) {
-  var transform = this.getTransform(frameState, 0);
-  this.dispatchPreComposeEvent(context, frameState, transform);
-  this.renderTileImages(context, frameState, layerState);
-  this.dispatchPostComposeEvent(context, frameState, transform);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(
-    frameState, layerState) {
-
-  var pixelRatio = frameState.pixelRatio;
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
-
-  var tileLayer = this.getLayer();
-  var tileSource = /** @type {ol.source.Tile} */ (tileLayer.getSource());
-  var tileGrid = tileSource.getTileGridForProjection(projection);
-  var z = tileGrid.getZForResolution(viewState.resolution, this.zDirection);
-  var tileResolution = tileGrid.getResolution(z);
-  var extent = frameState.extent;
-
-  if (layerState.extent !== undefined) {
-    extent = ol.extent.getIntersection(extent, layerState.extent);
-  }
-  if (ol.extent.isEmpty(extent)) {
-    // Return false to prevent the rendering of the layer.
-    return false;
-  }
-
-  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
-      extent, tileResolution);
-
-  /**
-   * @type {Object.<number, Object.<string, ol.Tile>>}
-   */
-  var tilesToDrawByZ = {};
-  tilesToDrawByZ[z] = {};
-
-  var findLoadedTiles = this.createLoadedTileFinder(
-      tileSource, projection, tilesToDrawByZ);
-
-  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-
-  var tmpExtent = this.tmpExtent;
-  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-  var childTileRange, fullyLoaded, tile, x, y;
-  var drawableTile = (
-      /**
-       * @param {!ol.Tile} tile Tile.
-       * @return {boolean} Tile is selected.
-       */
-      function(tile) {
-        var tileState = tile.getState();
-        return tileState == ol.Tile.State.LOADED ||
-            tileState == ol.Tile.State.EMPTY ||
-            tileState == ol.Tile.State.ERROR && !useInterimTilesOnError;
-      });
-  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-      if (!drawableTile(tile)) {
-        tile = tile.getInterimTile();
-      }
-      if (drawableTile(tile)) {
-        tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
-        continue;
-      }
-      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
-          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
-      if (!fullyLoaded) {
-        childTileRange = tileGrid.getTileCoordChildTileRange(
-            tile.tileCoord, tmpTileRange, tmpExtent);
-        if (childTileRange) {
-          findLoadedTiles(z + 1, childTileRange);
-        }
-      }
-
-    }
-  }
-
-  /** @type {Array.<number>} */
-  var zs = Object.keys(tilesToDrawByZ).map(Number);
-  zs.sort(ol.array.numberSafeCompareFunction);
-  var renderables = this.renderedTiles;
-  renderables.length = 0;
-  var i, ii, currentZ, tileCoordKey, tilesToDraw;
-  for (i = 0, ii = zs.length; i < ii; ++i) {
-    currentZ = zs[i];
-    tilesToDraw = tilesToDrawByZ[currentZ];
-    for (tileCoordKey in tilesToDraw) {
-      tile = tilesToDraw[tileCoordKey];
-      if (tile.getState() == ol.Tile.State.LOADED) {
-        renderables.push(tile);
-      }
-    }
-  }
-
-  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
-  this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
-      projection, extent, z, tileLayer.getPreload());
-  this.scheduleExpireCache(frameState, tileSource);
-  this.updateLogos(frameState, tileSource);
-
-  return true;
-};
-
-
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
- */
-ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel = function(
-    pixel, frameState, callback, thisArg) {
-  var canvas = this.context.canvas;
-  var size = frameState.size;
-  var pixelRatio = frameState.pixelRatio;
-  canvas.width = size[0] * pixelRatio;
-  canvas.height = size[1] * pixelRatio;
-  this.composeFrame(frameState, this.getLayer().getLayerState(), this.context);
-
-  var imageData = this.context.getImageData(
-      pixel[0], pixel[1], 1, 1).data;
-
-  if (imageData[3] > 0) {
-    return callback.call(thisArg, this.getLayer(),  imageData);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @protected
- */
-ol.renderer.canvas.TileLayer.prototype.renderTileImages = function(context, frameState, layerState) {
-  var tilesToDraw = this.renderedTiles;
-  if (tilesToDraw.length == 0) {
-    return;
-  }
-
-  var pixelRatio = frameState.pixelRatio;
-  var viewState = frameState.viewState;
-  var center = viewState.center;
-  var projection = viewState.projection;
-  var resolution = viewState.resolution;
-  var rotation = viewState.rotation;
-  var size = frameState.size;
-  var offsetX = Math.round(pixelRatio * size[0] / 2);
-  var offsetY = Math.round(pixelRatio * size[1] / 2);
-  var pixelScale = pixelRatio / resolution;
-  var layer = this.getLayer();
-  var source = /** @type {ol.source.Tile} */ (layer.getSource());
-  var tileGutter = source.getTilePixelRatio(pixelRatio) * source.getGutter(projection);
-  var tileGrid = source.getTileGridForProjection(projection);
-
-  var hasRenderListeners = layer.hasListener(ol.render.Event.Type.RENDER);
-  var renderContext = context;
-  var drawScale = 1;
-  var drawOffsetX, drawOffsetY, drawSize;
-  if (rotation || hasRenderListeners) {
-    renderContext = this.context;
-    var renderCanvas = renderContext.canvas;
-    drawScale = source.getTilePixelRatio(pixelRatio) / pixelRatio;
-    var width = context.canvas.width * drawScale;
-    var height = context.canvas.height * drawScale;
-    // Make sure the canvas is big enough for all possible rotation angles
-    drawSize = Math.round(Math.sqrt(width * width + height * height));
-    if (renderCanvas.width != drawSize) {
-      renderCanvas.width = renderCanvas.height = drawSize;
-    } else {
-      renderContext.clearRect(0, 0, drawSize, drawSize);
-    }
-    drawOffsetX = (drawSize - width) / 2 / drawScale;
-    drawOffsetY = (drawSize - height) / 2 / drawScale;
-    pixelScale *= drawScale;
-    offsetX = Math.round(drawScale * (offsetX + drawOffsetX));
-    offsetY = Math.round(drawScale * (offsetY + drawOffsetY));
-  }
-  // for performance reasons, context.save / context.restore is not used
-  // to save and restore the transformation matrix and the opacity.
-  // see http://jsperf.com/context-save-restore-versus-variable
-  var alpha = renderContext.globalAlpha;
-  renderContext.globalAlpha = layerState.opacity;
-
-  // Origin of the lowest resolution tile that contains the map center. We will
-  // try to use the same origin for all resolutions for pixel-perfect tile
-  // alignment across resolutions.
-  var lowResTileCoord = tilesToDraw[0].getTileCoord();
-  var minZOrigin = ol.extent.getBottomLeft(tileGrid.getTileCoordExtent(
-      tileGrid.getTileCoordForCoordAndZ(center,
-          lowResTileCoord[0], this.tmpTileCoord_), this.tmpExtent));
-  var maxZ = tilesToDraw[tilesToDraw.length - 1].getTileCoord()[0];
-  var maxZResolution = tileGrid.getResolution(maxZ);
-  var maxZTileSize = ol.size.toSize(tileGrid.getTileSize(maxZ));
-
-  var pixelExtents;
-  var opaque = source.getOpaque(projection) && layerState.opacity == 1;
-  if (!opaque) {
-    tilesToDraw.reverse();
-    pixelExtents = [];
-  }
-
-  var extent = layerState.extent;
-  var clipped = extent !== undefined;
-  if (clipped) {
-    var topLeft = ol.extent.getTopLeft(/** @type {ol.Extent} */ (extent));
-    var topRight = ol.extent.getTopRight(/** @type {ol.Extent} */ (extent));
-    var bottomRight = ol.extent.getBottomRight(/** @type {ol.Extent} */ (extent));
-    var bottomLeft = ol.extent.getBottomLeft(/** @type {ol.Extent} */ (extent));
-
-    ol.transform.apply(frameState.coordinateToPixelTransform, topLeft);
-    ol.transform.apply(frameState.coordinateToPixelTransform, topRight);
-    ol.transform.apply(frameState.coordinateToPixelTransform, bottomRight);
-    ol.transform.apply(frameState.coordinateToPixelTransform, bottomLeft);
-
-    var ox = drawOffsetX || 0;
-    var oy = drawOffsetY || 0;
-    renderContext.save();
-    var cx = (renderContext.canvas.width) / 2;
-    var cy = (renderContext.canvas.height) / 2;
-    ol.render.canvas.rotateAtOffset(renderContext, -rotation, cx, cy);
-    renderContext.beginPath();
-    renderContext.moveTo(drawScale * (topLeft[0] * pixelRatio + ox),
-        drawScale * (topLeft[1] * pixelRatio + oy));
-    renderContext.lineTo(drawScale * (topRight[0] * pixelRatio + ox),
-        drawScale * (topRight[1] * pixelRatio + oy));
-    renderContext.lineTo(drawScale * (bottomRight[0] * pixelRatio + ox),
-        drawScale * (bottomRight[1] * pixelRatio + oy));
-    renderContext.lineTo(drawScale * (bottomLeft[0] * pixelRatio + ox),
-        drawScale * (bottomLeft[1] * pixelRatio + oy));
-    renderContext.clip();
-    ol.render.canvas.rotateAtOffset(renderContext, rotation, cx, cy);
-  }
-
-  for (var i = 0, ii = tilesToDraw.length; i < ii; ++i) {
-    var tile = tilesToDraw[i];
-    var tileCoord = tile.getTileCoord();
-    var currentZ = tileCoord[0];
-    var tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ));
-    // Calculate all insert points by tile widths from a common origin to avoid
-    // gaps caused by rounding
-    var originTileCoord = tileGrid.getTileCoordForCoordAndZ(minZOrigin, currentZ, this.tmpTileCoord_);
-    var origin = ol.extent.getBottomLeft(tileGrid.getTileCoordExtent(originTileCoord, this.tmpExtent));
-    // Calculate tile width and height by a tile size factor from the highest
-    // resolution tile size to avoid gaps when combining tiles from different
-    // resolutions
-    var resolutionFactor = tileGrid.getResolution(currentZ) / maxZResolution;
-    var tileSizeFactorW = tileSize[0] / maxZTileSize[0] * resolutionFactor;
-    var tileSizeFactorH = tileSize[1] / maxZTileSize[1] * resolutionFactor;
-    var w = Math.round(maxZTileSize[0] / resolution * maxZResolution * pixelRatio * drawScale) * tileSizeFactorW;
-    var h = Math.round(maxZTileSize[1] / resolution * maxZResolution * pixelRatio * drawScale) * tileSizeFactorH;
-    var left = (tileCoord[1] - originTileCoord[1]) * w +
-        offsetX + Math.round((origin[0] - center[0]) * pixelScale);
-    var top = (originTileCoord[2] - tileCoord[2] - 1) * h +
-        offsetY + Math.round((center[1] - origin[1]) * pixelScale);
-    if (!opaque) {
-      var pixelExtent = [left, top, left + w, top + h];
-      // Create a clip mask for regions in this low resolution tile that are
-      // already filled by a higher resolution tile
-      renderContext.save();
-      for (var j = 0, jj = pixelExtents.length; j < jj; ++j) {
-        var clipExtent = pixelExtents[j];
-        if (ol.extent.intersects(pixelExtent, clipExtent)) {
-          renderContext.beginPath();
-          // counter-clockwise (outer ring) for current tile
-          renderContext.moveTo(pixelExtent[0], pixelExtent[1]);
-          renderContext.lineTo(pixelExtent[0], pixelExtent[3]);
-          renderContext.lineTo(pixelExtent[2], pixelExtent[3]);
-          renderContext.lineTo(pixelExtent[2], pixelExtent[1]);
-          // clockwise (inner ring) for higher resolution tile
-          renderContext.moveTo(clipExtent[0], clipExtent[1]);
-          renderContext.lineTo(clipExtent[2], clipExtent[1]);
-          renderContext.lineTo(clipExtent[2], clipExtent[3]);
-          renderContext.lineTo(clipExtent[0], clipExtent[3]);
-          renderContext.closePath();
-          renderContext.clip();
-        }
-      }
-      pixelExtents.push(pixelExtent);
-    }
-    var tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection);
-    renderContext.drawImage(tile.getImage(), tileGutter, tileGutter,
-        tilePixelSize[0], tilePixelSize[1], left, top, w, h);
-    if (!opaque) {
-      renderContext.restore();
-    }
-  }
-
-  if (clipped) {
-    renderContext.restore();
-  }
-
-  if (hasRenderListeners) {
-    var dX = drawOffsetX - offsetX / drawScale + offsetX;
-    var dY = drawOffsetY - offsetY / drawScale + offsetY;
-    var imageTransform = ol.transform.compose(this.imageTransform_,
-        drawSize / 2 - dX, drawSize / 2 - dY,
-        pixelScale, -pixelScale,
-        -rotation,
-        -center[0] + dX / pixelScale, -center[1] - dY / pixelScale);
-    this.dispatchRenderEvent(renderContext, frameState, imageTransform);
-  }
-  if (rotation || hasRenderListeners) {
-    context.drawImage(renderContext.canvas, -Math.round(drawOffsetX),
-        -Math.round(drawOffsetY), drawSize / drawScale, drawSize / drawScale);
-  }
-  renderContext.globalAlpha = alpha;
-};
-
-
-/**
- * @function
- * @return {ol.layer.Tile|ol.layer.VectorTile}
- */
-ol.renderer.canvas.TileLayer.prototype.getLayer;
-
-goog.provide('ol.renderer.canvas.VectorLayer');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.render.Event');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.renderer.canvas.Layer');
-goog.require('ol.renderer.vector');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.canvas.Layer}
- * @param {ol.layer.Vector} vectorLayer Vector layer.
- */
-ol.renderer.canvas.VectorLayer = function(vectorLayer) {
-
-  ol.renderer.canvas.Layer.call(this, vectorLayer);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.dirty_ = false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedResolution_ = NaN;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.renderedExtent_ = ol.extent.createEmpty();
-
-  /**
-   * @private
-   * @type {function(ol.Feature, ol.Feature): number|null}
-   */
-  this.renderedRenderOrder_ = null;
-
-  /**
-   * @private
-   * @type {ol.render.canvas.ReplayGroup}
-   */
-  this.replayGroup_ = null;
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D();
-
-};
-ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) {
-
-  var extent = frameState.extent;
-  var pixelRatio = frameState.pixelRatio;
-  var skippedFeatureUids = layerState.managed ?
-      frameState.skippedFeatureUids : {};
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
-  var rotation = viewState.rotation;
-  var projectionExtent = projection.getExtent();
-  var vectorSource = /** @type {ol.source.Vector} */ (this.getLayer().getSource());
-
-  var transform = this.getTransform(frameState, 0);
-
-  this.dispatchPreComposeEvent(context, frameState, transform);
-
-  // clipped rendering if layer extent is set
-  var clipExtent = layerState.extent;
-  var clipped = clipExtent !== undefined;
-  if (clipped) {
-    this.clip(context, frameState,  /** @type {ol.Extent} */ (clipExtent));
-  }
-  var replayGroup = this.replayGroup_;
-  if (replayGroup && !replayGroup.isEmpty()) {
-    var layer = this.getLayer();
-    var drawOffsetX = 0;
-    var drawOffsetY = 0;
-    var replayContext;
-    if (layer.hasListener(ol.render.Event.Type.RENDER)) {
-      var drawWidth = context.canvas.width;
-      var drawHeight = context.canvas.height;
-      if (rotation) {
-        var drawSize = Math.round(Math.sqrt(drawWidth * drawWidth + drawHeight * drawHeight));
-        drawOffsetX = (drawSize - drawWidth) / 2;
-        drawOffsetY = (drawSize - drawHeight) / 2;
-        drawWidth = drawHeight = drawSize;
-      }
-      // resize and clear
-      this.context_.canvas.width = drawWidth;
-      this.context_.canvas.height = drawHeight;
-      replayContext = this.context_;
-    } else {
-      replayContext = context;
-    }
-    // for performance reasons, context.save / context.restore is not used
-    // to save and restore the transformation matrix and the opacity.
-    // see http://jsperf.com/context-save-restore-versus-variable
-    var alpha = replayContext.globalAlpha;
-    replayContext.globalAlpha = layerState.opacity;
-    if (replayContext != context) {
-      replayContext.translate(drawOffsetX, drawOffsetY);
-    }
-
-    var width = frameState.size[0] * pixelRatio;
-    var height = frameState.size[1] * pixelRatio;
-    ol.render.canvas.rotateAtOffset(replayContext, -rotation,
-        width / 2, height / 2);
-    replayGroup.replay(replayContext, pixelRatio, transform, rotation,
-        skippedFeatureUids);
-    if (vectorSource.getWrapX() && projection.canWrapX() &&
-        !ol.extent.containsExtent(projectionExtent, extent)) {
-      var startX = extent[0];
-      var worldWidth = ol.extent.getWidth(projectionExtent);
-      var world = 0;
-      var offsetX;
-      while (startX < projectionExtent[0]) {
-        --world;
-        offsetX = worldWidth * world;
-        transform = this.getTransform(frameState, offsetX);
-        replayGroup.replay(replayContext, pixelRatio, transform, rotation,
-            skippedFeatureUids);
-        startX += worldWidth;
-      }
-      world = 0;
-      startX = extent[2];
-      while (startX > projectionExtent[2]) {
-        ++world;
-        offsetX = worldWidth * world;
-        transform = this.getTransform(frameState, offsetX);
-        replayGroup.replay(replayContext, pixelRatio, transform, rotation,
-            skippedFeatureUids);
-        startX -= worldWidth;
-      }
-      // restore original transform for render and compose events
-      transform = this.getTransform(frameState, 0);
-    }
-    ol.render.canvas.rotateAtOffset(replayContext, rotation,
-        width / 2, height / 2);
-
-    if (replayContext != context) {
-      this.dispatchRenderEvent(replayContext, frameState, transform);
-      context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
-      replayContext.translate(-drawOffsetX, -drawOffsetY);
-    }
-    replayContext.globalAlpha = alpha;
-  }
-
-  if (clipped) {
-    context.restore();
-  }
-  this.dispatchPostComposeEvent(context, frameState, transform);
-
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
-  if (!this.replayGroup_) {
-    return undefined;
-  } else {
-    var resolution = frameState.viewState.resolution;
-    var rotation = frameState.viewState.rotation;
-    var layer = this.getLayer();
-    /** @type {Object.<string, boolean>} */
-    var features = {};
-    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
-        rotation, {},
-        /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @return {?} Callback result.
-         */
-        function(feature) {
-          var key = ol.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback.call(thisArg, feature, layer);
-          }
-        });
-  }
-};
-
-
-/**
- * Handle changes in image style state.
- * @param {ol.events.Event} event Image style change event.
- * @private
- */
-ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
-  this.renderIfReadyAndVisible();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) {
-
-  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
-  var vectorSource = vectorLayer.getSource();
-
-  this.updateAttributions(
-      frameState.attributions, vectorSource.getAttributions());
-  this.updateLogos(frameState, vectorSource);
-
-  var animating = frameState.viewHints[ol.View.Hint.ANIMATING];
-  var interacting = frameState.viewHints[ol.View.Hint.INTERACTING];
-  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
-  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();
-
-  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
-      (!updateWhileInteracting && interacting)) {
-    return true;
-  }
-
-  var frameStateExtent = frameState.extent;
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
-  var resolution = viewState.resolution;
-  var pixelRatio = frameState.pixelRatio;
-  var vectorLayerRevision = vectorLayer.getRevision();
-  var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer();
-  var vectorLayerRenderOrder = vectorLayer.getRenderOrder();
-
-  if (vectorLayerRenderOrder === undefined) {
-    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
-  }
-
-  var extent = ol.extent.buffer(frameStateExtent,
-      vectorLayerRenderBuffer * resolution);
-  var projectionExtent = viewState.projection.getExtent();
-
-  if (vectorSource.getWrapX() && viewState.projection.canWrapX() &&
-      !ol.extent.containsExtent(projectionExtent, frameState.extent)) {
-    // For the replay group, we need an extent that intersects the real world
-    // (-180° to +180°). To support geometries in a coordinate range from -540°
-    // to +540°, we add at least 1 world width on each side of the projection
-    // extent. If the viewport is wider than the world, we need to add half of
-    // the viewport width to make sure we cover the whole viewport.
-    var worldWidth = ol.extent.getWidth(projectionExtent);
-    var buffer = Math.max(ol.extent.getWidth(extent) / 2, worldWidth);
-    extent[0] = projectionExtent[0] - buffer;
-    extent[2] = projectionExtent[2] + buffer;
-  }
-
-  if (!this.dirty_ &&
-      this.renderedResolution_ == resolution &&
-      this.renderedRevision_ == vectorLayerRevision &&
-      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
-      ol.extent.containsExtent(this.renderedExtent_, extent)) {
-    return true;
-  }
-
-  this.replayGroup_ = null;
-
-  this.dirty_ = false;
-
-  var replayGroup =
-      new ol.render.canvas.ReplayGroup(
-          ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
-          resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer());
-  vectorSource.loadFeatures(extent, resolution, projection);
-  /**
-   * @param {ol.Feature} feature Feature.
-   * @this {ol.renderer.canvas.VectorLayer}
-   */
-  var renderFeature = function(feature) {
-    var styles;
-    var styleFunction = feature.getStyleFunction();
-    if (styleFunction) {
-      styles = styleFunction.call(feature, resolution);
-    } else {
-      styleFunction = vectorLayer.getStyleFunction();
-      if (styleFunction) {
-        styles = styleFunction(feature, resolution);
-      }
-    }
-    if (styles) {
-      var dirty = this.renderFeature(
-          feature, resolution, pixelRatio, styles, replayGroup);
-      this.dirty_ = this.dirty_ || dirty;
-    }
-  };
-  if (vectorLayerRenderOrder) {
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    vectorSource.forEachFeatureInExtent(extent,
-        /**
-         * @param {ol.Feature} feature Feature.
-         */
-        function(feature) {
-          features.push(feature);
-        }, this);
-    features.sort(vectorLayerRenderOrder);
-    features.forEach(renderFeature, this);
-  } else {
-    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
-  }
-  replayGroup.finish();
-
-  this.renderedResolution_ = resolution;
-  this.renderedRevision_ = vectorLayerRevision;
-  this.renderedRenderOrder_ = vectorLayerRenderOrder;
-  this.renderedExtent_ = extent;
-  this.replayGroup_ = replayGroup;
-
-  return true;
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
- *     styles.
- * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
- * @return {boolean} `true` if an image is loading.
- */
-ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
-  if (!styles) {
-    return false;
-  }
-  var loading = false;
-  if (Array.isArray(styles)) {
-    for (var i = 0, ii = styles.length; i < ii; ++i) {
-      loading = ol.renderer.vector.renderFeature(
-          replayGroup, feature, styles[i],
-          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-          this.handleStyleImageChange_, this) || loading;
-    }
-  } else {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles,
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleStyleImageChange_, this) || loading;
-  }
-  return loading;
-};
-
-goog.provide('ol.renderer.canvas.VectorTileLayer');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.proj.Units');
-goog.require('ol.layer.VectorTile');
-goog.require('ol.render.Event');
-goog.require('ol.render.ReplayType');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.ReplayGroup');
-goog.require('ol.render.replay');
-goog.require('ol.renderer.canvas.TileLayer');
-goog.require('ol.renderer.vector');
-goog.require('ol.size');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.canvas.TileLayer}
- * @param {ol.layer.VectorTile} layer VectorTile layer.
- */
-ol.renderer.canvas.VectorTileLayer = function(layer) {
-
-  ol.renderer.canvas.TileLayer.call(this, layer);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.dirty_ = false;
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.tmpTransform_ = ol.transform.create();
-
-  // Use lower resolution for pure vector rendering. Closest resolution otherwise.
-  this.zDirection =
-      layer.getRenderMode() == ol.layer.VectorTile.RenderType.VECTOR ? 1 : 0;
-
-};
-ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
-
-
-/**
- * @const
- * @type {!Object.<string, Array.<ol.render.ReplayType>>}
- */
-ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = {
-  'image': ol.render.replay.ORDER,
-  'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
-};
-
-
-/**
- * @const
- * @type {!Object.<string, Array.<ol.render.ReplayType>>}
- */
-ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = {
-  'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
-  'vector': ol.render.replay.ORDER
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(
-    frameState, layerState, context) {
-  var transform = this.getTransform(frameState, 0);
-  this.dispatchPreComposeEvent(context, frameState, transform);
-
-  // clipped rendering if layer extent is set
-  var extent = layerState.extent;
-  var clipped = extent !== undefined;
-  if (clipped) {
-    this.clip(context, frameState,  /** @type {ol.Extent} */ (extent));
-  }
-
-  var renderMode = this.getLayer().getRenderMode();
-  if (renderMode !== ol.layer.VectorTile.RenderType.VECTOR) {
-    this.renderTileImages(context, frameState, layerState);
-  }
-  if (renderMode !== ol.layer.VectorTile.RenderType.IMAGE) {
-    this.renderTileReplays_(context, frameState, layerState);
-  }
-
-  if (clipped) {
-    context.restore();
-  }
-
-  this.dispatchPostComposeEvent(context, frameState, transform);
-};
-
-
-/**
- * @param {CanvasRenderingContext2D} context Context.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @private
- */
-ol.renderer.canvas.VectorTileLayer.prototype.renderTileReplays_ = function(
-    context, frameState, layerState) {
-
-  var layer = this.getLayer();
-  var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[layer.getRenderMode()];
-  var pixelRatio = frameState.pixelRatio;
-  var skippedFeatureUids = layerState.managed ?
-      frameState.skippedFeatureUids : {};
-  var viewState = frameState.viewState;
-  var center = viewState.center;
-  var resolution = viewState.resolution;
-  var rotation = viewState.rotation;
-  var size = frameState.size;
-  var pixelScale = pixelRatio / resolution;
-  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
-  var tilePixelRatio = source.getTilePixelRatio();
-
-  var transform = this.getTransform(frameState, 0);
-
-  var replayContext;
-  if (layer.hasListener(ol.render.Event.Type.RENDER)) {
-    // resize and clear
-    this.context.canvas.width = context.canvas.width;
-    this.context.canvas.height = context.canvas.height;
-    replayContext = this.context;
-  } else {
-    replayContext = context;
-  }
-  // for performance reasons, context.save / context.restore is not used
-  // to save and restore the transformation matrix and the opacity.
-  // see http://jsperf.com/context-save-restore-versus-variable
-  var alpha = replayContext.globalAlpha;
-  replayContext.globalAlpha = layerState.opacity;
-
-  var tilesToDraw = this.renderedTiles;
-  var tileGrid = source.getTileGrid();
-
-  var currentZ, i, ii, offsetX, offsetY, origin, pixelSpace, replayState;
-  var tile, tileExtent, tilePixelResolution, tileResolution, tileTransform;
-  for (i = 0, ii = tilesToDraw.length; i < ii; ++i) {
-    tile = tilesToDraw[i];
-    replayState = tile.getReplayState();
-    tileExtent = tileGrid.getTileCoordExtent(
-        tile.getTileCoord(), this.tmpExtent);
-    currentZ = tile.getTileCoord()[0];
-    pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
-    tileResolution = tileGrid.getResolution(currentZ);
-    tilePixelResolution = tileResolution / tilePixelRatio;
-    offsetX = Math.round(pixelRatio * size[0] / 2);
-    offsetY = Math.round(pixelRatio * size[1] / 2);
-
-    if (pixelSpace) {
-      origin = ol.extent.getTopLeft(tileExtent);
-      tileTransform = ol.transform.reset(this.tmpTransform_);
-      tileTransform = ol.transform.compose(this.tmpTransform_,
-          offsetX, offsetY,
-          pixelScale * tilePixelResolution, pixelScale * tilePixelResolution,
-          rotation,
-          (origin[0] - center[0]) / tilePixelResolution, (center[1] - origin[1]) / tilePixelResolution);
-    } else {
-      tileTransform = transform;
-    }
-    ol.render.canvas.rotateAtOffset(replayContext, -rotation, offsetX, offsetY);
-    replayState.replayGroup.replay(replayContext, pixelRatio,
-        tileTransform, rotation, skippedFeatureUids, replays);
-    ol.render.canvas.rotateAtOffset(replayContext, rotation, offsetX, offsetY);
-  }
-
-  if (replayContext != context) {
-    this.dispatchRenderEvent(replayContext, frameState, transform);
-    context.drawImage(replayContext.canvas, 0, 0);
-  }
-  replayContext.globalAlpha = alpha;
-};
-
-
-/**
- * @param {ol.VectorTile} tile Tile.
- * @param {olx.FrameState} frameState Frame state.
- */
-ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
-    frameState) {
-  var layer = this.getLayer();
-  var pixelRatio = frameState.pixelRatio;
-  var projection = frameState.viewState.projection;
-  var revision = layer.getRevision();
-  var renderOrder = layer.getRenderOrder() || null;
-
-  var replayState = tile.getReplayState();
-  if (!replayState.dirty && replayState.renderedRevision == revision &&
-      replayState.renderedRenderOrder == renderOrder) {
-    return;
-  }
-
-  replayState.replayGroup = null;
-  replayState.dirty = false;
-
-  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
-  var tileGrid = source.getTileGrid();
-  var tileCoord = tile.getTileCoord();
-  var tileProjection = tile.getProjection();
-  var pixelSpace = tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS;
-  var resolution = tileGrid.getResolution(tileCoord[0]);
-  var extent, reproject, tileResolution;
-  if (pixelSpace) {
-    var tilePixelRatio = tileResolution = source.getTilePixelRatio();
-    var tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0]));
-    extent = [0, 0, tileSize[0] * tilePixelRatio, tileSize[1] * tilePixelRatio];
-  } else {
-    tileResolution = resolution;
-    extent = tileGrid.getTileCoordExtent(tileCoord);
-    if (!ol.proj.equivalent(projection, tileProjection)) {
-      reproject = true;
-      tile.setProjection(projection);
-    }
-  }
-  replayState.dirty = false;
-  var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
-      tileResolution, source.getOverlaps(), layer.getRenderBuffer());
-  var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
-      tileResolution, pixelRatio);
-
-  /**
-   * @param {ol.Feature|ol.render.Feature} feature Feature.
-   * @this {ol.renderer.canvas.VectorTileLayer}
-   */
-  function renderFeature(feature) {
-    var styles;
-    var styleFunction = feature.getStyleFunction();
-    if (styleFunction) {
-      styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
-    } else {
-      styleFunction = layer.getStyleFunction();
-      if (styleFunction) {
-        styles = styleFunction(feature, resolution);
-      }
-    }
-    if (styles) {
-      if (!Array.isArray(styles)) {
-        styles = [styles];
-      }
-      var dirty = this.renderFeature(feature, squaredTolerance, styles,
-          replayGroup);
-      this.dirty_ = this.dirty_ || dirty;
-      replayState.dirty = replayState.dirty || dirty;
-    }
-  }
-
-  var features = tile.getFeatures();
-  if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
-    features.sort(renderOrder);
-  }
-  var feature;
-  for (var i = 0, ii = features.length; i < ii; ++i) {
-    feature = features[i];
-    if (reproject) {
-      feature.getGeometry().transform(tileProjection, projection);
-    }
-    renderFeature.call(this, feature);
-  }
-
-  replayGroup.finish();
-
-  replayState.renderedRevision = revision;
-  replayState.renderedRenderOrder = renderOrder;
-  replayState.replayGroup = replayGroup;
-  replayState.resolution = NaN;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
-  var resolution = frameState.viewState.resolution;
-  var rotation = frameState.viewState.rotation;
-  var layer = this.getLayer();
-  /** @type {Object.<string, boolean>} */
-  var features = {};
-
-  var replayables = this.renderedTiles;
-  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
-  var tileGrid = source.getTileGrid();
-  var found, tileSpaceCoordinate;
-  var i, ii, origin, replayGroup;
-  var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution;
-  for (i = 0, ii = replayables.length; i < ii; ++i) {
-    tile = replayables[i];
-    tileCoord = tile.getTileCoord();
-    tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord,
-        this.tmpExtent);
-    if (!ol.extent.containsCoordinate(tileExtent, coordinate)) {
-      continue;
-    }
-    if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
-      origin = ol.extent.getTopLeft(tileExtent);
-      tilePixelRatio = source.getTilePixelRatio();
-      tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio;
-      tileSpaceCoordinate = [
-        (coordinate[0] - origin[0]) / tileResolution,
-        (origin[1] - coordinate[1]) / tileResolution
-      ];
-      resolution = tilePixelRatio;
-    } else {
-      tileSpaceCoordinate = coordinate;
-    }
-    replayGroup = tile.getReplayState().replayGroup;
-    found = found || replayGroup.forEachFeatureAtCoordinate(
-        tileSpaceCoordinate, resolution, rotation, {},
-        /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @return {?} Callback result.
-         */
-        function(feature) {
-          var key = ol.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback.call(thisArg, feature, layer);
-          }
-        });
-  }
-  return found;
-};
-
-
-/**
- * Handle changes in image style state.
- * @param {ol.events.Event} event Image style change event.
- * @private
- */
-ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(event) {
-  this.renderIfReadyAndVisible();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState, layerState) {
-  var prepared = ol.renderer.canvas.TileLayer.prototype.prepareFrame.call(this, frameState, layerState);
-  if (prepared) {
-    var skippedFeatures = Object.keys(frameState.skippedFeatureUids_ || {});
-    for (var i = 0, ii = this.renderedTiles.length; i < ii; ++i) {
-      var tile = /** @type {ol.VectorTile} */ (this.renderedTiles[i]);
-      this.createReplayGroup(tile, frameState);
-      this.renderTileImage_(tile, frameState, layerState, skippedFeatures);
-    }
-  }
-  return prepared;
-};
-
-
-/**
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
- *     styles.
- * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
- * @return {boolean} `true` if an image is loading.
- */
-ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) {
-  if (!styles) {
-    return false;
-  }
-  var loading = false;
-  if (Array.isArray(styles)) {
-    for (var i = 0, ii = styles.length; i < ii; ++i) {
-      loading = ol.renderer.vector.renderFeature(
-          replayGroup, feature, styles[i], squaredTolerance,
-          this.handleStyleImageChange_, this) || loading;
-    }
-  } else {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles, squaredTolerance,
-        this.handleStyleImageChange_, this) || loading;
-  }
-  return loading;
-};
-
-
-/**
- * @param {ol.VectorTile} tile Tile.
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @param {Array.<string>} skippedFeatures Skipped features.
- * @private
- */
-ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
-    tile, frameState, layerState, skippedFeatures) {
-  var layer = this.getLayer();
-  var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()];
-  if (!replays) {
-    // do not create an image in 'vector' mode
-    return;
-  }
-  var pixelRatio = frameState.pixelRatio;
-  var replayState = tile.getReplayState();
-  var revision = layer.getRevision();
-  if (!ol.array.equals(replayState.skippedFeatures, skippedFeatures) ||
-      replayState.renderedTileRevision !== revision) {
-    replayState.skippedFeatures = skippedFeatures;
-    replayState.renderedTileRevision = revision;
-    var tileContext = tile.getContext();
-    var source = layer.getSource();
-    var tileGrid = source.getTileGrid();
-    var currentZ = tile.getTileCoord()[0];
-    var resolution = tileGrid.getResolution(currentZ);
-    var tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ));
-    var tileResolution = tileGrid.getResolution(currentZ);
-    var resolutionRatio = tileResolution / resolution;
-    var width = tileSize[0] * pixelRatio * resolutionRatio;
-    var height = tileSize[1] * pixelRatio * resolutionRatio;
-    tileContext.canvas.width = width / resolutionRatio + 0.5;
-    tileContext.canvas.height = height / resolutionRatio + 0.5;
-    tileContext.scale(1 / resolutionRatio, 1 / resolutionRatio);
-    tileContext.translate(width / 2, height / 2);
-    var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
-    var pixelScale = pixelRatio / resolution;
-    var tilePixelRatio = source.getTilePixelRatio();
-    var tilePixelResolution = tileResolution / tilePixelRatio;
-    var tileExtent = tileGrid.getTileCoordExtent(
-        tile.getTileCoord(), this.tmpExtent);
-    var tileTransform = ol.transform.reset(this.tmpTransform_);
-    if (pixelSpace) {
-      ol.transform.scale(tileTransform,
-          pixelScale * tilePixelResolution, pixelScale * tilePixelResolution);
-      ol.transform.translate(tileTransform,
-          -tileSize[0] * tilePixelRatio / 2, -tileSize[1] * tilePixelRatio / 2);
-    } else {
-      var tileCenter = ol.extent.getCenter(tileExtent);
-      ol.transform.scale(tileTransform, pixelScale, -pixelScale);
-      ol.transform.translate(tileTransform, -tileCenter[0], -tileCenter[1]);
-    }
-
-    replayState.replayGroup.replay(tileContext, pixelRatio,
-        tileTransform, 0, frameState.skippedFeatureUids || {}, replays);
-  }
-};
-
-// FIXME offset panning
-
-goog.provide('ol.renderer.canvas.Map');
-
-goog.require('ol.transform');
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.css');
-goog.require('ol.dom');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Layer');
-goog.require('ol.layer.Tile');
-goog.require('ol.layer.Vector');
-goog.require('ol.layer.VectorTile');
-goog.require('ol.render.Event');
-goog.require('ol.render.canvas');
-goog.require('ol.render.canvas.Immediate');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.Type');
-goog.require('ol.renderer.canvas.ImageLayer');
-goog.require('ol.renderer.canvas.TileLayer');
-goog.require('ol.renderer.canvas.VectorLayer');
-goog.require('ol.renderer.canvas.VectorTileLayer');
-goog.require('ol.source.State');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.Map}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
- */
-ol.renderer.canvas.Map = function(container, map) {
-
-  ol.renderer.Map.call(this, container, map);
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D();
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = this.context_.canvas;
-
-  this.canvas_.style.width = '100%';
-  this.canvas_.style.height = '100%';
-  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
-  container.insertBefore(this.canvas_, container.childNodes[0] || null);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.transform_ = ol.transform.create();
-
-};
-ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) {
-  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
-    return new ol.renderer.canvas.ImageLayer(layer);
-  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
-    return new ol.renderer.canvas.TileLayer(layer);
-  } else if (ol.ENABLE_VECTOR_TILE && layer instanceof ol.layer.VectorTile) {
-    return new ol.renderer.canvas.VectorTileLayer(layer);
-  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
-    return new ol.renderer.canvas.VectorLayer(layer);
-  } else {
-    ol.DEBUG && console.assert(false, 'unexpected layer configuration');
-    return null;
-  }
-};
-
-
-/**
- * @param {ol.render.Event.Type} type Event type.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = function(type, frameState) {
-  var map = this.getMap();
-  var context = this.context_;
-  if (map.hasListener(type)) {
-    var extent = frameState.extent;
-    var pixelRatio = frameState.pixelRatio;
-    var viewState = frameState.viewState;
-    var rotation = viewState.rotation;
-
-    var transform = this.getTransform(frameState);
-
-    var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
-        extent, transform, rotation);
-    var composeEvent = new ol.render.Event(type, vectorContext,
-        frameState, context, null);
-    map.dispatchEvent(composeEvent);
-  }
-};
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @protected
- * @return {!ol.Transform} Transform.
- */
-ol.renderer.canvas.Map.prototype.getTransform = function(frameState) {
-  var viewState = frameState.viewState;
-  var dx1 = this.canvas_.width / 2;
-  var dy1 = this.canvas_.height / 2;
-  var sx = frameState.pixelRatio / viewState.resolution;
-  var sy = -sx;
-  var angle = -viewState.rotation;
-  var dx2 = -viewState.center[0];
-  var dy2 = -viewState.center[1];
-  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.Map.prototype.getType = function() {
-  return ol.renderer.Type.CANVAS;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
-
-  if (!frameState) {
-    if (this.renderedVisible_) {
-      this.canvas_.style.display = 'none';
-      this.renderedVisible_ = false;
-    }
-    return;
-  }
-
-  var context = this.context_;
-  var pixelRatio = frameState.pixelRatio;
-  var width = Math.round(frameState.size[0] * pixelRatio);
-  var height = Math.round(frameState.size[1] * pixelRatio);
-  if (this.canvas_.width != width || this.canvas_.height != height) {
-    this.canvas_.width = width;
-    this.canvas_.height = height;
-  } else {
-    context.clearRect(0, 0, width, height);
-  }
-
-  var rotation = frameState.viewState.rotation;
-
-  this.calculateMatrices2D(frameState);
-
-  this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, frameState);
-
-  var layerStatesArray = frameState.layerStatesArray;
-  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
-
-  ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
-
-  var viewResolution = frameState.viewState.resolution;
-  var i, ii, layer, layerRenderer, layerState;
-  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-    layerState = layerStatesArray[i];
-    layer = layerState.layer;
-    layerRenderer = /** @type {ol.renderer.canvas.Layer} */ (this.getLayerRenderer(layer));
-    if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) ||
-        layerState.sourceState != ol.source.State.READY) {
-      continue;
-    }
-    if (layerRenderer.prepareFrame(frameState, layerState)) {
-      layerRenderer.composeFrame(frameState, layerState, context);
-    }
-  }
-
-  ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
-
-  this.dispatchComposeEvent_(
-      ol.render.Event.Type.POSTCOMPOSE, frameState);
-
-  if (!this.renderedVisible_) {
-    this.canvas_.style.display = '';
-    this.renderedVisible_ = true;
-  }
-
-  this.scheduleRemoveUnusedLayerRenderers(frameState);
-  this.scheduleExpireIconCache(frameState);
-};
-
-goog.provide('ol.webgl.Shader');
-
-goog.require('ol.functions');
-goog.require('ol.webgl');
-
-
-/**
- * @constructor
- * @param {string} source Source.
- * @struct
- */
-ol.webgl.Shader = function(source) {
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.source_ = source;
-
-};
-
-
-/**
- * @abstract
- * @return {number} Type.
- */
-ol.webgl.Shader.prototype.getType = function() {};
-
-
-/**
- * @return {string} Source.
- */
-ol.webgl.Shader.prototype.getSource = function() {
-  return this.source_;
-};
-
-
-/**
- * @return {boolean} Is animated?
- */
-ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE;
-
-goog.provide('ol.webgl.Fragment');
-
-goog.require('ol');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Shader');
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Shader}
- * @param {string} source Source.
- * @struct
- */
-ol.webgl.Fragment = function(source) {
-  ol.webgl.Shader.call(this, source);
-};
-ol.inherits(ol.webgl.Fragment, ol.webgl.Shader);
-
-
-/**
- * @inheritDoc
- */
-ol.webgl.Fragment.prototype.getType = function() {
-  return ol.webgl.FRAGMENT_SHADER;
-};
-
-goog.provide('ol.webgl.Vertex');
-
-goog.require('ol');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Shader');
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Shader}
- * @param {string} source Source.
- * @struct
- */
-ol.webgl.Vertex = function(source) {
-  ol.webgl.Shader.call(this, source);
-};
-ol.inherits(ol.webgl.Vertex, ol.webgl.Shader);
-
-
-/**
- * @inheritDoc
- */
-ol.webgl.Vertex.prototype.getType = function() {
-  return ol.webgl.VERTEX_SHADER;
-};
-
-// This file is automatically generated, do not edit
-goog.provide('ol.render.webgl.imagereplay.defaultshader');
-
-goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Fragment}
- * @struct
- */
-ol.render.webgl.imagereplay.defaultshader.Fragment = function() {
-  ol.webgl.Fragment.call(this, ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE);
-};
-ol.inherits(ol.render.webgl.imagereplay.defaultshader.Fragment, ol.webgl.Fragment);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_image, v_texCoord);\n  gl_FragColor.rgb = texColor.rgb;\n  float alpha = texColor.a * v_opacity * u_opacity;\n  if (alpha == 0.0) {\n    discard;\n  }\n  gl_FragColor.a = alpha;\n}\n';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE = ol.DEBUG ?
-    ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE :
-    ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE;
-
-
-ol.render.webgl.imagereplay.defaultshader.fragment = new ol.render.webgl.imagereplay.defaultshader.Fragment();
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Vertex}
- * @struct
- */
-ol.render.webgl.imagereplay.defaultshader.Vertex = function() {
-  ol.webgl.Vertex.call(this, ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE);
-};
-ol.inherits(ol.render.webgl.imagereplay.defaultshader.Vertex, ol.webgl.Vertex);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n  mat4 offsetMatrix = u_offsetScaleMatrix;\n  if (a_rotateWithView == 1.0) {\n    offsetMatrix = u_ [...]
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE = ol.DEBUG ?
-    ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE :
-    ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE;
-
-
-ol.render.webgl.imagereplay.defaultshader.vertex = new ol.render.webgl.imagereplay.defaultshader.Vertex();
-
-
-/**
- * @constructor
- * @param {WebGLRenderingContext} gl GL.
- * @param {WebGLProgram} program Program.
- * @struct
- */
-ol.render.webgl.imagereplay.defaultshader.Locations = function(gl, program) {
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_image = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_image' : 'l');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_offsetRotateMatrix = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_offsetScaleMatrix = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_opacity = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_opacity' : 'k');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_projectionMatrix = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_projectionMatrix' : 'h');
-
-  /**
-   * @type {number}
-   */
-  this.a_offsets = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_offsets' : 'e');
-
-  /**
-   * @type {number}
-   */
-  this.a_opacity = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_opacity' : 'f');
-
-  /**
-   * @type {number}
-   */
-  this.a_position = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_position' : 'c');
-
-  /**
-   * @type {number}
-   */
-  this.a_rotateWithView = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_rotateWithView' : 'g');
-
-  /**
-   * @type {number}
-   */
-  this.a_texCoord = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_texCoord' : 'd');
-};
-
-goog.provide('ol.vec.Mat4');
-
-
-/**
- * @return {Array.<number>} 4x4 matrix representing a 3D identity transform.
- */
-ol.vec.Mat4.create = function() {
-  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
-};
-
-
-/**
- * @param {Array.<number>} mat4 Flattened 4x4 matrix receiving the result.
- * @param {ol.Transform} transform Transformation matrix.
- * @return {Array.<number>} 2D transformation matrix as flattened 4x4 matrix.
- */
-ol.vec.Mat4.fromTransform = function(mat4, transform) {
-  mat4[0] = transform[0];
-  mat4[1] = transform[1];
-  mat4[4] = transform[2];
-  mat4[5] = transform[3];
-  mat4[12] = transform[4];
-  mat4[13] = transform[5];
-  return mat4;
-};
-
-goog.provide('ol.webgl.Buffer');
-
-goog.require('ol');
-goog.require('ol.webgl');
-
-
-/**
- * @constructor
- * @param {Array.<number>=} opt_arr Array.
- * @param {number=} opt_usage Usage.
- * @struct
- */
-ol.webgl.Buffer = function(opt_arr, opt_usage) {
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.arr_ = opt_arr !== undefined ? opt_arr : [];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.usage_ = opt_usage !== undefined ?
-      opt_usage : ol.webgl.Buffer.Usage.STATIC_DRAW;
-
-};
-
-
-/**
- * @return {Array.<number>} Array.
- */
-ol.webgl.Buffer.prototype.getArray = function() {
-  return this.arr_;
-};
-
-
-/**
- * @return {number} Usage.
- */
-ol.webgl.Buffer.prototype.getUsage = function() {
-  return this.usage_;
-};
-
-
-/**
- * @enum {number}
- */
-ol.webgl.Buffer.Usage = {
-  STATIC_DRAW: ol.webgl.STATIC_DRAW,
-  STREAM_DRAW: ol.webgl.STREAM_DRAW,
-  DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW
-};
-
-goog.provide('ol.webgl.ContextEventType');
-
-
-/**
- * @enum {string}
- */
-ol.webgl.ContextEventType = {
-  LOST: 'webglcontextlost',
-  RESTORED: 'webglcontextrestored'
-};
-
-goog.provide('ol.webgl.Context');
-
-goog.require('ol');
-goog.require('ol.Disposable');
-goog.require('ol.array');
-goog.require('ol.events');
-goog.require('ol.obj');
-goog.require('ol.webgl');
-goog.require('ol.webgl.ContextEventType');
-
-
-/**
- * @classdesc
- * A WebGL context for accessing low-level WebGL capabilities.
- *
- * @constructor
- * @extends {ol.Disposable}
- * @param {HTMLCanvasElement} canvas Canvas.
- * @param {WebGLRenderingContext} gl GL.
- */
-ol.webgl.Context = function(canvas, gl) {
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = canvas;
-
-  /**
-   * @private
-   * @type {WebGLRenderingContext}
-   */
-  this.gl_ = gl;
-
-  /**
-   * @private
-   * @type {Object.<string, ol.WebglBufferCacheEntry>}
-   */
-  this.bufferCache_ = {};
-
-  /**
-   * @private
-   * @type {Object.<string, WebGLShader>}
-   */
-  this.shaderCache_ = {};
-
-  /**
-   * @private
-   * @type {Object.<string, WebGLProgram>}
-   */
-  this.programCache_ = {};
-
-  /**
-   * @private
-   * @type {WebGLProgram}
-   */
-  this.currentProgram_ = null;
-
-  /**
-   * @private
-   * @type {WebGLFramebuffer}
-   */
-  this.hitDetectionFramebuffer_ = null;
-
-  /**
-   * @private
-   * @type {WebGLTexture}
-   */
-  this.hitDetectionTexture_ = null;
-
-  /**
-   * @private
-   * @type {WebGLRenderbuffer}
-   */
-  this.hitDetectionRenderbuffer_ = null;
-
-  /**
-   * @type {boolean}
-   */
-  this.hasOESElementIndexUint = ol.array.includes(
-      ol.WEBGL_EXTENSIONS, 'OES_element_index_uint');
-
-  // use the OES_element_index_uint extension if available
-  if (this.hasOESElementIndexUint) {
-    var ext = gl.getExtension('OES_element_index_uint');
-    ol.DEBUG && console.assert(ext,
-        'Failed to get extension "OES_element_index_uint"');
-  }
-
-  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
-      this.handleWebGLContextLost, this);
-  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
-      this.handleWebGLContextRestored, this);
-
-};
-ol.inherits(ol.webgl.Context, ol.Disposable);
-
-
-/**
- * Just bind the buffer if it's in the cache. Otherwise create
- * the WebGL buffer, bind it, populate it, and add an entry to
- * the cache.
- * @param {number} target Target.
- * @param {ol.webgl.Buffer} buf Buffer.
- */
-ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
-  var gl = this.getGL();
-  var arr = buf.getArray();
-  var bufferKey = String(ol.getUid(buf));
-  if (bufferKey in this.bufferCache_) {
-    var bufferCacheEntry = this.bufferCache_[bufferKey];
-    gl.bindBuffer(target, bufferCacheEntry.buffer);
-  } else {
-    var buffer = gl.createBuffer();
-    gl.bindBuffer(target, buffer);
-    ol.DEBUG && console.assert(target == ol.webgl.ARRAY_BUFFER ||
-        target == ol.webgl.ELEMENT_ARRAY_BUFFER,
-        'target is supposed to be an ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER');
-    var /** @type {ArrayBufferView} */ arrayBuffer;
-    if (target == ol.webgl.ARRAY_BUFFER) {
-      arrayBuffer = new Float32Array(arr);
-    } else if (target == ol.webgl.ELEMENT_ARRAY_BUFFER) {
-      arrayBuffer = this.hasOESElementIndexUint ?
-          new Uint32Array(arr) : new Uint16Array(arr);
-    }
-    gl.bufferData(target, arrayBuffer, buf.getUsage());
-    this.bufferCache_[bufferKey] = {
-      buf: buf,
-      buffer: buffer
-    };
-  }
-};
-
-
-/**
- * @param {ol.webgl.Buffer} buf Buffer.
- */
-ol.webgl.Context.prototype.deleteBuffer = function(buf) {
-  var gl = this.getGL();
-  var bufferKey = String(ol.getUid(buf));
-  ol.DEBUG && console.assert(bufferKey in this.bufferCache_,
-      'attempted to delete uncached buffer');
-  var bufferCacheEntry = this.bufferCache_[bufferKey];
-  if (!gl.isContextLost()) {
-    gl.deleteBuffer(bufferCacheEntry.buffer);
-  }
-  delete this.bufferCache_[bufferKey];
-};
-
-
-/**
- * @inheritDoc
- */
-ol.webgl.Context.prototype.disposeInternal = function() {
-  ol.events.unlistenAll(this.canvas_);
-  var gl = this.getGL();
-  if (!gl.isContextLost()) {
-    var key;
-    for (key in this.bufferCache_) {
-      gl.deleteBuffer(this.bufferCache_[key].buffer);
-    }
-    for (key in this.programCache_) {
-      gl.deleteProgram(this.programCache_[key]);
-    }
-    for (key in this.shaderCache_) {
-      gl.deleteShader(this.shaderCache_[key]);
-    }
-    // delete objects for hit-detection
-    gl.deleteFramebuffer(this.hitDetectionFramebuffer_);
-    gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_);
-    gl.deleteTexture(this.hitDetectionTexture_);
-  }
-};
-
-
-/**
- * @return {HTMLCanvasElement} Canvas.
- */
-ol.webgl.Context.prototype.getCanvas = function() {
-  return this.canvas_;
-};
-
-
-/**
- * Get the WebGL rendering context
- * @return {WebGLRenderingContext} The rendering context.
- * @api
- */
-ol.webgl.Context.prototype.getGL = function() {
-  return this.gl_;
-};
-
-
-/**
- * Get the frame buffer for hit detection.
- * @return {WebGLFramebuffer} The hit detection frame buffer.
- */
-ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() {
-  if (!this.hitDetectionFramebuffer_) {
-    this.initHitDetectionFramebuffer_();
-  }
-  return this.hitDetectionFramebuffer_;
-};
-
-
-/**
- * Get shader from the cache if it's in the cache. Otherwise, create
- * the WebGL shader, compile it, and add entry to cache.
- * @param {ol.webgl.Shader} shaderObject Shader object.
- * @return {WebGLShader} Shader.
- */
-ol.webgl.Context.prototype.getShader = function(shaderObject) {
-  var shaderKey = String(ol.getUid(shaderObject));
-  if (shaderKey in this.shaderCache_) {
-    return this.shaderCache_[shaderKey];
-  } else {
-    var gl = this.getGL();
-    var shader = gl.createShader(shaderObject.getType());
-    gl.shaderSource(shader, shaderObject.getSource());
-    gl.compileShader(shader);
-    ol.DEBUG && console.assert(
-        gl.getShaderParameter(shader, ol.webgl.COMPILE_STATUS) ||
-        gl.isContextLost(),
-        gl.getShaderInfoLog(shader) || 'illegal state, shader not compiled or context lost');
-    this.shaderCache_[shaderKey] = shader;
-    return shader;
-  }
-};
-
-
-/**
- * Get the program from the cache if it's in the cache. Otherwise create
- * the WebGL program, attach the shaders to it, and add an entry to the
- * cache.
- * @param {ol.webgl.Fragment} fragmentShaderObject Fragment shader.
- * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader.
- * @return {WebGLProgram} Program.
- */
-ol.webgl.Context.prototype.getProgram = function(
-    fragmentShaderObject, vertexShaderObject) {
-  var programKey =
-      ol.getUid(fragmentShaderObject) + '/' + ol.getUid(vertexShaderObject);
-  if (programKey in this.programCache_) {
-    return this.programCache_[programKey];
-  } else {
-    var gl = this.getGL();
-    var program = gl.createProgram();
-    gl.attachShader(program, this.getShader(fragmentShaderObject));
-    gl.attachShader(program, this.getShader(vertexShaderObject));
-    gl.linkProgram(program);
-    ol.DEBUG && console.assert(
-        gl.getProgramParameter(program, ol.webgl.LINK_STATUS) ||
-        gl.isContextLost(),
-        gl.getProgramInfoLog(program) || 'illegal state, shader not linked or context lost');
-    this.programCache_[programKey] = program;
-    return program;
-  }
-};
-
-
-/**
- * FIXME empy description for jsdoc
- */
-ol.webgl.Context.prototype.handleWebGLContextLost = function() {
-  ol.obj.clear(this.bufferCache_);
-  ol.obj.clear(this.shaderCache_);
-  ol.obj.clear(this.programCache_);
-  this.currentProgram_ = null;
-  this.hitDetectionFramebuffer_ = null;
-  this.hitDetectionTexture_ = null;
-  this.hitDetectionRenderbuffer_ = null;
-};
-
-
-/**
- * FIXME empy description for jsdoc
- */
-ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
-};
-
-
-/**
- * Creates a 1x1 pixel framebuffer for the hit-detection.
- * @private
- */
-ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() {
-  var gl = this.gl_;
-  var framebuffer = gl.createFramebuffer();
-  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
-
-  var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1);
-  var renderbuffer = gl.createRenderbuffer();
-  gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
-  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1);
-  gl.framebufferTexture2D(
-      gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
-  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
-      gl.RENDERBUFFER, renderbuffer);
-
-  gl.bindTexture(gl.TEXTURE_2D, null);
-  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
-  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-
-  this.hitDetectionFramebuffer_ = framebuffer;
-  this.hitDetectionTexture_ = texture;
-  this.hitDetectionRenderbuffer_ = renderbuffer;
-};
-
-
-/**
- * Use a program.  If the program is already in use, this will return `false`.
- * @param {WebGLProgram} program Program.
- * @return {boolean} Changed.
- * @api
- */
-ol.webgl.Context.prototype.useProgram = function(program) {
-  if (program == this.currentProgram_) {
-    return false;
-  } else {
-    var gl = this.getGL();
-    gl.useProgram(program);
-    this.currentProgram_ = program;
-    return true;
-  }
-};
-
-
-/**
- * @param {WebGLRenderingContext} gl WebGL rendering context.
- * @param {number=} opt_wrapS wrapS.
- * @param {number=} opt_wrapT wrapT.
- * @return {WebGLTexture} The texture.
- * @private
- */
-ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) {
-  var texture = gl.createTexture();
-  gl.bindTexture(gl.TEXTURE_2D, texture);
-  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
-  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
-
-  if (opt_wrapS !== undefined) {
-    gl.texParameteri(
-        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, opt_wrapS);
-  }
-  if (opt_wrapT !== undefined) {
-    gl.texParameteri(
-        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, opt_wrapT);
-  }
-
-  return texture;
-};
-
-
-/**
- * @param {WebGLRenderingContext} gl WebGL rendering context.
- * @param {number} width Width.
- * @param {number} height Height.
- * @param {number=} opt_wrapS wrapS.
- * @param {number=} opt_wrapT wrapT.
- * @return {WebGLTexture} The texture.
- */
-ol.webgl.Context.createEmptyTexture = function(
-    gl, width, height, opt_wrapS, opt_wrapT) {
-  var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT);
-  gl.texImage2D(
-      gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE,
-      null);
-
-  return texture;
-};
-
-
-/**
- * @param {WebGLRenderingContext} gl WebGL rendering context.
- * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image.
- * @param {number=} opt_wrapS wrapS.
- * @param {number=} opt_wrapT wrapT.
- * @return {WebGLTexture} The texture.
- */
-ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) {
-  var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT);
-  gl.texImage2D(
-      gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
-
-  return texture;
-};
-
-goog.provide('ol.render.webgl.ImageReplay');
-goog.provide('ol.render.webgl.ReplayGroup');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.render.ReplayGroup');
-goog.require('ol.render.VectorContext');
-goog.require('ol.render.replay');
-goog.require('ol.render.webgl.imagereplay.defaultshader');
-goog.require('ol.transform');
-goog.require('ol.vec.Mat4');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Buffer');
-goog.require('ol.webgl.Context');
-
-
-/**
- * @constructor
- * @extends {ol.render.VectorContext}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Max extent.
- * @protected
- * @struct
- */
-ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
-  ol.render.VectorContext.call(this);
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.anchorX_ = undefined;
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.anchorY_ = undefined;
-
-  /**
-   * The origin of the coordinate system for the point coordinates sent to
-   * the GPU. To eliminate jitter caused by precision problems in the GPU
-   * we use the "Rendering Relative to Eye" technique described in the "3D
-   * Engine Design for Virtual Globes" book.
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.origin_ = ol.extent.getCenter(maxExtent);
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.groupIndices_ = [];
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.hitDetectionGroupIndices_ = [];
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.height_ = undefined;
-
-  /**
-   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
-   * @private
-   */
-  this.images_ = [];
-
-  /**
-   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
-   * @private
-   */
-  this.hitDetectionImages_ = [];
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.imageHeight_ = undefined;
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.imageWidth_ = undefined;
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.indices_ = [];
-
-  /**
-   * @type {ol.webgl.Buffer}
-   * @private
-   */
-  this.indicesBuffer_ = null;
-
-  /**
-   * @private
-   * @type {ol.render.webgl.imagereplay.defaultshader.Locations}
-   */
-  this.defaultLocations_ = null;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.opacity_ = undefined;
-
-  /**
-   * @type {ol.Transform}
-   * @private
-   */
-  this.offsetRotateMatrix_ = ol.transform.create();
-
-  /**
-   * @type {ol.Transform}
-   * @private
-   */
-  this.offsetScaleMatrix_ = ol.transform.create();
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.originX_ = undefined;
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.originY_ = undefined;
-
-  /**
-   * @type {ol.Transform}
-   * @private
-   */
-  this.projectionMatrix_ = ol.transform.create();
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.tmpMat4_ = ol.vec.Mat4.create();
-
-  /**
-   * @private
-   * @type {boolean|undefined}
-   */
-  this.rotateWithView_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.rotation_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.scale_ = undefined;
-
-  /**
-   * @type {Array.<WebGLTexture>}
-   * @private
-   */
-  this.textures_ = [];
-
-  /**
-   * @type {Array.<WebGLTexture>}
-   * @private
-   */
-  this.hitDetectionTextures_ = [];
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.vertices_ = [];
-
-  /**
-   * @type {ol.webgl.Buffer}
-   * @private
-   */
-  this.verticesBuffer_ = null;
-
-  /**
-   * Start index per feature (the index).
-   * @type {Array.<number>}
-   * @private
-   */
-  this.startIndices_ = [];
-
-  /**
-   * Start index per feature (the feature).
-   * @type {Array.<ol.Feature|ol.render.Feature>}
-   * @private
-   */
-  this.startIndicesFeature_ = [];
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.width_ = undefined;
-};
-ol.inherits(ol.render.webgl.ImageReplay, ol.render.VectorContext);
-
-
-/**
- * @param {ol.webgl.Context} context WebGL context.
- * @return {function()} Delete resources function.
- */
-ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) {
-  // We only delete our stuff here. The shaders and the program may
-  // be used by other ImageReplay instances (for other layers). And
-  // they will be deleted when disposing of the ol.webgl.Context
-  // object.
-  ol.DEBUG && console.assert(this.verticesBuffer_,
-      'verticesBuffer must not be null');
-  ol.DEBUG && console.assert(this.indicesBuffer_,
-      'indicesBuffer must not be null');
-  var verticesBuffer = this.verticesBuffer_;
-  var indicesBuffer = this.indicesBuffer_;
-  var textures = this.textures_;
-  var hitDetectionTextures = this.hitDetectionTextures_;
-  var gl = context.getGL();
-  return function() {
-    if (!gl.isContextLost()) {
-      var i, ii;
-      for (i = 0, ii = textures.length; i < ii; ++i) {
-        gl.deleteTexture(textures[i]);
-      }
-      for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) {
-        gl.deleteTexture(hitDetectionTextures[i]);
-      }
-    }
-    context.deleteBuffer(verticesBuffer);
-    context.deleteBuffer(indicesBuffer);
-  };
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {number} My end.
- * @private
- */
-ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
-  ol.DEBUG && console.assert(this.anchorX_ !== undefined, 'anchorX is defined');
-  ol.DEBUG && console.assert(this.anchorY_ !== undefined, 'anchorY is defined');
-  ol.DEBUG && console.assert(this.height_ !== undefined, 'height is defined');
-  ol.DEBUG && console.assert(this.imageHeight_ !== undefined,
-      'imageHeight is defined');
-  ol.DEBUG && console.assert(this.imageWidth_ !== undefined, 'imageWidth is defined');
-  ol.DEBUG && console.assert(this.opacity_ !== undefined, 'opacity is defined');
-  ol.DEBUG && console.assert(this.originX_ !== undefined, 'originX is defined');
-  ol.DEBUG && console.assert(this.originY_ !== undefined, 'originY is defined');
-  ol.DEBUG && console.assert(this.rotateWithView_ !== undefined,
-      'rotateWithView is defined');
-  ol.DEBUG && console.assert(this.rotation_ !== undefined, 'rotation is defined');
-  ol.DEBUG && console.assert(this.scale_ !== undefined, 'scale is defined');
-  ol.DEBUG && console.assert(this.width_ !== undefined, 'width is defined');
-  var anchorX = /** @type {number} */ (this.anchorX_);
-  var anchorY = /** @type {number} */ (this.anchorY_);
-  var height = /** @type {number} */ (this.height_);
-  var imageHeight = /** @type {number} */ (this.imageHeight_);
-  var imageWidth = /** @type {number} */ (this.imageWidth_);
-  var opacity = /** @type {number} */ (this.opacity_);
-  var originX = /** @type {number} */ (this.originX_);
-  var originY = /** @type {number} */ (this.originY_);
-  var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0;
-  var rotation = /** @type {number} */ (this.rotation_);
-  var scale = /** @type {number} */ (this.scale_);
-  var width = /** @type {number} */ (this.width_);
-  var cos = Math.cos(rotation);
-  var sin = Math.sin(rotation);
-  var numIndices = this.indices_.length;
-  var numVertices = this.vertices_.length;
-  var i, n, offsetX, offsetY, x, y;
-  for (i = offset; i < end; i += stride) {
-    x = flatCoordinates[i] - this.origin_[0];
-    y = flatCoordinates[i + 1] - this.origin_[1];
-
-    // There are 4 vertices per [x, y] point, one for each corner of the
-    // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if
-    // WebGL supported Geometry Shaders (which can emit new vertices), but that
-    // is not currently the case.
-    //
-    // And each vertex includes 8 values: the x and y coordinates, the x and
-    // y offsets used to calculate the position of the corner, the u and
-    // v texture coordinates for the corner, the opacity, and whether the
-    // the image should be rotated with the view (rotateWithView).
-
-    n = numVertices / 8;
-
-    // bottom-left corner
-    offsetX = -scale * anchorX;
-    offsetY = -scale * (height - anchorY);
-    this.vertices_[numVertices++] = x;
-    this.vertices_[numVertices++] = y;
-    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
-    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
-    this.vertices_[numVertices++] = originX / imageWidth;
-    this.vertices_[numVertices++] = (originY + height) / imageHeight;
-    this.vertices_[numVertices++] = opacity;
-    this.vertices_[numVertices++] = rotateWithView;
-
-    // bottom-right corner
-    offsetX = scale * (width - anchorX);
-    offsetY = -scale * (height - anchorY);
-    this.vertices_[numVertices++] = x;
-    this.vertices_[numVertices++] = y;
-    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
-    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
-    this.vertices_[numVertices++] = (originX + width) / imageWidth;
-    this.vertices_[numVertices++] = (originY + height) / imageHeight;
-    this.vertices_[numVertices++] = opacity;
-    this.vertices_[numVertices++] = rotateWithView;
-
-    // top-right corner
-    offsetX = scale * (width - anchorX);
-    offsetY = scale * anchorY;
-    this.vertices_[numVertices++] = x;
-    this.vertices_[numVertices++] = y;
-    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
-    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
-    this.vertices_[numVertices++] = (originX + width) / imageWidth;
-    this.vertices_[numVertices++] = originY / imageHeight;
-    this.vertices_[numVertices++] = opacity;
-    this.vertices_[numVertices++] = rotateWithView;
-
-    // top-left corner
-    offsetX = -scale * anchorX;
-    offsetY = scale * anchorY;
-    this.vertices_[numVertices++] = x;
-    this.vertices_[numVertices++] = y;
-    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
-    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
-    this.vertices_[numVertices++] = originX / imageWidth;
-    this.vertices_[numVertices++] = originY / imageHeight;
-    this.vertices_[numVertices++] = opacity;
-    this.vertices_[numVertices++] = rotateWithView;
-
-    this.indices_[numIndices++] = n;
-    this.indices_[numIndices++] = n + 1;
-    this.indices_[numIndices++] = n + 2;
-    this.indices_[numIndices++] = n;
-    this.indices_[numIndices++] = n + 2;
-    this.indices_[numIndices++] = n + 3;
-  }
-
-  return numVertices;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) {
-  this.startIndices_.push(this.indices_.length);
-  this.startIndicesFeature_.push(feature);
-  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
-  var stride = multiPointGeometry.getStride();
-  this.drawCoordinates_(
-      flatCoordinates, 0, flatCoordinates.length, stride);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) {
-  this.startIndices_.push(this.indices_.length);
-  this.startIndicesFeature_.push(feature);
-  var flatCoordinates = pointGeometry.getFlatCoordinates();
-  var stride = pointGeometry.getStride();
-  this.drawCoordinates_(
-      flatCoordinates, 0, flatCoordinates.length, stride);
-};
-
-
-/**
- * @param {ol.webgl.Context} context Context.
- */
-ol.render.webgl.ImageReplay.prototype.finish = function(context) {
-  var gl = context.getGL();
-
-  this.groupIndices_.push(this.indices_.length);
-  ol.DEBUG && console.assert(this.images_.length === this.groupIndices_.length,
-      'number of images and groupIndices match');
-  this.hitDetectionGroupIndices_.push(this.indices_.length);
-  ol.DEBUG && console.assert(this.hitDetectionImages_.length ===
-      this.hitDetectionGroupIndices_.length,
-      'number of hitDetectionImages and hitDetectionGroupIndices match');
-
-  // create, bind, and populate the vertices buffer
-  this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_);
-  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_);
-
-  var indices = this.indices_;
-  var bits = context.hasOESElementIndexUint ? 32 : 16;
-  ol.DEBUG && console.assert(indices[indices.length - 1] < Math.pow(2, bits),
-      'Too large element index detected [%s] (OES_element_index_uint "%s")',
-      indices[indices.length - 1], context.hasOESElementIndexUint);
-
-  // create, bind, and populate the indices buffer
-  this.indicesBuffer_ = new ol.webgl.Buffer(indices);
-  context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
-
-  // create textures
-  /** @type {Object.<string, WebGLTexture>} */
-  var texturePerImage = {};
-
-  this.createTextures_(this.textures_, this.images_, texturePerImage, gl);
-  ol.DEBUG && console.assert(this.textures_.length === this.groupIndices_.length,
-      'number of textures and groupIndices match');
-
-  this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_,
-      texturePerImage, gl);
-  ol.DEBUG && console.assert(this.hitDetectionTextures_.length ===
-      this.hitDetectionGroupIndices_.length,
-      'number of hitDetectionTextures and hitDetectionGroupIndices match');
-
-  this.anchorX_ = undefined;
-  this.anchorY_ = undefined;
-  this.height_ = undefined;
-  this.images_ = null;
-  this.hitDetectionImages_ = null;
-  this.imageHeight_ = undefined;
-  this.imageWidth_ = undefined;
-  this.indices_ = null;
-  this.opacity_ = undefined;
-  this.originX_ = undefined;
-  this.originY_ = undefined;
-  this.rotateWithView_ = undefined;
-  this.rotation_ = undefined;
-  this.scale_ = undefined;
-  this.vertices_ = null;
-  this.width_ = undefined;
-};
-
-
-/**
- * @private
- * @param {Array.<WebGLTexture>} textures Textures.
- * @param {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} images
- *    Images.
- * @param {Object.<string, WebGLTexture>} texturePerImage Texture cache.
- * @param {WebGLRenderingContext} gl Gl.
- */
-ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, images, texturePerImage, gl) {
-  ol.DEBUG && console.assert(textures.length === 0,
-      'upon creation, textures is empty');
-
-  var texture, image, uid, i;
-  var ii = images.length;
-  for (i = 0; i < ii; ++i) {
-    image = images[i];
-
-    uid = ol.getUid(image).toString();
-    if (uid in texturePerImage) {
-      texture = texturePerImage[uid];
-    } else {
-      texture = ol.webgl.Context.createTexture(
-          gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE);
-      texturePerImage[uid] = texture;
-    }
-    textures[i] = texture;
-  }
-};
-
-
-/**
- * @param {ol.webgl.Context} context Context.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Size} size Size.
- * @param {number} pixelRatio Pixel ratio.
- * @param {number} opacity Global opacity.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
- * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
- * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
- *  this extent are checked.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.webgl.ImageReplay.prototype.replay = function(context,
-    center, resolution, rotation, size, pixelRatio,
-    opacity, skippedFeaturesHash,
-    featureCallback, oneByOne, opt_hitExtent) {
-  var gl = context.getGL();
-
-  // bind the vertices buffer
-  ol.DEBUG && console.assert(this.verticesBuffer_,
-      'verticesBuffer must not be null');
-  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_);
-
-  // bind the indices buffer
-  ol.DEBUG && console.assert(this.indicesBuffer_,
-      'indecesBuffer must not be null');
-  context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
-
-  // get the program
-  var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment;
-  var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex;
-  var program = context.getProgram(fragmentShader, vertexShader);
-
-  // get the locations
-  var locations;
-  if (!this.defaultLocations_) {
-    locations =
-        new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program);
-    this.defaultLocations_ = locations;
-  } else {
-    locations = this.defaultLocations_;
-  }
-
-  // use the program (FIXME: use the return value)
-  context.useProgram(program);
-
-  // enable the vertex attrib arrays
-  gl.enableVertexAttribArray(locations.a_position);
-  gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
-      false, 32, 0);
-
-  gl.enableVertexAttribArray(locations.a_offsets);
-  gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT,
-      false, 32, 8);
-
-  gl.enableVertexAttribArray(locations.a_texCoord);
-  gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT,
-      false, 32, 16);
-
-  gl.enableVertexAttribArray(locations.a_opacity);
-  gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT,
-      false, 32, 24);
-
-  gl.enableVertexAttribArray(locations.a_rotateWithView);
-  gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT,
-      false, 32, 28);
-
-  // set the "uniform" values
-  var projectionMatrix = ol.transform.reset(this.projectionMatrix_);
-  ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1]));
-  ol.transform.rotate(projectionMatrix, -rotation);
-  ol.transform.translate(projectionMatrix, -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1]));
-
-  var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_);
-  ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]);
-
-  var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_);
-  if (rotation !== 0) {
-    ol.transform.rotate(offsetRotateMatrix, -rotation);
-  }
-
-  gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
-      ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix));
-  gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false,
-      ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix));
-  gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false,
-      ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix));
-  gl.uniform1f(locations.u_opacity, opacity);
-
-  // draw!
-  var result;
-  if (featureCallback === undefined) {
-    this.drawReplay_(gl, context, skippedFeaturesHash,
-        this.textures_, this.groupIndices_);
-  } else {
-    // draw feature by feature for the hit-detection
-    result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash,
-        featureCallback, oneByOne, opt_hitExtent);
-  }
-
-  // disable the vertex attrib arrays
-  gl.disableVertexAttribArray(locations.a_position);
-  gl.disableVertexAttribArray(locations.a_offsets);
-  gl.disableVertexAttribArray(locations.a_texCoord);
-  gl.disableVertexAttribArray(locations.a_opacity);
-  gl.disableVertexAttribArray(locations.a_rotateWithView);
-
-  return result;
-};
-
-
-/**
- * @private
- * @param {WebGLRenderingContext} gl gl.
- * @param {ol.webgl.Context} context Context.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {Array.<WebGLTexture>} textures Textures.
- * @param {Array.<number>} groupIndices Texture group indices.
- */
-ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, textures, groupIndices) {
-  ol.DEBUG && console.assert(textures.length === groupIndices.length,
-      'number of textures and groupIndeces match');
-  var elementType = context.hasOESElementIndexUint ?
-      ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT;
-  var elementSize = context.hasOESElementIndexUint ? 4 : 2;
-
-  if (!ol.obj.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(ol.webgl.TEXTURE_2D, textures[i]);
-      var end = groupIndices[i];
-      this.drawElements_(gl, start, end, elementType, elementSize);
-      start = end;
-    }
-  }
-};
-
-
-/**
- * Draw the replay while paying attention to skipped features.
- *
- * This functions creates groups of features that can be drawn to together,
- * so that the number of `drawElements` calls is minimized.
- *
- * For example given the following texture groups:
- *
- *    Group 1: A B C
- *    Group 2: D [E] F G
- *
- * If feature E should be skipped, the following `drawElements` calls will be
- * made:
- *
- *    drawElements with feature A, B and C
- *    drawElements with feature D
- *    drawElements with feature F and G
- *
- * @private
- * @param {WebGLRenderingContext} gl gl.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {Array.<WebGLTexture>} textures Textures.
- * @param {Array.<number>} groupIndices Texture group indices.
- * @param {number} elementType Element type.
- * @param {number} elementSize Element Size.
- */
-ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, skippedFeaturesHash, textures, groupIndices,
-    elementType, elementSize) {
-  var featureIndex = 0;
-
-  var i, ii;
-  for (i = 0, ii = textures.length; i < ii; ++i) {
-    gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]);
-    var groupStart = (i > 0) ? groupIndices[i - 1] : 0;
-    var groupEnd = groupIndices[i];
-
-    var start = groupStart;
-    var end = groupStart;
-    while (featureIndex < this.startIndices_.length &&
-        this.startIndices_[featureIndex] <= groupEnd) {
-      var feature = this.startIndicesFeature_[featureIndex];
-
-      var featureUid = ol.getUid(feature).toString();
-      if (skippedFeaturesHash[featureUid] !== undefined) {
-        // feature should be skipped
-        if (start !== end) {
-          // draw the features so far
-          this.drawElements_(gl, 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(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
-};
-
-
-/**
- * @private
- * @param {WebGLRenderingContext} gl gl.
- * @param {ol.webgl.Context} context Context.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
- * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
- * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
- *  this extent are checked.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = function(gl, context, skippedFeaturesHash, featureCallback, oneByOne,
-    opt_hitExtent) {
-  if (!oneByOne) {
-    // draw all hit-detection features in "once" (by texture group)
-    return this.drawHitDetectionReplayAll_(gl, context,
-        skippedFeaturesHash, featureCallback);
-  } else {
-    // draw hit-detection features one by one
-    return this.drawHitDetectionReplayOneByOne_(gl, context,
-        skippedFeaturesHash, featureCallback, opt_hitExtent);
-  }
-};
-
-
-/**
- * @private
- * @param {WebGLRenderingContext} gl gl.
- * @param {ol.webgl.Context} context Context.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, featureCallback) {
-  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-  this.drawReplay_(gl, context, skippedFeaturesHash,
-      this.hitDetectionTextures_, this.hitDetectionGroupIndices_);
-
-  var result = featureCallback(null);
-  if (result) {
-    return result;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @private
- * @param {WebGLRenderingContext} gl gl.
- * @param {ol.webgl.Context} context Context.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
- * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
- *  this extent are checked.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, featureCallback,
-    opt_hitExtent) {
-  ol.DEBUG && console.assert(this.hitDetectionTextures_.length ===
-      this.hitDetectionGroupIndices_.length,
-      'number of hitDetectionTextures and hitDetectionGroupIndices match');
-  var elementType = context.hasOESElementIndexUint ?
-      ol.webgl.UNSIGNED_INT : ol.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(ol.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]);
-    groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0;
-    end = this.hitDetectionGroupIndices_[i];
-
-    // draw all features for this texture group
-    while (featureIndex >= 0 &&
-        this.startIndices_[featureIndex] >= groupStart) {
-      start = this.startIndices_[featureIndex];
-      feature = this.startIndicesFeature_[featureIndex];
-      featureUid = ol.getUid(feature).toString();
-
-      if (skippedFeaturesHash[featureUid] === undefined &&
-          feature.getGeometry() &&
-          (opt_hitExtent === undefined || ol.extent.intersects(
-              /** @type {Array<number>} */ (opt_hitExtent),
-              feature.getGeometry().getExtent()))) {
-        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-        this.drawElements_(gl, start, end, elementType, elementSize);
-
-        var result = featureCallback(feature);
-        if (result) {
-          return result;
-        }
-      }
-
-      end = start;
-      featureIndex--;
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @inheritDoc
- * @abstract
- */
-ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = function() {};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) {
-  var anchor = imageStyle.getAnchor();
-  var image = imageStyle.getImage(1);
-  var imageSize = imageStyle.getImageSize();
-  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
-  var hitDetectionImageSize = imageStyle.getHitDetectionImageSize();
-  var opacity = imageStyle.getOpacity();
-  var origin = imageStyle.getOrigin();
-  var rotateWithView = imageStyle.getRotateWithView();
-  var rotation = imageStyle.getRotation();
-  var size = imageStyle.getSize();
-  var scale = imageStyle.getScale();
-  ol.DEBUG && console.assert(anchor, 'imageStyle anchor is not null');
-  ol.DEBUG && console.assert(image, 'imageStyle image is not null');
-  ol.DEBUG && console.assert(imageSize,
-      'imageStyle imageSize is not null');
-  ol.DEBUG && console.assert(hitDetectionImage,
-      'imageStyle hitDetectionImage is not null');
-  ol.DEBUG && console.assert(hitDetectionImageSize,
-      'imageStyle hitDetectionImageSize is not null');
-  ol.DEBUG && console.assert(opacity !== undefined, 'imageStyle opacity is defined');
-  ol.DEBUG && console.assert(origin, 'imageStyle origin is not null');
-  ol.DEBUG && console.assert(rotateWithView !== undefined,
-      'imageStyle rotateWithView is defined');
-  ol.DEBUG && console.assert(rotation !== undefined, 'imageStyle rotation is defined');
-  ol.DEBUG && console.assert(size, 'imageStyle size is not null');
-  ol.DEBUG && console.assert(scale !== undefined, 'imageStyle scale is defined');
-
-  var currentImage;
-  if (this.images_.length === 0) {
-    this.images_.push(image);
-  } else {
-    currentImage = this.images_[this.images_.length - 1];
-    if (ol.getUid(currentImage) != ol.getUid(image)) {
-      this.groupIndices_.push(this.indices_.length);
-      ol.DEBUG && console.assert(this.groupIndices_.length === this.images_.length,
-          'number of groupIndices and images match');
-      this.images_.push(image);
-    }
-  }
-
-  if (this.hitDetectionImages_.length === 0) {
-    this.hitDetectionImages_.push(hitDetectionImage);
-  } else {
-    currentImage =
-        this.hitDetectionImages_[this.hitDetectionImages_.length - 1];
-    if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) {
-      this.hitDetectionGroupIndices_.push(this.indices_.length);
-      ol.DEBUG && console.assert(this.hitDetectionGroupIndices_.length ===
-          this.hitDetectionImages_.length,
-          'number of hitDetectionGroupIndices and hitDetectionImages match');
-      this.hitDetectionImages_.push(hitDetectionImage);
-    }
-  }
-
-  this.anchorX_ = anchor[0];
-  this.anchorY_ = anchor[1];
-  this.height_ = size[1];
-  this.imageHeight_ = imageSize[1];
-  this.imageWidth_ = imageSize[0];
-  this.opacity_ = opacity;
-  this.originX_ = origin[0];
-  this.originY_ = origin[1];
-  this.rotation_ = rotation;
-  this.rotateWithView_ = rotateWithView;
-  this.scale_ = scale;
-  this.width_ = size[0];
-};
-
-
-/**
- * @constructor
- * @extends {ol.render.ReplayGroup}
- * @param {number} tolerance Tolerance.
- * @param {ol.Extent} maxExtent Max extent.
- * @param {number=} opt_renderBuffer Render buffer.
- * @struct
- */
-ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) {
-  ol.render.ReplayGroup.call(this);
-
-  /**
-   * @type {ol.Extent}
-   * @private
-   */
-  this.maxExtent_ = maxExtent;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.tolerance_ = tolerance;
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.renderBuffer_ = opt_renderBuffer;
-
-  /**
-   * ImageReplay only is supported at this point.
-   * @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>}
-   * @private
-   */
-  this.replays_ = {};
-
-};
-ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);
-
-
-/**
- * @param {ol.webgl.Context} context WebGL context.
- * @return {function()} Delete resources function.
- */
-ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) {
-  var functions = [];
-  var replayKey;
-  for (replayKey in this.replays_) {
-    functions.push(
-        this.replays_[replayKey].getDeleteResourcesFunction(context));
-  }
-  return function() {
-    var length = functions.length;
-    var result;
-    for (var i = 0; i < length; i++) {
-      result = functions[i].apply(this, arguments);
-    }
-    return result;
-  };
-};
-
-
-/**
- * @param {ol.webgl.Context} context Context.
- */
-ol.render.webgl.ReplayGroup.prototype.finish = function(context) {
-  var replayKey;
-  for (replayKey in this.replays_) {
-    this.replays_[replayKey].finish(context);
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {
-  var replay = this.replays_[replayType];
-  if (replay === undefined) {
-    var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType];
-    replay = new constructor(this.tolerance_, this.maxExtent_);
-    this.replays_[replayType] = replay;
-  }
-  return replay;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
-  return ol.obj.isEmpty(this.replays_);
-};
-
-
-/**
- * @param {ol.webgl.Context} context Context.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Size} size Size.
- * @param {number} pixelRatio Pixel ratio.
- * @param {number} opacity Global opacity.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- */
-ol.render.webgl.ReplayGroup.prototype.replay = function(context,
-    center, resolution, rotation, size, pixelRatio,
-    opacity, skippedFeaturesHash) {
-  var i, ii, replay;
-  for (i = 0, ii = ol.render.replay.ORDER.length; i < ii; ++i) {
-    replay = this.replays_[ol.render.replay.ORDER[i]];
-    if (replay !== undefined) {
-      replay.replay(context,
-          center, resolution, rotation, size, pixelRatio,
-          opacity, skippedFeaturesHash,
-          undefined, false);
-    }
-  }
-};
-
-
-/**
- * @private
- * @param {ol.webgl.Context} context Context.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Size} size Size.
- * @param {number} pixelRatio Pixel ratio.
- * @param {number} opacity Global opacity.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
- * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
- * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
- *  this extent are checked.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context,
-    center, resolution, rotation, size, pixelRatio, opacity,
-    skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) {
-  var i, replay, result;
-  for (i = ol.render.replay.ORDER.length - 1; i >= 0; --i) {
-    replay = this.replays_[ol.render.replay.ORDER[i]];
-    if (replay !== undefined) {
-      result = replay.replay(context,
-          center, resolution, rotation, size, pixelRatio, opacity,
-          skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent);
-      if (result) {
-        return result;
-      }
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.webgl.Context} context Context.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Size} size Size.
- * @param {number} pixelRatio Pixel ratio.
- * @param {number} opacity Global opacity.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback.
- * @return {T|undefined} Callback result.
- * @template T
- */
-ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
-    coordinate, context, center, resolution, rotation, size, pixelRatio,
-    opacity, skippedFeaturesHash,
-    callback) {
-  var gl = context.getGL();
-  gl.bindFramebuffer(
-      gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());
-
-
-  /**
-   * @type {ol.Extent}
-   */
-  var hitExtent;
-  if (this.renderBuffer_ !== undefined) {
-    // build an extent around the coordinate, so that only features that
-    // intersect this extent are checked
-    hitExtent = ol.extent.buffer(
-        ol.extent.createOrUpdateFromCoordinate(coordinate),
-        resolution * this.renderBuffer_);
-  }
-
-  return this.replayHitDetection_(context,
-      coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_,
-      pixelRatio, opacity, skippedFeaturesHash,
-      /**
-       * @param {ol.Feature|ol.render.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        var imageData = new Uint8Array(4);
-        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);
-
-        if (imageData[3] > 0) {
-          var result = callback(feature);
-          if (result) {
-            return result;
-          }
-        }
-      }, true, hitExtent);
-};
-
-
-/**
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {ol.webgl.Context} context Context.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Size} size Size.
- * @param {number} pixelRatio Pixel ratio.
- * @param {number} opacity Global opacity.
- * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
- *  to skip.
- * @return {boolean} Is there a feature at the given coordinate?
- */
-ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function(
-    coordinate, context, center, resolution, rotation, size, pixelRatio,
-    opacity, skippedFeaturesHash) {
-  var gl = context.getGL();
-  gl.bindFramebuffer(
-      gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());
-
-  var hasFeature = this.replayHitDetection_(context,
-      coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_,
-      pixelRatio, opacity, skippedFeaturesHash,
-      /**
-       * @param {ol.Feature|ol.render.Feature} feature Feature.
-       * @return {boolean} Is there a feature?
-       */
-      function(feature) {
-        var imageData = new Uint8Array(4);
-        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);
-        return imageData[3] > 0;
-      }, false);
-
-  return hasFeature !== undefined;
-};
-
-
-/**
- * @const
- * @private
- * @type {Object.<ol.render.ReplayType,
- *                function(new: ol.render.webgl.ImageReplay, number,
- *                ol.Extent)>}
- */
-ol.render.webgl.BATCH_CONSTRUCTORS_ = {
-  'Image': ol.render.webgl.ImageReplay
-};
-
-
-/**
- * @const
- * @private
- * @type {Array.<number>}
- */
-ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1];
-
-goog.provide('ol.render.webgl.Immediate');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.render.ReplayType');
-goog.require('ol.render.VectorContext');
-goog.require('ol.render.webgl.ReplayGroup');
-
-
-/**
- * @constructor
- * @extends {ol.render.VectorContext}
- * @param {ol.webgl.Context} context Context.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} rotation Rotation.
- * @param {ol.Size} size Size.
- * @param {ol.Extent} extent Extent.
- * @param {number} pixelRatio Pixel ratio.
- * @struct
- */
-ol.render.webgl.Immediate = function(context, center, resolution, rotation, size, extent, pixelRatio) {
-  ol.render.VectorContext.call(this);
-
-  /**
-   * @private
-   */
-  this.context_ = context;
-
-  /**
-   * @private
-   */
-  this.center_ = center;
-
-  /**
-   * @private
-   */
-  this.extent_ = extent;
-
-  /**
-   * @private
-   */
-  this.pixelRatio_ = pixelRatio;
-
-  /**
-   * @private
-   */
-  this.size_ = size;
-
-  /**
-   * @private
-   */
-  this.rotation_ = rotation;
-
-  /**
-   * @private
-   */
-  this.resolution_ = resolution;
-
-  /**
-   * @private
-   * @type {ol.style.Image}
-   */
-  this.imageStyle_ = null;
-
-};
-ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext);
-
-
-/**
- * Set the rendering style.  Note that since this is an immediate rendering API,
- * any `zIndex` on the provided style will be ignored.
- *
- * @param {ol.style.Style} style The rendering style.
- * @api
- */
-ol.render.webgl.Immediate.prototype.setStyle = function(style) {
-  this.setImageStyle(style.getImage());
-};
-
-
-/**
- * Render a geometry into the canvas.  Call
- * {@link ol.render.webgl.Immediate#setStyle} first to set the rendering style.
- *
- * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render.
- * @api
- */
-ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) {
-  var type = geometry.getType();
-  switch (type) {
-    case ol.geom.GeometryType.POINT:
-      this.drawPoint(/** @type {ol.geom.Point} */ (geometry), null);
-      break;
-    case ol.geom.GeometryType.MULTI_POINT:
-      this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry), null);
-      break;
-    case ol.geom.GeometryType.GEOMETRY_COLLECTION:
-      this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry), null);
-      break;
-    default:
-      ol.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type);
-  }
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) {
-  var geometry = style.getGeometryFunction()(feature);
-  if (!geometry ||
-      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
-    return;
-  }
-  this.setStyle(style);
-  ol.DEBUG && console.assert(geometry, 'geometry must be truthy');
-  this.drawGeometry(geometry);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.drawGeometryCollection = function(geometry, data) {
-  var geometries = geometry.getGeometriesArray();
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    this.drawGeometry(geometries[i]);
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, data) {
-  var context = this.context_;
-  var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
-  var replay = /** @type {ol.render.webgl.ImageReplay} */ (
-      replayGroup.getReplay(0, ol.render.ReplayType.IMAGE));
-  replay.setImageStyle(this.imageStyle_);
-  replay.drawPoint(geometry, data);
-  replay.finish(context);
-  // default colors
-  var opacity = 1;
-  var skippedFeatures = {};
-  var featureCallback;
-  var oneByOne = false;
-  replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
-      this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback,
-      oneByOne);
-  replay.getDeleteResourcesFunction(context)();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) {
-  var context = this.context_;
-  var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
-  var replay = /** @type {ol.render.webgl.ImageReplay} */ (
-      replayGroup.getReplay(0, ol.render.ReplayType.IMAGE));
-  replay.setImageStyle(this.imageStyle_);
-  replay.drawMultiPoint(geometry, data);
-  replay.finish(context);
-  var opacity = 1;
-  var skippedFeatures = {};
-  var featureCallback;
-  var oneByOne = false;
-  replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
-      this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback,
-      oneByOne);
-  replay.getDeleteResourcesFunction(context)();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
-  this.imageStyle_ = imageStyle;
-};
-
-// This file is automatically generated, do not edit
-goog.provide('ol.renderer.webgl.defaultmapshader');
-
-goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Fragment}
- * @struct
- */
-ol.renderer.webgl.defaultmapshader.Fragment = function() {
-  ol.webgl.Fragment.call(this, ol.renderer.webgl.defaultmapshader.Fragment.SOURCE);
-};
-ol.inherits(ol.renderer.webgl.defaultmapshader.Fragment, ol.webgl.Fragment);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_texture, v_texCoord);\n  gl_FragColor.rgb = texColor.rgb;\n  gl_FragColor.a = texColor.a * u_opacity;\n}\n';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.defaultmapshader.Fragment.SOURCE = ol.DEBUG ?
-    ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE :
-    ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE;
-
-
-ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment();
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Vertex}
- * @struct
- */
-ol.renderer.webgl.defaultmapshader.Vertex = function() {
-  ol.webgl.Vertex.call(this, ol.renderer.webgl.defaultmapshader.Vertex.SOURCE);
-};
-ol.inherits(ol.renderer.webgl.defaultmapshader.Vertex, ol.webgl.Vertex);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n  v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = ol.DEBUG ?
-    ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE :
-    ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE;
-
-
-ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex();
-
-
-/**
- * @constructor
- * @param {WebGLRenderingContext} gl GL.
- * @param {WebGLProgram} program Program.
- * @struct
- */
-ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) {
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_opacity = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_opacity' : 'f');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_projectionMatrix = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_projectionMatrix' : 'e');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_texCoordMatrix = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_texCoordMatrix' : 'd');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_texture = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_texture' : 'g');
-
-  /**
-   * @type {number}
-   */
-  this.a_position = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_position' : 'b');
-
-  /**
-   * @type {number}
-   */
-  this.a_texCoord = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_texCoord' : 'c');
-};
-
-goog.provide('ol.renderer.webgl.Layer');
-
-goog.require('ol');
-goog.require('ol.render.Event');
-goog.require('ol.render.webgl.Immediate');
-goog.require('ol.renderer.Layer');
-goog.require('ol.renderer.webgl.defaultmapshader');
-goog.require('ol.transform');
-goog.require('ol.vec.Mat4');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Buffer');
-goog.require('ol.webgl.Context');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.Layer}
- * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
- * @param {ol.layer.Layer} layer Layer.
- */
-ol.renderer.webgl.Layer = function(mapRenderer, layer) {
-
-  ol.renderer.Layer.call(this, layer);
-
-  /**
-   * @protected
-   * @type {ol.renderer.webgl.Map}
-   */
-  this.mapRenderer = mapRenderer;
-
-  /**
-   * @private
-   * @type {ol.webgl.Buffer}
-   */
-  this.arrayBuffer_ = new ol.webgl.Buffer([
-    -1, -1, 0, 0,
-    1, -1, 1, 0,
-    -1, 1, 0, 1,
-    1, 1, 1, 1
-  ]);
-
-  /**
-   * @protected
-   * @type {WebGLTexture}
-   */
-  this.texture = null;
-
-  /**
-   * @protected
-   * @type {WebGLFramebuffer}
-   */
-  this.framebuffer = null;
-
-  /**
-   * @protected
-   * @type {number|undefined}
-   */
-  this.framebufferDimension = undefined;
-
-  /**
-   * @protected
-   * @type {ol.Transform}
-   */
-  this.texCoordMatrix = ol.transform.create();
-
-  /**
-   * @protected
-   * @type {ol.Transform}
-   */
-  this.projectionMatrix = ol.transform.create();
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.tmpMat4_ = ol.vec.Mat4.create();
-
-  /**
-   * @private
-   * @type {ol.renderer.webgl.defaultmapshader.Locations}
-   */
-  this.defaultLocations_ = null;
-
-};
-ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {number} framebufferDimension Framebuffer dimension.
- * @protected
- */
-ol.renderer.webgl.Layer.prototype.bindFramebuffer = function(frameState, framebufferDimension) {
-
-  var gl = this.mapRenderer.getGL();
-
-  if (this.framebufferDimension === undefined ||
-      this.framebufferDimension != framebufferDimension) {
-    /**
-     * @param {WebGLRenderingContext} gl GL.
-     * @param {WebGLFramebuffer} framebuffer Framebuffer.
-     * @param {WebGLTexture} texture Texture.
-     */
-    var postRenderFunction = function(gl, framebuffer, texture) {
-      if (!gl.isContextLost()) {
-        gl.deleteFramebuffer(framebuffer);
-        gl.deleteTexture(texture);
-      }
-    }.bind(null, gl, this.framebuffer, this.texture);
-
-    frameState.postRenderFunctions.push(
-      /** @type {ol.PostRenderFunction} */ (postRenderFunction)
-    );
-
-    var texture = ol.webgl.Context.createEmptyTexture(
-        gl, framebufferDimension, framebufferDimension);
-
-    var framebuffer = gl.createFramebuffer();
-    gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, framebuffer);
-    gl.framebufferTexture2D(ol.webgl.FRAMEBUFFER,
-        ol.webgl.COLOR_ATTACHMENT0, ol.webgl.TEXTURE_2D, texture, 0);
-
-    this.texture = texture;
-    this.framebuffer = framebuffer;
-    this.framebufferDimension = framebufferDimension;
-
-  } else {
-    gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer);
-  }
-
-};
-
-
-/**
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @param {ol.webgl.Context} context Context.
- */
-ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) {
-
-  this.dispatchComposeEvent_(
-      ol.render.Event.Type.PRECOMPOSE, context, frameState);
-
-  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_);
-
-  var gl = context.getGL();
-
-  var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment;
-  var vertexShader = ol.renderer.webgl.defaultmapshader.vertex;
-
-  var program = context.getProgram(fragmentShader, vertexShader);
-
-  var locations;
-  if (!this.defaultLocations_) {
-    locations =
-        new ol.renderer.webgl.defaultmapshader.Locations(gl, program);
-    this.defaultLocations_ = locations;
-  } else {
-    locations = this.defaultLocations_;
-  }
-
-  if (context.useProgram(program)) {
-    gl.enableVertexAttribArray(locations.a_position);
-    gl.vertexAttribPointer(
-        locations.a_position, 2, ol.webgl.FLOAT, false, 16, 0);
-    gl.enableVertexAttribArray(locations.a_texCoord);
-    gl.vertexAttribPointer(
-        locations.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8);
-    gl.uniform1i(locations.u_texture, 0);
-  }
-
-  gl.uniformMatrix4fv(locations.u_texCoordMatrix, false,
-      ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getTexCoordMatrix()));
-  gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
-      ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getProjectionMatrix()));
-  gl.uniform1f(locations.u_opacity, layerState.opacity);
-  gl.bindTexture(ol.webgl.TEXTURE_2D, this.getTexture());
-  gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4);
-
-  this.dispatchComposeEvent_(
-      ol.render.Event.Type.POSTCOMPOSE, context, frameState);
-
-};
-
-
-/**
- * @param {ol.render.Event.Type} type Event type.
- * @param {ol.webgl.Context} context WebGL context.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState) {
-  var layer = this.getLayer();
-  if (layer.hasListener(type)) {
-    var viewState = frameState.viewState;
-    var resolution = viewState.resolution;
-    var pixelRatio = frameState.pixelRatio;
-    var extent = frameState.extent;
-    var center = viewState.center;
-    var rotation = viewState.rotation;
-    var size = frameState.size;
-
-    var render = new ol.render.webgl.Immediate(
-        context, center, resolution, rotation, size, extent, pixelRatio);
-    var composeEvent = new ol.render.Event(
-        type, render, frameState, null, context);
-    layer.dispatchEvent(composeEvent);
-  }
-};
-
-
-/**
- * @return {!ol.Transform} Matrix.
- */
-ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
-  return this.texCoordMatrix;
-};
-
-
-/**
- * @return {WebGLTexture} Texture.
- */
-ol.renderer.webgl.Layer.prototype.getTexture = function() {
-  return this.texture;
-};
-
-
-/**
- * @return {!ol.Transform} Matrix.
- */
-ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
-  return this.projectionMatrix;
-};
-
-
-/**
- * Handle webglcontextlost.
- */
-ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
-  this.texture = null;
-  this.framebuffer = null;
-  this.framebufferDimension = undefined;
-};
-
-
-/**
- * @abstract
- * @param {olx.FrameState} frameState Frame state.
- * @param {ol.LayerState} layerState Layer state.
- * @param {ol.webgl.Context} context Context.
- * @return {boolean} whether composeFrame should be called.
- */
-ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {};
-
-goog.provide('ol.renderer.webgl.ImageLayer');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.functions');
-goog.require('ol.proj');
-goog.require('ol.renderer.webgl.Layer');
-goog.require('ol.source.ImageVector');
-goog.require('ol.transform');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Context');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.webgl.Layer}
- * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
- * @param {ol.layer.Image} imageLayer Tile layer.
- */
-ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) {
-
-  ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer);
-
-  /**
-   * The last rendered image.
-   * @private
-   * @type {?ol.ImageBase}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.hitCanvasContext_ = null;
-
-  /**
-   * @private
-   * @type {?ol.Transform}
-   */
-  this.hitTransformationMatrix_ = null;
-
-};
-ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);
-
-
-/**
- * @param {ol.ImageBase} image Image.
- * @private
- * @return {WebGLTexture} Texture.
- */
-ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
-
-  // We meet the conditions to work with non-power of two textures.
-  // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support
-  // http://learningwebgl.com/blog/?p=2101
-
-  var imageElement = image.getImage();
-  var gl = this.mapRenderer.getGL();
-
-  return ol.webgl.Context.createTexture(
-      gl, imageElement, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
-  var layer = this.getLayer();
-  var source = layer.getSource();
-  var resolution = frameState.viewState.resolution;
-  var rotation = frameState.viewState.rotation;
-  var skippedFeatureUids = frameState.skippedFeatureUids;
-  return source.forEachFeatureAtCoordinate(
-      coordinate, resolution, rotation, skippedFeatureUids,
-
-      /**
-       * @param {ol.Feature|ol.render.Feature} feature Feature.
-       * @return {?} Callback result.
-       */
-      function(feature) {
-        return callback.call(thisArg, feature, layer);
-      });
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) {
-
-  var gl = this.mapRenderer.getGL();
-
-  var pixelRatio = frameState.pixelRatio;
-  var viewState = frameState.viewState;
-  var viewCenter = viewState.center;
-  var viewResolution = viewState.resolution;
-  var viewRotation = viewState.rotation;
-
-  var image = this.image_;
-  var texture = this.texture;
-  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
-  var imageSource = imageLayer.getSource();
-
-  var hints = frameState.viewHints;
-
-  var renderedExtent = frameState.extent;
-  if (layerState.extent !== undefined) {
-    renderedExtent = ol.extent.getIntersection(
-        renderedExtent, layerState.extent);
-  }
-  if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] &&
-      !ol.extent.isEmpty(renderedExtent)) {
-    var projection = viewState.projection;
-    if (!ol.ENABLE_RASTER_REPROJECTION) {
-      var sourceProjection = imageSource.getProjection();
-      if (sourceProjection) {
-        ol.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection),
-            'projection and sourceProjection are equivalent');
-        projection = sourceProjection;
-      }
-    }
-    var image_ = imageSource.getImage(renderedExtent, viewResolution,
-        pixelRatio, projection);
-    if (image_) {
-      var loaded = this.loadImage(image_);
-      if (loaded) {
-        image = image_;
-        texture = this.createTexture_(image_);
-        if (this.texture) {
-          /**
-           * @param {WebGLRenderingContext} gl GL.
-           * @param {WebGLTexture} texture Texture.
-           */
-          var postRenderFunction = function(gl, texture) {
-            if (!gl.isContextLost()) {
-              gl.deleteTexture(texture);
-            }
-          }.bind(null, gl, this.texture);
-          frameState.postRenderFunctions.push(
-            /** @type {ol.PostRenderFunction} */ (postRenderFunction)
-          );
-        }
-      }
-    }
-  }
-
-  if (image) {
-    ol.DEBUG && console.assert(texture, 'texture is truthy');
-
-    var canvas = this.mapRenderer.getContext().getCanvas();
-
-    this.updateProjectionMatrix_(canvas.width, canvas.height,
-        pixelRatio, viewCenter, viewResolution, viewRotation,
-        image.getExtent());
-    this.hitTransformationMatrix_ = null;
-
-    // Translate and scale to flip the Y coord.
-    var texCoordMatrix = this.texCoordMatrix;
-    ol.transform.reset(texCoordMatrix);
-    ol.transform.scale(texCoordMatrix, 1, -1);
-    ol.transform.translate(texCoordMatrix, 0, -1);
-
-    this.image_ = image;
-    this.texture = texture;
-
-    this.updateAttributions(frameState.attributions, image.getAttributions());
-    this.updateLogos(frameState, imageSource);
-  }
-
-  return true;
-};
-
-
-/**
- * @param {number} canvasWidth Canvas width.
- * @param {number} canvasHeight Canvas height.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.Coordinate} viewCenter View center.
- * @param {number} viewResolution View resolution.
- * @param {number} viewRotation View rotation.
- * @param {ol.Extent} imageExtent Image extent.
- * @private
- */
-ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvasWidth, canvasHeight, pixelRatio,
-        viewCenter, viewResolution, viewRotation, imageExtent) {
-
-  var canvasExtentWidth = canvasWidth * viewResolution;
-  var canvasExtentHeight = canvasHeight * viewResolution;
-
-  var projectionMatrix = this.projectionMatrix;
-  ol.transform.reset(projectionMatrix);
-  ol.transform.scale(projectionMatrix,
-      pixelRatio * 2 / canvasExtentWidth,
-      pixelRatio * 2 / canvasExtentHeight);
-  ol.transform.rotate(projectionMatrix, -viewRotation);
-  ol.transform.translate(projectionMatrix,
-      imageExtent[0] - viewCenter[0],
-      imageExtent[1] - viewCenter[1]);
-  ol.transform.scale(projectionMatrix,
-      (imageExtent[2] - imageExtent[0]) / 2,
-      (imageExtent[3] - imageExtent[1]) / 2);
-  ol.transform.translate(projectionMatrix, 1, 1);
-
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
-  var hasFeature = this.forEachFeatureAtCoordinate(
-      coordinate, frameState, ol.functions.TRUE, this);
-  return hasFeature !== undefined;
-};
-
-
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
- */
-ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
-  if (!this.image_ || !this.image_.getImage()) {
-    return undefined;
-  }
-
-  if (this.getLayer().getSource() instanceof ol.source.ImageVector) {
-    // for ImageVector sources use the original hit-detection logic,
-    // so that for example also transparent polygons are detected
-    var coordinate = ol.transform.apply(
-        frameState.pixelToCoordinateTransform, pixel.slice());
-    var hasFeature = this.forEachFeatureAtCoordinate(
-        coordinate, frameState, ol.functions.TRUE, this);
-
-    if (hasFeature) {
-      return callback.call(thisArg, this.getLayer(), null);
-    } else {
-      return undefined;
-    }
-  } else {
-    var imageSize =
-        [this.image_.getImage().width, this.image_.getImage().height];
-
-    if (!this.hitTransformationMatrix_) {
-      this.hitTransformationMatrix_ = this.getHitTransformationMatrix_(
-          frameState.size, imageSize);
-    }
-
-    var pixelOnFrameBuffer = ol.transform.apply(
-        this.hitTransformationMatrix_, pixel.slice());
-
-    if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] ||
-        pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) {
-      // outside the image, no need to check
-      return undefined;
-    }
-
-    if (!this.hitCanvasContext_) {
-      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
-    }
-
-    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
-    this.hitCanvasContext_.drawImage(this.image_.getImage(),
-        pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1);
-
-    var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
-    if (imageData[3] > 0) {
-      return callback.call(thisArg, this.getLayer(),  imageData);
-    } else {
-      return undefined;
-    }
-  }
-};
-
-
-/**
- * The transformation matrix to get the pixel on the image for a
- * pixel on the map.
- * @param {ol.Size} mapSize The map size.
- * @param {ol.Size} imageSize The image size.
- * @return {ol.Transform} The transformation matrix.
- * @private
- */
-ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = function(mapSize, imageSize) {
-  // the first matrix takes a map pixel, flips the y-axis and scales to
-  // a range between -1 ... 1
-  var mapCoordTransform = ol.transform.create();
-  ol.transform.translate(mapCoordTransform, -1, -1);
-  ol.transform.scale(mapCoordTransform, 2 / mapSize[0], 2 / mapSize[1]);
-  ol.transform.translate(mapCoordTransform, 0, mapSize[1]);
-  ol.transform.scale(mapCoordTransform, 1, -1);
-
-  // the second matrix is the inverse of the projection matrix used in the
-  // shader for drawing
-  var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice());
-
-  // the third matrix scales to the image dimensions and flips the y-axis again
-  var transform = ol.transform.create();
-  ol.transform.translate(transform, 0, imageSize[1]);
-  ol.transform.scale(transform, 1, -1);
-  ol.transform.scale(transform, imageSize[0] / 2, imageSize[1] / 2);
-  ol.transform.translate(transform, 1, 1);
-
-  ol.transform.multiply(transform, projectionMatrixInv);
-  ol.transform.multiply(transform, mapCoordTransform);
-
-  return transform;
-};
-
-// This file is automatically generated, do not edit
-goog.provide('ol.renderer.webgl.tilelayershader');
-
-goog.require('ol');
-goog.require('ol.webgl.Fragment');
-goog.require('ol.webgl.Vertex');
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Fragment}
- * @struct
- */
-ol.renderer.webgl.tilelayershader.Fragment = function() {
-  ol.webgl.Fragment.call(this, ol.renderer.webgl.tilelayershader.Fragment.SOURCE);
-};
-ol.inherits(ol.renderer.webgl.tilelayershader.Fragment, ol.webgl.Fragment);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayershader.Fragment.SOURCE = ol.DEBUG ?
-    ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE :
-    ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE;
-
-
-ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment();
-
-
-/**
- * @constructor
- * @extends {ol.webgl.Vertex}
- * @struct
- */
-ol.renderer.webgl.tilelayershader.Vertex = function() {
-  ol.webgl.Vertex.call(this, ol.renderer.webgl.tilelayershader.Vertex.SOURCE);
-};
-ol.inherits(ol.renderer.webgl.tilelayershader.Vertex, ol.webgl.Vertex);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n  gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n  v_texCoord = a_texCoord;\n}\n\n\n';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.renderer.webgl.tilelayershader.Vertex.SOURCE = ol.DEBUG ?
-    ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE :
-    ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE;
-
-
-ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex();
-
-
-/**
- * @constructor
- * @param {WebGLRenderingContext} gl GL.
- * @param {WebGLProgram} program Program.
- * @struct
- */
-ol.renderer.webgl.tilelayershader.Locations = function(gl, program) {
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_texture = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_texture' : 'e');
-
-  /**
-   * @type {WebGLUniformLocation}
-   */
-  this.u_tileOffset = gl.getUniformLocation(
-      program, ol.DEBUG ? 'u_tileOffset' : 'd');
-
-  /**
-   * @type {number}
-   */
-  this.a_position = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_position' : 'b');
-
-  /**
-   * @type {number}
-   */
-  this.a_texCoord = gl.getAttribLocation(
-      program, ol.DEBUG ? 'a_texCoord' : 'c');
-};
-
-// FIXME large resolutions lead to too large framebuffers :-(
-// FIXME animated shaders! check in redraw
-
-goog.provide('ol.renderer.webgl.TileLayer');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.TileRange');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.renderer.webgl.Layer');
-goog.require('ol.renderer.webgl.tilelayershader');
-goog.require('ol.size');
-goog.require('ol.transform');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Buffer');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.webgl.Layer}
- * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
- * @param {ol.layer.Tile} tileLayer Tile layer.
- */
-ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
-
-  ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer);
-
-  /**
-   * @private
-   * @type {ol.webgl.Fragment}
-   */
-  this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment;
-
-  /**
-   * @private
-   * @type {ol.webgl.Vertex}
-   */
-  this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex;
-
-  /**
-   * @private
-   * @type {ol.renderer.webgl.tilelayershader.Locations}
-   */
-  this.locations_ = null;
-
-  /**
-   * @private
-   * @type {ol.webgl.Buffer}
-   */
-  this.renderArrayBuffer_ = new ol.webgl.Buffer([
-    0, 0, 0, 1,
-    1, 0, 1, 1,
-    0, 1, 0, 0,
-    1, 1, 1, 0
-  ]);
-
-  /**
-   * @private
-   * @type {ol.TileRange}
-   */
-  this.renderedTileRange_ = null;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.renderedFramebufferExtent_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = -1;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.tmpSize_ = [0, 0];
-
-};
-ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
-  var context = this.mapRenderer.getContext();
-  context.deleteBuffer(this.renderArrayBuffer_);
-  ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * Create a function that adds loaded tiles to the tile lookup.
- * @param {ol.source.Tile} source Tile source.
- * @param {ol.proj.Projection} projection Projection of the tiles.
- * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded
- *     tiles by zoom level.
- * @return {function(number, ol.TileRange):boolean} A function that can be
- *     called with a zoom level and a tile range to add loaded tiles to the
- *     lookup.
- * @protected
- */
-ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
-  var mapRenderer = this.mapRenderer;
-
-  return (
-      /**
-       * @param {number} zoom Zoom level.
-       * @param {ol.TileRange} tileRange Tile range.
-       * @return {boolean} The tile range is fully loaded.
-       */
-      function(zoom, tileRange) {
-        function callback(tile) {
-          var loaded = mapRenderer.isTileTextureLoaded(tile);
-          if (loaded) {
-            if (!tiles[zoom]) {
-              tiles[zoom] = {};
-            }
-            tiles[zoom][tile.tileCoord.toString()] = tile;
-          }
-          return loaded;
-        }
-        return source.forEachLoadedTile(projection, zoom, tileRange, callback);
-      });
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
-  ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this);
-  this.locations_ = null;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) {
-
-  var mapRenderer = this.mapRenderer;
-  var gl = context.getGL();
-
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
-
-  var tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer());
-  var tileSource = tileLayer.getSource();
-  var tileGrid = tileSource.getTileGridForProjection(projection);
-  var z = tileGrid.getZForResolution(viewState.resolution);
-  var tileResolution = tileGrid.getResolution(z);
-
-  var tilePixelSize =
-      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
-  var pixelRatio = tilePixelSize[0] /
-      ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0];
-  var tilePixelResolution = tileResolution / pixelRatio;
-  var tileGutter = tileSource.getTilePixelRatio(pixelRatio) * tileSource.getGutter(projection);
-
-  var center = viewState.center;
-  var extent = frameState.extent;
-  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
-      extent, tileResolution);
-
-  var framebufferExtent;
-  if (this.renderedTileRange_ &&
-      this.renderedTileRange_.equals(tileRange) &&
-      this.renderedRevision_ == tileSource.getRevision()) {
-    framebufferExtent = this.renderedFramebufferExtent_;
-  } else {
-
-    var tileRangeSize = tileRange.getSize();
-
-    var maxDimension = Math.max(
-        tileRangeSize[0] * tilePixelSize[0],
-        tileRangeSize[1] * tilePixelSize[1]);
-    var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension);
-    var framebufferExtentDimension = tilePixelResolution * framebufferDimension;
-    var origin = tileGrid.getOrigin(z);
-    var minX = origin[0] +
-        tileRange.minX * tilePixelSize[0] * tilePixelResolution;
-    var minY = origin[1] +
-        tileRange.minY * tilePixelSize[1] * tilePixelResolution;
-    framebufferExtent = [
-      minX, minY,
-      minX + framebufferExtentDimension, minY + framebufferExtentDimension
-    ];
-
-    this.bindFramebuffer(frameState, framebufferDimension);
-    gl.viewport(0, 0, framebufferDimension, framebufferDimension);
-
-    gl.clearColor(0, 0, 0, 0);
-    gl.clear(ol.webgl.COLOR_BUFFER_BIT);
-    gl.disable(ol.webgl.BLEND);
-
-    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
-    context.useProgram(program);
-    if (!this.locations_) {
-      this.locations_ =
-          new ol.renderer.webgl.tilelayershader.Locations(gl, program);
-    }
-
-    context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.renderArrayBuffer_);
-    gl.enableVertexAttribArray(this.locations_.a_position);
-    gl.vertexAttribPointer(
-        this.locations_.a_position, 2, ol.webgl.FLOAT, false, 16, 0);
-    gl.enableVertexAttribArray(this.locations_.a_texCoord);
-    gl.vertexAttribPointer(
-        this.locations_.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8);
-    gl.uniform1i(this.locations_.u_texture, 0);
-
-    /**
-     * @type {Object.<number, Object.<string, ol.Tile>>}
-     */
-    var tilesToDrawByZ = {};
-    tilesToDrawByZ[z] = {};
-
-    var findLoadedTiles = this.createLoadedTileFinder(
-        tileSource, projection, tilesToDrawByZ);
-
-    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
-    var allTilesLoaded = true;
-    var tmpExtent = ol.extent.createEmpty();
-    var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
-    var childTileRange, drawable, fullyLoaded, tile, tileState;
-    var x, y, tileExtent;
-    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
-      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
-
-        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
-        if (layerState.extent !== undefined) {
-          // ignore tiles outside layer extent
-          tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
-          if (!ol.extent.intersects(tileExtent, layerState.extent)) {
-            continue;
-          }
-        }
-        tileState = tile.getState();
-        drawable = tileState == ol.Tile.State.LOADED ||
-            tileState == ol.Tile.State.EMPTY ||
-            tileState == ol.Tile.State.ERROR && !useInterimTilesOnError;
-        if (!drawable) {
-          tile = tile.getInterimTile();
-        }
-        tileState = tile.getState();
-        if (tileState == ol.Tile.State.LOADED) {
-          if (mapRenderer.isTileTextureLoaded(tile)) {
-            tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
-            continue;
-          }
-        } else if (tileState == ol.Tile.State.EMPTY ||
-                   (tileState == ol.Tile.State.ERROR &&
-                    !useInterimTilesOnError)) {
-          continue;
-        }
-
-        allTilesLoaded = false;
-        fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
-            tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
-        if (!fullyLoaded) {
-          childTileRange = tileGrid.getTileCoordChildTileRange(
-              tile.tileCoord, tmpTileRange, tmpExtent);
-          if (childTileRange) {
-            findLoadedTiles(z + 1, childTileRange);
-          }
-        }
-
-      }
-
-    }
-
-    /** @type {Array.<number>} */
-    var zs = Object.keys(tilesToDrawByZ).map(Number);
-    zs.sort(ol.array.numberSafeCompareFunction);
-    var u_tileOffset = new Float32Array(4);
-    var i, ii, tileKey, tilesToDraw;
-    for (i = 0, ii = zs.length; i < ii; ++i) {
-      tilesToDraw = tilesToDrawByZ[zs[i]];
-      for (tileKey in tilesToDraw) {
-        tile = tilesToDraw[tileKey];
-        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
-        u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) /
-            framebufferExtentDimension;
-        u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) /
-            framebufferExtentDimension;
-        u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) /
-            framebufferExtentDimension - 1;
-        u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) /
-            framebufferExtentDimension - 1;
-        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
-        mapRenderer.bindTileTexture(tile, tilePixelSize,
-            tileGutter * pixelRatio, ol.webgl.LINEAR, ol.webgl.LINEAR);
-        gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4);
-      }
-    }
-
-    if (allTilesLoaded) {
-      this.renderedTileRange_ = tileRange;
-      this.renderedFramebufferExtent_ = framebufferExtent;
-      this.renderedRevision_ = tileSource.getRevision();
-    } else {
-      this.renderedTileRange_ = null;
-      this.renderedFramebufferExtent_ = null;
-      this.renderedRevision_ = -1;
-      frameState.animate = true;
-    }
-
-  }
-
-  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
-  var tileTextureQueue = mapRenderer.getTileTextureQueue();
-  this.manageTilePyramid(
-      frameState, tileSource, tileGrid, pixelRatio, projection, extent, z,
-      tileLayer.getPreload(),
-      /**
-       * @param {ol.Tile} tile Tile.
-       */
-      function(tile) {
-        if (tile.getState() == ol.Tile.State.LOADED &&
-            !mapRenderer.isTileTextureLoaded(tile) &&
-            !tileTextureQueue.isKeyQueued(tile.getKey())) {
-          tileTextureQueue.enqueue([
-            tile,
-            tileGrid.getTileCoordCenter(tile.tileCoord),
-            tileGrid.getResolution(tile.tileCoord[0]),
-            tilePixelSize, tileGutter * pixelRatio
-          ]);
-        }
-      }, this);
-  this.scheduleExpireCache(frameState, tileSource);
-  this.updateLogos(frameState, tileSource);
-
-  var texCoordMatrix = this.texCoordMatrix;
-  ol.transform.reset(texCoordMatrix);
-  ol.transform.translate(texCoordMatrix,
-      (Math.round(center[0] / tileResolution) * tileResolution - framebufferExtent[0]) /
-          (framebufferExtent[2] - framebufferExtent[0]),
-      (Math.round(center[1] / tileResolution) * tileResolution - framebufferExtent[1]) /
-          (framebufferExtent[3] - framebufferExtent[1]));
-  if (viewState.rotation !== 0) {
-    ol.transform.rotate(texCoordMatrix, viewState.rotation);
-  }
-  ol.transform.scale(texCoordMatrix,
-      frameState.size[0] * viewState.resolution /
-          (framebufferExtent[2] - framebufferExtent[0]),
-      frameState.size[1] * viewState.resolution /
-          (framebufferExtent[3] - framebufferExtent[1]));
-  ol.transform.translate(texCoordMatrix, -0.5, -0.5);
-
-  return true;
-};
-
-
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
- */
-ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
-  if (!this.framebuffer) {
-    return undefined;
-  }
-
-  var pixelOnMapScaled = [
-    pixel[0] / frameState.size[0],
-    (frameState.size[1] - pixel[1]) / frameState.size[1]];
-
-  var pixelOnFrameBufferScaled = ol.transform.apply(
-      this.texCoordMatrix, pixelOnMapScaled.slice());
-  var pixelOnFrameBuffer = [
-    pixelOnFrameBufferScaled[0] * this.framebufferDimension,
-    pixelOnFrameBufferScaled[1] * this.framebufferDimension];
-
-  var gl = this.mapRenderer.getContext().getGL();
-  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
-  var imageData = new Uint8Array(4);
-  gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1,
-      gl.RGBA, gl.UNSIGNED_BYTE, imageData);
-
-  if (imageData[3] > 0) {
-    return callback.call(thisArg, this.getLayer(), imageData);
-  } else {
-    return undefined;
-  }
-};
-
-goog.provide('ol.renderer.webgl.VectorLayer');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.extent');
-goog.require('ol.render.webgl.ReplayGroup');
-goog.require('ol.renderer.vector');
-goog.require('ol.renderer.webgl.Layer');
-goog.require('ol.transform');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.webgl.Layer}
- * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
- * @param {ol.layer.Vector} vectorLayer Vector layer.
- */
-ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
-
-  ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.dirty_ = false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedResolution_ = NaN;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.renderedExtent_ = ol.extent.createEmpty();
-
-  /**
-   * @private
-   * @type {function(ol.Feature, ol.Feature): number|null}
-   */
-  this.renderedRenderOrder_ = null;
-
-  /**
-   * @private
-   * @type {ol.render.webgl.ReplayGroup}
-   */
-  this.replayGroup_ = null;
-
-  /**
-   * The last layer state.
-   * @private
-   * @type {?ol.LayerState}
-   */
-  this.layerState_ = null;
-
-};
-ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) {
-  this.layerState_ = layerState;
-  var viewState = frameState.viewState;
-  var replayGroup = this.replayGroup_;
-  if (replayGroup && !replayGroup.isEmpty()) {
-    replayGroup.replay(context,
-        viewState.center, viewState.resolution, viewState.rotation,
-        frameState.size, frameState.pixelRatio, layerState.opacity,
-        layerState.managed ? frameState.skippedFeatureUids : {});
-  }
-
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
-  var replayGroup = this.replayGroup_;
-  if (replayGroup) {
-    var context = this.mapRenderer.getContext();
-    replayGroup.getDeleteResourcesFunction(context)();
-    this.replayGroup_ = null;
-  }
-  ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
-  if (!this.replayGroup_ || !this.layerState_) {
-    return undefined;
-  } else {
-    var context = this.mapRenderer.getContext();
-    var viewState = frameState.viewState;
-    var layer = this.getLayer();
-    var layerState = this.layerState_;
-    /** @type {Object.<string, boolean>} */
-    var features = {};
-    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate,
-        context, viewState.center, viewState.resolution, viewState.rotation,
-        frameState.size, frameState.pixelRatio, layerState.opacity,
-        {},
-        /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @return {?} Callback result.
-         */
-        function(feature) {
-          var key = ol.getUid(feature).toString();
-          if (!(key in features)) {
-            features[key] = true;
-            return callback.call(thisArg, feature, layer);
-          }
-        });
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
-  if (!this.replayGroup_ || !this.layerState_) {
-    return false;
-  } else {
-    var context = this.mapRenderer.getContext();
-    var viewState = frameState.viewState;
-    var layerState = this.layerState_;
-    return this.replayGroup_.hasFeatureAtCoordinate(coordinate,
-        context, viewState.center, viewState.resolution, viewState.rotation,
-        frameState.size, frameState.pixelRatio, layerState.opacity,
-        frameState.skippedFeatureUids);
-  }
-};
-
-
-/**
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.FrameState} frameState FrameState.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer
- *     callback.
- * @param {S} thisArg Value to use as `this` when executing `callback`.
- * @return {T|undefined} Callback result.
- * @template S,T,U
- */
-ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
-  var coordinate = ol.transform.apply(
-      frameState.pixelToCoordinateTransform, pixel.slice());
-  var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState);
-
-  if (hasFeature) {
-    return callback.call(thisArg, this.getLayer(), null);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * Handle changes in image style state.
- * @param {ol.events.Event} event Image style change event.
- * @private
- */
-ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
-  this.renderIfReadyAndVisible();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) {
-
-  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
-  var vectorSource = vectorLayer.getSource();
-
-  this.updateAttributions(
-      frameState.attributions, vectorSource.getAttributions());
-  this.updateLogos(frameState, vectorSource);
-
-  var animating = frameState.viewHints[ol.View.Hint.ANIMATING];
-  var interacting = frameState.viewHints[ol.View.Hint.INTERACTING];
-  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
-  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();
-
-  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
-      (!updateWhileInteracting && interacting)) {
-    return true;
-  }
-
-  var frameStateExtent = frameState.extent;
-  var viewState = frameState.viewState;
-  var projection = viewState.projection;
-  var resolution = viewState.resolution;
-  var pixelRatio = frameState.pixelRatio;
-  var vectorLayerRevision = vectorLayer.getRevision();
-  var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer();
-  var vectorLayerRenderOrder = vectorLayer.getRenderOrder();
-
-  if (vectorLayerRenderOrder === undefined) {
-    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
-  }
-
-  var extent = ol.extent.buffer(frameStateExtent,
-      vectorLayerRenderBuffer * resolution);
-
-  if (!this.dirty_ &&
-      this.renderedResolution_ == resolution &&
-      this.renderedRevision_ == vectorLayerRevision &&
-      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
-      ol.extent.containsExtent(this.renderedExtent_, extent)) {
-    return true;
-  }
-
-  if (this.replayGroup_) {
-    frameState.postRenderFunctions.push(
-        this.replayGroup_.getDeleteResourcesFunction(context));
-  }
-
-  this.dirty_ = false;
-
-  var replayGroup = new ol.render.webgl.ReplayGroup(
-      ol.renderer.vector.getTolerance(resolution, pixelRatio),
-      extent, vectorLayer.getRenderBuffer());
-  vectorSource.loadFeatures(extent, resolution, projection);
-  /**
-   * @param {ol.Feature} feature Feature.
-   * @this {ol.renderer.webgl.VectorLayer}
-   */
-  var renderFeature = function(feature) {
-    var styles;
-    var styleFunction = feature.getStyleFunction();
-    if (styleFunction) {
-      styles = styleFunction.call(feature, resolution);
-    } else {
-      styleFunction = vectorLayer.getStyleFunction();
-      if (styleFunction) {
-        styles = styleFunction(feature, resolution);
-      }
-    }
-    if (styles) {
-      var dirty = this.renderFeature(
-          feature, resolution, pixelRatio, styles, replayGroup);
-      this.dirty_ = this.dirty_ || dirty;
-    }
-  };
-  if (vectorLayerRenderOrder) {
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    vectorSource.forEachFeatureInExtent(extent,
-        /**
-         * @param {ol.Feature} feature Feature.
-         */
-        function(feature) {
-          features.push(feature);
-        }, this);
-    features.sort(vectorLayerRenderOrder);
-    features.forEach(renderFeature, this);
-  } else {
-    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
-  }
-  replayGroup.finish(context);
-
-  this.renderedResolution_ = resolution;
-  this.renderedRevision_ = vectorLayerRevision;
-  this.renderedRenderOrder_ = vectorLayerRenderOrder;
-  this.renderedExtent_ = extent;
-  this.replayGroup_ = replayGroup;
-
-  return true;
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @param {number} resolution Resolution.
- * @param {number} pixelRatio Pixel ratio.
- * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
- *     styles.
- * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group.
- * @return {boolean} `true` if an image is loading.
- */
-ol.renderer.webgl.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
-  if (!styles) {
-    return false;
-  }
-  var loading = false;
-  if (Array.isArray(styles)) {
-    for (var i = 0, ii = styles.length; i < ii; ++i) {
-      loading = ol.renderer.vector.renderFeature(
-          replayGroup, feature, styles[i],
-          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-          this.handleStyleImageChange_, this) || loading;
-    }
-  } else {
-    loading = ol.renderer.vector.renderFeature(
-        replayGroup, feature, styles,
-        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
-        this.handleStyleImageChange_, this) || loading;
-  }
-  return loading;
-};
-
-goog.provide('ol.structs.LRUCache');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.obj');
-
-
-/**
- * Implements a Least-Recently-Used cache where the keys do not conflict with
- * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring
- * items from the cache is the responsibility of the user.
- * @constructor
- * @struct
- * @template T
- */
-ol.structs.LRUCache = function() {
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.count_ = 0;
-
-  /**
-   * @private
-   * @type {!Object.<string, ol.LRUCacheEntry>}
-   */
-  this.entries_ = {};
-
-  /**
-   * @private
-   * @type {?ol.LRUCacheEntry}
-   */
-  this.oldest_ = null;
-
-  /**
-   * @private
-   * @type {?ol.LRUCacheEntry}
-   */
-  this.newest_ = null;
-
-};
-
-
-if (ol.DEBUG) {
-  /**
-   * FIXME empty description for jsdoc
-   */
-  ol.structs.LRUCache.prototype.assertValid = function() {
-    if (this.count_ === 0) {
-      console.assert(ol.obj.isEmpty(this.entries_),
-          'entries must be an empty object (count = 0)');
-      console.assert(!this.oldest_,
-          'oldest must be null (count = 0)');
-      console.assert(!this.newest_,
-          'newest must be null (count = 0)');
-    } else {
-      console.assert(Object.keys(this.entries_).length == this.count_,
-          'number of entries matches count');
-      console.assert(this.oldest_,
-          'we have an oldest entry');
-      console.assert(!this.oldest_.older,
-          'no entry is older than oldest');
-      console.assert(this.newest_,
-          'we have a newest entry');
-      console.assert(!this.newest_.newer,
-          'no entry is newer than newest');
-      var i, entry;
-      var older = null;
-      i = 0;
-      for (entry = this.oldest_; entry; entry = entry.newer) {
-        console.assert(entry.older === older,
-            'entry.older links to correct older');
-        older = entry;
-        ++i;
-      }
-      console.assert(i == this.count_, 'iterated correct amount of times');
-      var newer = null;
-      i = 0;
-      for (entry = this.newest_; entry; entry = entry.older) {
-        console.assert(entry.newer === newer,
-            'entry.newer links to correct newer');
-        newer = entry;
-        ++i;
-      }
-      console.assert(i == this.count_, 'iterated correct amount of times');
-    }
-  };
-}
-
-
-/**
- * FIXME empty description for jsdoc
- */
-ol.structs.LRUCache.prototype.clear = function() {
-  this.count_ = 0;
-  this.entries_ = {};
-  this.oldest_ = null;
-  this.newest_ = null;
-};
-
-
-/**
- * @param {string} key Key.
- * @return {boolean} Contains key.
- */
-ol.structs.LRUCache.prototype.containsKey = function(key) {
-  return this.entries_.hasOwnProperty(key);
-};
-
-
-/**
- * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function
- *     to call for every entry from the oldest to the newer. This function takes
- *     3 arguments (the entry value, the entry key and the LRUCache object).
- *     The return value is ignored.
- * @param {S=} opt_this The object to use as `this` in `f`.
- * @template S
- */
-ol.structs.LRUCache.prototype.forEach = function(f, opt_this) {
-  var entry = this.oldest_;
-  while (entry) {
-    f.call(opt_this, entry.value_, entry.key_, this);
-    entry = entry.newer;
-  }
-};
-
-
-/**
- * @param {string} key Key.
- * @return {T} Value.
- */
-ol.structs.LRUCache.prototype.get = function(key) {
-  var entry = this.entries_[key];
-  ol.asserts.assert(entry !== undefined,
-      15); // Tried to get a value for a key that does not exist in the cache
-  if (entry === this.newest_) {
-    return entry.value_;
-  } else if (entry === this.oldest_) {
-    this.oldest_ = /** @type {ol.LRUCacheEntry} */ (this.oldest_.newer);
-    this.oldest_.older = null;
-  } else {
-    entry.newer.older = entry.older;
-    entry.older.newer = entry.newer;
-  }
-  entry.newer = null;
-  entry.older = this.newest_;
-  this.newest_.newer = entry;
-  this.newest_ = entry;
-  return entry.value_;
-};
-
-
-/**
- * @return {number} Count.
- */
-ol.structs.LRUCache.prototype.getCount = function() {
-  return this.count_;
-};
-
-
-/**
- * @return {Array.<string>} Keys.
- */
-ol.structs.LRUCache.prototype.getKeys = function() {
-  var keys = new Array(this.count_);
-  var i = 0;
-  var entry;
-  for (entry = this.newest_; entry; entry = entry.older) {
-    keys[i++] = entry.key_;
-  }
-  ol.DEBUG && console.assert(i == this.count_, 'iterated correct number of times');
-  return keys;
-};
-
-
-/**
- * @return {Array.<T>} Values.
- */
-ol.structs.LRUCache.prototype.getValues = function() {
-  var values = new Array(this.count_);
-  var i = 0;
-  var entry;
-  for (entry = this.newest_; entry; entry = entry.older) {
-    values[i++] = entry.value_;
-  }
-  ol.DEBUG && console.assert(i == this.count_, 'iterated correct number of times');
-  return values;
-};
-
-
-/**
- * @return {T} Last value.
- */
-ol.structs.LRUCache.prototype.peekLast = function() {
-  ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null');
-  return this.oldest_.value_;
-};
-
-
-/**
- * @return {string} Last key.
- */
-ol.structs.LRUCache.prototype.peekLastKey = function() {
-  ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null');
-  return this.oldest_.key_;
-};
-
-
-/**
- * @return {T} value Value.
- */
-ol.structs.LRUCache.prototype.pop = function() {
-  ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null');
-  ol.DEBUG && console.assert(this.newest_, 'newest must not be null');
-  var entry = this.oldest_;
-  ol.DEBUG && console.assert(entry.key_ in this.entries_,
-      'oldest is indexed in entries');
-  delete this.entries_[entry.key_];
-  if (entry.newer) {
-    entry.newer.older = null;
-  }
-  this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
-  if (!this.oldest_) {
-    this.newest_ = null;
-  }
-  --this.count_;
-  return entry.value_;
-};
-
-
-/**
- * @param {string} key Key.
- * @param {T} value Value.
- */
-ol.structs.LRUCache.prototype.replace = function(key, value) {
-  this.get(key);  // update `newest_`
-  this.entries_[key].value_ = value;
-};
-
-
-/**
- * @param {string} key Key.
- * @param {T} value Value.
- */
-ol.structs.LRUCache.prototype.set = function(key, value) {
-  ol.DEBUG && console.assert(!(key in {}),
-      'key is not a standard property of objects (e.g. "__proto__")');
-  ol.asserts.assert(!(key in this.entries_),
-      16); // Tried to set a value for a key that is used already
-  var entry = /** @type {ol.LRUCacheEntry} */ ({
-    key_: key,
-    newer: null,
-    older: this.newest_,
-    value_: value
-  });
-  if (!this.newest_) {
-    this.oldest_ = entry;
-  } else {
-    this.newest_.newer = entry;
-  }
-  this.newest_ = entry;
-  this.entries_[key] = entry;
-  ++this.count_;
-};
-
-// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)
-
-goog.provide('ol.renderer.webgl.Map');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.css');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Layer');
-goog.require('ol.layer.Tile');
-goog.require('ol.layer.Vector');
-goog.require('ol.render.Event');
-goog.require('ol.render.webgl.Immediate');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.Type');
-goog.require('ol.renderer.webgl.ImageLayer');
-goog.require('ol.renderer.webgl.TileLayer');
-goog.require('ol.renderer.webgl.VectorLayer');
-goog.require('ol.source.State');
-goog.require('ol.structs.LRUCache');
-goog.require('ol.structs.PriorityQueue');
-goog.require('ol.webgl');
-goog.require('ol.webgl.Context');
-goog.require('ol.webgl.ContextEventType');
-
-
-/**
- * @constructor
- * @extends {ol.renderer.Map}
- * @param {Element} container Container.
- * @param {ol.Map} map Map.
- */
-ol.renderer.webgl.Map = function(container, map) {
-
-  ol.renderer.Map.call(this, container, map);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = /** @type {HTMLCanvasElement} */
-      (document.createElement('CANVAS'));
-  this.canvas_.style.width = '100%';
-  this.canvas_.style.height = '100%';
-  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
-  container.insertBefore(this.canvas_, container.childNodes[0] || null);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.clipTileCanvasWidth_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.clipTileCanvasHeight_ = 0;
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.clipTileContext_ = ol.dom.createCanvasContext2D();
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = true;
-
-  /**
-   * @private
-   * @type {WebGLRenderingContext}
-   */
-  this.gl_ = ol.webgl.getContext(this.canvas_, {
-    antialias: true,
-    depth: false,
-    failIfMajorPerformanceCaveat: true,
-    preserveDrawingBuffer: false,
-    stencil: true
-  });
-  ol.DEBUG && console.assert(this.gl_, 'got a WebGLRenderingContext');
-
-  /**
-   * @private
-   * @type {ol.webgl.Context}
-   */
-  this.context_ = new ol.webgl.Context(this.canvas_, this.gl_);
-
-  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
-      this.handleWebGLContextLost, this);
-  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
-      this.handleWebGLContextRestored, this);
-
-  /**
-   * @private
-   * @type {ol.structs.LRUCache.<ol.WebglTextureCacheEntry|null>}
-   */
-  this.textureCache_ = new ol.structs.LRUCache();
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.focus_ = null;
-
-  /**
-   * @private
-   * @type {ol.structs.PriorityQueue.<Array>}
-   */
-  this.tileTextureQueue_ = new ol.structs.PriorityQueue(
-      /**
-       * @param {Array.<*>} element Element.
-       * @return {number} Priority.
-       * @this {ol.renderer.webgl.Map}
-       */
-      (function(element) {
-        var tileCenter = /** @type {ol.Coordinate} */ (element[1]);
-        var tileResolution = /** @type {number} */ (element[2]);
-        var deltaX = tileCenter[0] - this.focus_[0];
-        var deltaY = tileCenter[1] - this.focus_[1];
-        return 65536 * Math.log(tileResolution) +
-            Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
-      }).bind(this),
-      /**
-       * @param {Array.<*>} element Element.
-       * @return {string} Key.
-       */
-      function(element) {
-        return /** @type {ol.Tile} */ (element[0]).getKey();
-      });
-
-
- /**
-  * @param {ol.Map} map Map.
-  * @param {?olx.FrameState} frameState Frame state.
-  * @return {boolean} false.
-  * @this {ol.renderer.webgl.Map}
-  */
-  this.loadNextTileTexture_ =
-      function(map, frameState) {
-        if (!this.tileTextureQueue_.isEmpty()) {
-          this.tileTextureQueue_.reprioritize();
-          var element = this.tileTextureQueue_.dequeue();
-          var tile = /** @type {ol.Tile} */ (element[0]);
-          var tileSize = /** @type {ol.Size} */ (element[3]);
-          var tileGutter = /** @type {number} */ (element[4]);
-          this.bindTileTexture(
-              tile, tileSize, tileGutter, ol.webgl.LINEAR, ol.webgl.LINEAR);
-        }
-        return false;
-      }.bind(this);
-
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.textureCacheFrameMarkerCount_ = 0;
-
-  this.initializeGL_();
-
-};
-ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
-
-
-/**
- * @param {ol.Tile} tile Tile.
- * @param {ol.Size} tileSize Tile size.
- * @param {number} tileGutter Tile gutter.
- * @param {number} magFilter Mag filter.
- * @param {number} minFilter Min filter.
- */
-ol.renderer.webgl.Map.prototype.bindTileTexture = function(tile, tileSize, tileGutter, magFilter, minFilter) {
-  var gl = this.getGL();
-  var tileKey = tile.getKey();
-  if (this.textureCache_.containsKey(tileKey)) {
-    var textureCacheEntry = this.textureCache_.get(tileKey);
-    gl.bindTexture(ol.webgl.TEXTURE_2D, textureCacheEntry.texture);
-    if (textureCacheEntry.magFilter != magFilter) {
-      gl.texParameteri(
-          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter);
-      textureCacheEntry.magFilter = magFilter;
-    }
-    if (textureCacheEntry.minFilter != minFilter) {
-      gl.texParameteri(
-          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter);
-      textureCacheEntry.minFilter = minFilter;
-    }
-  } else {
-    var texture = gl.createTexture();
-    gl.bindTexture(ol.webgl.TEXTURE_2D, texture);
-    if (tileGutter > 0) {
-      var clipTileCanvas = this.clipTileContext_.canvas;
-      var clipTileContext = this.clipTileContext_;
-      if (this.clipTileCanvasWidth_ !== tileSize[0] ||
-          this.clipTileCanvasHeight_ !== tileSize[1]) {
-        clipTileCanvas.width = tileSize[0];
-        clipTileCanvas.height = tileSize[1];
-        this.clipTileCanvasWidth_ = tileSize[0];
-        this.clipTileCanvasHeight_ = tileSize[1];
-      } else {
-        clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]);
-      }
-      clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter,
-          tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]);
-      gl.texImage2D(ol.webgl.TEXTURE_2D, 0,
-          ol.webgl.RGBA, ol.webgl.RGBA,
-          ol.webgl.UNSIGNED_BYTE, clipTileCanvas);
-    } else {
-      gl.texImage2D(ol.webgl.TEXTURE_2D, 0,
-          ol.webgl.RGBA, ol.webgl.RGBA,
-          ol.webgl.UNSIGNED_BYTE, tile.getImage());
-    }
-    gl.texParameteri(
-        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter);
-    gl.texParameteri(
-        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter);
-    gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S,
-        ol.webgl.CLAMP_TO_EDGE);
-    gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T,
-        ol.webgl.CLAMP_TO_EDGE);
-    this.textureCache_.set(tileKey, {
-      texture: texture,
-      magFilter: magFilter,
-      minFilter: minFilter
-    });
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) {
-  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
-    return new ol.renderer.webgl.ImageLayer(this, layer);
-  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
-    return new ol.renderer.webgl.TileLayer(this, layer);
-  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
-    return new ol.renderer.webgl.VectorLayer(this, layer);
-  } else {
-    ol.DEBUG && console.assert(false, 'unexpected layer configuration');
-    return null;
-  }
-};
-
-
-/**
- * @param {ol.render.Event.Type} type Event type.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = function(type, frameState) {
-  var map = this.getMap();
-  if (map.hasListener(type)) {
-    var context = this.context_;
-
-    var extent = frameState.extent;
-    var size = frameState.size;
-    var viewState = frameState.viewState;
-    var pixelRatio = frameState.pixelRatio;
-
-    var resolution = viewState.resolution;
-    var center = viewState.center;
-    var rotation = viewState.rotation;
-
-    var vectorContext = new ol.render.webgl.Immediate(context,
-        center, resolution, rotation, size, extent, pixelRatio);
-    var composeEvent = new ol.render.Event(type, vectorContext,
-        frameState, null, context);
-    map.dispatchEvent(composeEvent);
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.Map.prototype.disposeInternal = function() {
-  var gl = this.getGL();
-  if (!gl.isContextLost()) {
-    this.textureCache_.forEach(
-        /**
-         * @param {?ol.WebglTextureCacheEntry} textureCacheEntry
-         *     Texture cache entry.
-         */
-        function(textureCacheEntry) {
-          if (textureCacheEntry) {
-            gl.deleteTexture(textureCacheEntry.texture);
-          }
-        });
-  }
-  this.context_.dispose();
-  ol.renderer.Map.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * @param {ol.Map} map Map.
- * @param {olx.FrameState} frameState Frame state.
- * @private
- */
-ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) {
-  var gl = this.getGL();
-  var textureCacheEntry;
-  while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
-      ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
-    textureCacheEntry = this.textureCache_.peekLast();
-    if (!textureCacheEntry) {
-      if (+this.textureCache_.peekLastKey() == frameState.index) {
-        break;
-      } else {
-        --this.textureCacheFrameMarkerCount_;
-      }
-    } else {
-      gl.deleteTexture(textureCacheEntry.texture);
-    }
-    this.textureCache_.pop();
-  }
-};
-
-
-/**
- * @return {ol.webgl.Context} The context.
- */
-ol.renderer.webgl.Map.prototype.getContext = function() {
-  return this.context_;
-};
-
-
-/**
- * @return {WebGLRenderingContext} GL.
- */
-ol.renderer.webgl.Map.prototype.getGL = function() {
-  return this.gl_;
-};
-
-
-/**
- * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
- */
-ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
-  return this.tileTextureQueue_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.Map.prototype.getType = function() {
-  return ol.renderer.Type.WEBGL;
-};
-
-
-/**
- * @param {ol.events.Event} event Event.
- * @protected
- */
-ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) {
-  event.preventDefault();
-  this.textureCache_.clear();
-  this.textureCacheFrameMarkerCount_ = 0;
-
-  var renderers = this.getLayerRenderers();
-  for (var id in renderers) {
-    var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]);
-    renderer.handleWebGLContextLost();
-  }
-};
-
-
-/**
- * @protected
- */
-ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
-  this.initializeGL_();
-  this.getMap().render();
-};
-
-
-/**
- * @private
- */
-ol.renderer.webgl.Map.prototype.initializeGL_ = function() {
-  var gl = this.gl_;
-  gl.activeTexture(ol.webgl.TEXTURE0);
-  gl.blendFuncSeparate(
-      ol.webgl.SRC_ALPHA, ol.webgl.ONE_MINUS_SRC_ALPHA,
-      ol.webgl.ONE, ol.webgl.ONE_MINUS_SRC_ALPHA);
-  gl.disable(ol.webgl.CULL_FACE);
-  gl.disable(ol.webgl.DEPTH_TEST);
-  gl.disable(ol.webgl.SCISSOR_TEST);
-  gl.disable(ol.webgl.STENCIL_TEST);
-};
-
-
-/**
- * @param {ol.Tile} tile Tile.
- * @return {boolean} Is tile texture loaded.
- */
-ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) {
-  return this.textureCache_.containsKey(tile.getKey());
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
-
-  var context = this.getContext();
-  var gl = this.getGL();
-
-  if (gl.isContextLost()) {
-    return false;
-  }
-
-  if (!frameState) {
-    if (this.renderedVisible_) {
-      this.canvas_.style.display = 'none';
-      this.renderedVisible_ = false;
-    }
-    return false;
-  }
-
-  this.focus_ = frameState.focus;
-
-  this.textureCache_.set((-frameState.index).toString(), null);
-  ++this.textureCacheFrameMarkerCount_;
-
-  this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, frameState);
-
-  /** @type {Array.<ol.LayerState>} */
-  var layerStatesToDraw = [];
-  var layerStatesArray = frameState.layerStatesArray;
-  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);
-
-  var viewResolution = frameState.viewState.resolution;
-  var i, ii, layerRenderer, layerState;
-  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-    layerState = layerStatesArray[i];
-    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
-        layerState.sourceState == ol.source.State.READY) {
-      layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer));
-      if (layerRenderer.prepareFrame(frameState, layerState, context)) {
-        layerStatesToDraw.push(layerState);
-      }
-    }
-  }
-
-  var width = frameState.size[0] * frameState.pixelRatio;
-  var height = frameState.size[1] * frameState.pixelRatio;
-  if (this.canvas_.width != width || this.canvas_.height != height) {
-    this.canvas_.width = width;
-    this.canvas_.height = height;
-  }
-
-  gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null);
-
-  gl.clearColor(0, 0, 0, 0);
-  gl.clear(ol.webgl.COLOR_BUFFER_BIT);
-  gl.enable(ol.webgl.BLEND);
-  gl.viewport(0, 0, this.canvas_.width, this.canvas_.height);
-
-  for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) {
-    layerState = layerStatesToDraw[i];
-    layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer));
-    layerRenderer.composeFrame(frameState, layerState, context);
-  }
-
-  if (!this.renderedVisible_) {
-    this.canvas_.style.display = '';
-    this.renderedVisible_ = true;
-  }
-
-  this.calculateMatrices2D(frameState);
-
-  if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
-      ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
-    frameState.postRenderFunctions.push(
-      /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this))
-    );
-  }
-
-  if (!this.tileTextureQueue_.isEmpty()) {
-    frameState.postRenderFunctions.push(this.loadNextTileTexture_);
-    frameState.animate = true;
-  }
-
-  this.dispatchComposeEvent_(ol.render.Event.Type.POSTCOMPOSE, frameState);
-
-  this.scheduleRemoveUnusedLayerRenderers(frameState);
-  this.scheduleExpireIconCache(frameState);
-
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg,
-        layerFilter, thisArg2) {
-  var result;
-
-  if (this.getGL().isContextLost()) {
-    return false;
-  }
-
-  var viewState = frameState.viewState;
-
-  var layerStates = frameState.layerStatesArray;
-  var numLayers = layerStates.length;
-  var i;
-  for (i = numLayers - 1; i >= 0; --i) {
-    var layerState = layerStates[i];
-    var layer = layerState.layer;
-    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
-        layerFilter.call(thisArg2, layer)) {
-      var layerRenderer = this.getLayerRenderer(layer);
-      result = layerRenderer.forEachFeatureAtCoordinate(
-          coordinate, frameState, callback, thisArg);
-      if (result) {
-        return result;
-      }
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) {
-  var hasFeature = false;
-
-  if (this.getGL().isContextLost()) {
-    return false;
-  }
-
-  var viewState = frameState.viewState;
-
-  var layerStates = frameState.layerStatesArray;
-  var numLayers = layerStates.length;
-  var i;
-  for (i = numLayers - 1; i >= 0; --i) {
-    var layerState = layerStates[i];
-    var layer = layerState.layer;
-    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
-        layerFilter.call(thisArg, layer)) {
-      var layerRenderer = this.getLayerRenderer(layer);
-      hasFeature =
-          layerRenderer.hasFeatureAtCoordinate(coordinate, frameState);
-      if (hasFeature) {
-        return true;
-      }
-    }
-  }
-  return hasFeature;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
-        layerFilter, thisArg2) {
-  if (this.getGL().isContextLost()) {
-    return false;
-  }
-
-  var viewState = frameState.viewState;
-  var result;
-
-  var layerStates = frameState.layerStatesArray;
-  var numLayers = layerStates.length;
-  var i;
-  for (i = numLayers - 1; i >= 0; --i) {
-    var layerState = layerStates[i];
-    var layer = layerState.layer;
-    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
-        layerFilter.call(thisArg, layer)) {
-      var layerRenderer = this.getLayerRenderer(layer);
-      result = layerRenderer.forEachLayerAtPixel(
-          pixel, frameState, callback, thisArg);
-      if (result) {
-        return result;
-      }
-    }
-  }
-  return undefined;
-};
-
-// FIXME recheck layer/map projection compatibility when projection changes
-// FIXME layer renderers should skip when they can't reproject
-// FIXME add tilt and height?
-
-goog.provide('ol.Map');
-
-goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserEventHandler');
-goog.require('ol.MapEvent');
-goog.require('ol.Object');
-goog.require('ol.ObjectEventType');
-goog.require('ol.TileQueue');
-goog.require('ol.View');
-goog.require('ol.array');
-goog.require('ol.asserts');
-goog.require('ol.control');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.functions');
-goog.require('ol.has');
-goog.require('ol.interaction');
-goog.require('ol.layer.Group');
-goog.require('ol.obj');
-goog.require('ol.proj.common');
-goog.require('ol.renderer.Type');
-goog.require('ol.renderer.Map');
-goog.require('ol.renderer.canvas.Map');
-goog.require('ol.renderer.webgl.Map');
-goog.require('ol.size');
-goog.require('ol.structs.PriorityQueue');
-goog.require('ol.transform');
-
-
-/**
- * @const
- * @type {string}
- */
-ol.OL3_URL = 'https://openlayers.org/';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.OL3_LOGO_URL = 'data:image/png;base64,' +
-    'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' +
-    'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' +
-    'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' +
-    'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' +
-    'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' +
-    'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' +
-    'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' +
-    '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' +
-    'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' +
-    'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' +
-    'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' +
-    'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' +
-    'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' +
-    'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' +
-    'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' +
-    'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' +
-    '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' +
-    'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' +
-    'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' +
-    'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' +
-    'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC';
-
-
-/**
- * @type {Array.<ol.renderer.Type>}
- * @const
- */
-ol.DEFAULT_RENDERER_TYPES = [
-  ol.renderer.Type.CANVAS,
-  ol.renderer.Type.WEBGL
-];
-
-
-/**
- * @classdesc
- * The map is the core component of OpenLayers. For a map to render, a view,
- * one or more layers, and a target container are needed:
- *
- *     var map = new ol.Map({
- *       view: new ol.View({
- *         center: [0, 0],
- *         zoom: 1
- *       }),
- *       layers: [
- *         new ol.layer.Tile({
- *           source: new ol.source.OSM()
- *         })
- *       ],
- *       target: 'map'
- *     });
- *
- * The above snippet creates a map using a {@link ol.layer.Tile} to display
- * {@link ol.source.OSM} OSM data and render it to a DOM element with the
- * id `map`.
- *
- * The constructor places a viewport container (with CSS class name
- * `ol-viewport`) in the target element (see `getViewport()`), and then two
- * further elements within the viewport: one with CSS class name
- * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with
- * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent`
- * option of {@link ol.Overlay} for the difference). The map itself is placed in
- * a further element within the viewport.
- *
- * Layers are stored as a `ol.Collection` in layerGroups. A top-level group is
- * provided by the library. This is what is accessed by `getLayerGroup` and
- * `setLayerGroup`. Layers entered in the options are added to this group, and
- * `addLayer` and `removeLayer` change the layer collection in the group.
- * `getLayers` is a convenience function for `getLayerGroup().getLayers()`.
- * Note that `ol.layer.Group` is a subclass of `ol.layer.Base`, so layers
- * entered in the options or added with `addLayer` can be groups, which can
- * contain further groups, and so on.
- *
- * @constructor
- * @extends {ol.Object}
- * @param {olx.MapOptions} options Map options.
- * @fires ol.MapBrowserEvent
- * @fires ol.MapEvent
- * @fires ol.render.Event#postcompose
- * @fires ol.render.Event#precompose
- * @api stable
- */
-ol.Map = function(options) {
-
-  ol.Object.call(this);
-
-  var optionsInternal = ol.Map.createOptionsInternal(options);
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.loadTilesWhileAnimating_ =
-      options.loadTilesWhileAnimating !== undefined ?
-          options.loadTilesWhileAnimating : false;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.loadTilesWhileInteracting_ =
-      options.loadTilesWhileInteracting !== undefined ?
-          options.loadTilesWhileInteracting : false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = options.pixelRatio !== undefined ?
-      options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;
-
-  /**
-   * @private
-   * @type {Object.<string, string>}
-   */
-  this.logos_ = optionsInternal.logos;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.animationDelayKey_;
-
-  /**
-   * @private
-   */
-  this.animationDelay_ = function() {
-    this.animationDelayKey_ = undefined;
-    this.renderFrame_.call(this, Date.now());
-  }.bind(this);
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.coordinateToPixelTransform_ = ol.transform.create();
-
-  /**
-   * @private
-   * @type {ol.Transform}
-   */
-  this.pixelToCoordinateTransform_ = ol.transform.create();
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.frameIndex_ = 0;
-
-  /**
-   * @private
-   * @type {?olx.FrameState}
-   */
-  this.frameState_ = null;
-
-  /**
-   * The extent at the previous 'moveend' event.
-   * @private
-   * @type {ol.Extent}
-   */
-  this.previousExtent_ = ol.extent.createEmpty();
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.viewPropertyListenerKey_ = null;
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.layerGroupPropertyListenerKeys_ = null;
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.viewport_ = document.createElement('DIV');
-  this.viewport_.className = 'ol-viewport' + (ol.has.TOUCH ? ' ol-touch' : '');
-  this.viewport_.style.position = 'relative';
-  this.viewport_.style.overflow = 'hidden';
-  this.viewport_.style.width = '100%';
-  this.viewport_.style.height = '100%';
-  // prevent page zoom on IE >= 10 browsers
-  this.viewport_.style.msTouchAction = 'none';
-  this.viewport_.style.touchAction = 'none';
-
-  /**
-   * @private
-   * @type {!Element}
-   */
-  this.overlayContainer_ = document.createElement('DIV');
-  this.overlayContainer_.className = 'ol-overlaycontainer';
-  this.viewport_.appendChild(this.overlayContainer_);
-
-  /**
-   * @private
-   * @type {!Element}
-   */
-  this.overlayContainerStopEvent_ = document.createElement('DIV');
-  this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
-  var overlayEvents = [
-    ol.events.EventType.CLICK,
-    ol.events.EventType.DBLCLICK,
-    ol.events.EventType.MOUSEDOWN,
-    ol.events.EventType.TOUCHSTART,
-    ol.events.EventType.MSPOINTERDOWN,
-    ol.MapBrowserEvent.EventType.POINTERDOWN,
-    ol.events.EventType.MOUSEWHEEL,
-    ol.events.EventType.WHEEL
-  ];
-  for (var i = 0, ii = overlayEvents.length; i < ii; ++i) {
-    ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i],
-        ol.events.Event.stopPropagation);
-  }
-  this.viewport_.appendChild(this.overlayContainerStopEvent_);
-
-  /**
-   * @private
-   * @type {ol.MapBrowserEventHandler}
-   */
-  this.mapBrowserEventHandler_ = new ol.MapBrowserEventHandler(this);
-  for (var key in ol.MapBrowserEvent.EventType) {
-    ol.events.listen(this.mapBrowserEventHandler_, ol.MapBrowserEvent.EventType[key],
-        this.handleMapBrowserEvent, this);
-  }
-
-  /**
-   * @private
-   * @type {Element|Document}
-   */
-  this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.keyHandlerKeys_ = null;
-
-  ol.events.listen(this.viewport_, ol.events.EventType.WHEEL,
-      this.handleBrowserEvent, this);
-  ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL,
-      this.handleBrowserEvent, this);
-
-  /**
-   * @type {ol.Collection.<ol.control.Control>}
-   * @private
-   */
-  this.controls_ = optionsInternal.controls;
-
-  /**
-   * @type {ol.Collection.<ol.interaction.Interaction>}
-   * @private
-   */
-  this.interactions_ = optionsInternal.interactions;
-
-  /**
-   * @type {ol.Collection.<ol.Overlay>}
-   * @private
-   */
-  this.overlays_ = optionsInternal.overlays;
-
-  /**
-   * A lookup of overlays by id.
-   * @private
-   * @type {Object.<string, ol.Overlay>}
-   */
-  this.overlayIdIndex_ = {};
-
-  /**
-   * @type {ol.renderer.Map}
-   * @private
-   */
-  this.renderer_ = new optionsInternal.rendererConstructor(this.viewport_, this);
-
-  /**
-   * @type {function(Event)|undefined}
-   * @private
-   */
-  this.handleResize_;
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.focus_ = null;
-
-  /**
-   * @private
-   * @type {Array.<ol.PreRenderFunction>}
-   */
-  this.preRenderFunctions_ = [];
-
-  /**
-   * @private
-   * @type {Array.<ol.PostRenderFunction>}
-   */
-  this.postRenderFunctions_ = [];
-
-  /**
-   * @private
-   * @type {ol.TileQueue}
-   */
-  this.tileQueue_ = new ol.TileQueue(
-      this.getTilePriority.bind(this),
-      this.handleTileChange_.bind(this));
-
-  /**
-   * Uids of features to skip at rendering time.
-   * @type {Object.<string, boolean>}
-   * @private
-   */
-  this.skippedFeatureUids_ = {};
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Map.Property.LAYERGROUP),
-      this.handleLayerGroupChanged_, this);
-  ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.VIEW),
-      this.handleViewChanged_, this);
-  ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.SIZE),
-      this.handleSizeChanged_, this);
-  ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.TARGET),
-      this.handleTargetChanged_, this);
-
-  // setProperties will trigger the rendering of the map if the map
-  // is "defined" already.
-  this.setProperties(optionsInternal.values);
-
-  this.controls_.forEach(
-      /**
-       * @param {ol.control.Control} control Control.
-       * @this {ol.Map}
-       */
-      function(control) {
-        control.setMap(this);
-      }, this);
-
-  ol.events.listen(this.controls_, ol.Collection.EventType.ADD,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(this);
-      }, this);
-
-  ol.events.listen(this.controls_, ol.Collection.EventType.REMOVE,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, this);
-
-  this.interactions_.forEach(
-      /**
-       * @param {ol.interaction.Interaction} interaction Interaction.
-       * @this {ol.Map}
-       */
-      function(interaction) {
-        interaction.setMap(this);
-      }, this);
-
-  ol.events.listen(this.interactions_, ol.Collection.EventType.ADD,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(this);
-      }, this);
-
-  ol.events.listen(this.interactions_, ol.Collection.EventType.REMOVE,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        event.element.setMap(null);
-      }, this);
-
-  this.overlays_.forEach(this.addOverlayInternal_, this);
-
-  ol.events.listen(this.overlays_, ol.Collection.EventType.ADD,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element));
-      }, this);
-
-  ol.events.listen(this.overlays_, ol.Collection.EventType.REMOVE,
-      /**
-       * @param {ol.Collection.Event} event Collection event.
-       */
-      function(event) {
-        var overlay = /** @type {ol.Overlay} */ (event.element);
-        var id = overlay.getId();
-        if (id !== undefined) {
-          delete this.overlayIdIndex_[id.toString()];
-        }
-        event.element.setMap(null);
-      }, this);
-
-};
-ol.inherits(ol.Map, ol.Object);
-
-
-/**
- * Add the given control to the map.
- * @param {ol.control.Control} control Control.
- * @api stable
- */
-ol.Map.prototype.addControl = function(control) {
-  this.getControls().push(control);
-};
-
-
-/**
- * Add the given interaction to the map.
- * @param {ol.interaction.Interaction} interaction Interaction to add.
- * @api stable
- */
-ol.Map.prototype.addInteraction = function(interaction) {
-  this.getInteractions().push(interaction);
-};
-
-
-/**
- * Adds the given layer to the top of this map. If you want to add a layer
- * elsewhere in the stack, use `getLayers()` and the methods available on
- * {@link ol.Collection}.
- * @param {ol.layer.Base} layer Layer.
- * @api stable
- */
-ol.Map.prototype.addLayer = function(layer) {
-  var layers = this.getLayerGroup().getLayers();
-  layers.push(layer);
-};
-
-
-/**
- * Add the given overlay to the map.
- * @param {ol.Overlay} overlay Overlay.
- * @api stable
- */
-ol.Map.prototype.addOverlay = function(overlay) {
-  this.getOverlays().push(overlay);
-};
-
-
-/**
- * This deals with map's overlay collection changes.
- * @param {ol.Overlay} overlay Overlay.
- * @private
- */
-ol.Map.prototype.addOverlayInternal_ = function(overlay) {
-  var id = overlay.getId();
-  if (id !== undefined) {
-    this.overlayIdIndex_[id.toString()] = overlay;
-  }
-  overlay.setMap(this);
-};
-
-
-/**
- * Add functions to be called before rendering. This can be used for attaching
- * animations before updating the map's view.  The {@link ol.animation}
- * namespace provides several static methods for creating prerender functions.
- * @param {...ol.PreRenderFunction} var_args Any number of pre-render functions.
- * @api
- */
-ol.Map.prototype.beforeRender = function(var_args) {
-  this.render();
-  Array.prototype.push.apply(this.preRenderFunctions_, arguments);
-};
-
-
-/**
- * @param {ol.PreRenderFunction} preRenderFunction Pre-render function.
- * @return {boolean} Whether the preRenderFunction has been found and removed.
- */
-ol.Map.prototype.removePreRenderFunction = function(preRenderFunction) {
-  return ol.array.remove(this.preRenderFunctions_, preRenderFunction);
-};
-
-
-/**
- *
- * @inheritDoc
- */
-ol.Map.prototype.disposeInternal = function() {
-  this.mapBrowserEventHandler_.dispose();
-  this.renderer_.dispose();
-  ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL,
-      this.handleBrowserEvent, this);
-  ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL,
-      this.handleBrowserEvent, this);
-  if (this.handleResize_ !== undefined) {
-    window.removeEventListener(ol.events.EventType.RESIZE,
-        this.handleResize_, false);
-    this.handleResize_ = undefined;
-  }
-  if (this.animationDelayKey_) {
-    cancelAnimationFrame(this.animationDelayKey_);
-    this.animationDelayKey_ = undefined;
-  }
-  this.setTarget(null);
-  ol.Object.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * Detect features that intersect a pixel on the viewport, and execute a
- * callback with each intersecting feature. Layers included in the detection can
- * be configured through `opt_layerFilter`.
- * @param {ol.Pixel} pixel Pixel.
- * @param {function(this: S, (ol.Feature|ol.render.Feature),
- *     ol.layer.Layer): T} callback Feature callback. The callback will be
- *     called with two arguments. The first argument is one
- *     {@link ol.Feature feature} or
- *     {@link ol.render.Feature render feature} at the pixel, the second is
- *     the {@link ol.layer.Layer layer} of the feature and will be null for
- *     unmanaged layers. To stop detection, callback functions can return a
- *     truthy value.
- * @param {S=} opt_this Value to use as `this` when executing `callback`.
- * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
- *     filter function. The filter function will receive one argument, the
- *     {@link ol.layer.Layer layer-candidate} and it should return a boolean
- *     value. Only layers which are visible and for which this function returns
- *     `true` will be tested for features. By default, all visible layers will
- *     be tested.
- * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result, i.e. the return value of last
- * callback execution, or the first truthy callback return value.
- * @template S,T,U
- * @api stable
- */
-ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
-  if (!this.frameState_) {
-    return;
-  }
-  var coordinate = this.getCoordinateFromPixel(pixel);
-  var thisArg = opt_this !== undefined ? opt_this : null;
-  var layerFilter = opt_layerFilter !== undefined ?
-      opt_layerFilter : ol.functions.TRUE;
-  var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
-  return this.renderer_.forEachFeatureAtCoordinate(
-      coordinate, this.frameState_, callback, thisArg,
-      layerFilter, thisArg2);
-};
-
-
-/**
- * Detect layers that have a color value at a pixel on the viewport, and
- * execute a callback with each matching layer. Layers included in the
- * detection can be configured through `opt_layerFilter`.
- * @param {ol.Pixel} pixel Pixel.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback
- *     Layer callback. This callback will recieve two arguments: first is the
- *     {@link ol.layer.Layer layer}, second argument is an array representing
- *     [R, G, B, A] pixel values (0 - 255) and will be `null` for layer types
- *     that do not currently support this argument. To stop detection, callback
- *     functions can return a truthy value.
- * @param {S=} opt_this Value to use as `this` when executing `callback`.
- * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
- *     filter function. The filter function will receive one argument, the
- *     {@link ol.layer.Layer layer-candidate} and it should return a boolean
- *     value. Only layers which are visible and for which this function returns
- *     `true` will be tested for features. By default, all visible layers will
- *     be tested.
- * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result, i.e. the return value of last
- * callback execution, or the first truthy callback return value.
- * @template S,T,U
- * @api stable
- */
-ol.Map.prototype.forEachLayerAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
-  if (!this.frameState_) {
-    return;
-  }
-  var thisArg = opt_this !== undefined ? opt_this : null;
-  var layerFilter = opt_layerFilter !== undefined ?
-      opt_layerFilter : ol.functions.TRUE;
-  var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
-  return this.renderer_.forEachLayerAtPixel(
-      pixel, this.frameState_, callback, thisArg,
-      layerFilter, thisArg2);
-};
-
-
-/**
- * Detect if features intersect a pixel on the viewport. Layers included in the
- * detection can be configured through `opt_layerFilter`.
- * @param {ol.Pixel} pixel Pixel.
- * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
- *     filter function. The filter function will receive one argument, the
- *     {@link ol.layer.Layer layer-candidate} and it should return a boolean
- *     value. Only layers which are visible and for which this function returns
- *     `true` will be tested for features. By default, all visible layers will
- *     be tested.
- * @param {U=} opt_this Value to use as `this` when executing `layerFilter`.
- * @return {boolean} Is there a feature at the given pixel?
- * @template U
- * @api
- */
-ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_layerFilter, opt_this) {
-  if (!this.frameState_) {
-    return false;
-  }
-  var coordinate = this.getCoordinateFromPixel(pixel);
-  var layerFilter = opt_layerFilter !== undefined ?
-      opt_layerFilter : ol.functions.TRUE;
-  var thisArg = opt_this !== undefined ? opt_this : null;
-  return this.renderer_.hasFeatureAtCoordinate(
-      coordinate, this.frameState_, layerFilter, thisArg);
-};
-
-
-/**
- * Returns the geographical coordinate for a browser event.
- * @param {Event} event Event.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
- */
-ol.Map.prototype.getEventCoordinate = function(event) {
-  return this.getCoordinateFromPixel(this.getEventPixel(event));
-};
-
-
-/**
- * Returns the map pixel position for a browser event relative to the viewport.
- * @param {Event} event Event.
- * @return {ol.Pixel} Pixel.
- * @api stable
- */
-ol.Map.prototype.getEventPixel = function(event) {
-  var viewportPosition = this.viewport_.getBoundingClientRect();
-  var eventPosition = event.changedTouches ? event.changedTouches[0] : event;
-  return [
-    eventPosition.clientX - viewportPosition.left,
-    eventPosition.clientY - viewportPosition.top
-  ];
-};
-
-
-/**
- * Get the target in which this map is rendered.
- * Note that this returns what is entered as an option or in setTarget:
- * if that was an element, it returns an element; if a string, it returns that.
- * @return {Element|string|undefined} The Element or id of the Element that the
- *     map is rendered in.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getTarget = function() {
-  return /** @type {Element|string|undefined} */ (
-      this.get(ol.Map.Property.TARGET));
-};
-
-
-/**
- * Get the DOM element into which this map is rendered. In contrast to
- * `getTarget` this method always return an `Element`, or `null` if the
- * map has no target.
- * @return {Element} The element that the map is rendered in.
- * @api
- */
-ol.Map.prototype.getTargetElement = function() {
-  var target = this.getTarget();
-  if (target !== undefined) {
-    return typeof target === 'string' ?
-      document.getElementById(target) :
-      target;
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * Get the coordinate for a given pixel.  This returns a coordinate in the
- * map view projection.
- * @param {ol.Pixel} pixel Pixel position in the map viewport.
- * @return {ol.Coordinate} The coordinate for the pixel position.
- * @api stable
- */
-ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
-  var frameState = this.frameState_;
-  if (!frameState) {
-    return null;
-  } else {
-    return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice());
-  }
-};
-
-
-/**
- * Get the map controls. Modifying this collection changes the controls
- * associated with the map.
- * @return {ol.Collection.<ol.control.Control>} Controls.
- * @api stable
- */
-ol.Map.prototype.getControls = function() {
-  return this.controls_;
-};
-
-
-/**
- * Get the map overlays. Modifying this collection changes the overlays
- * associated with the map.
- * @return {ol.Collection.<ol.Overlay>} Overlays.
- * @api stable
- */
-ol.Map.prototype.getOverlays = function() {
-  return this.overlays_;
-};
-
-
-/**
- * Get an overlay by its identifier (the value returned by overlay.getId()).
- * Note that the index treats string and numeric identifiers as the same. So
- * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`.
- * @param {string|number} id Overlay identifier.
- * @return {ol.Overlay} Overlay.
- * @api
- */
-ol.Map.prototype.getOverlayById = function(id) {
-  var overlay = this.overlayIdIndex_[id.toString()];
-  return overlay !== undefined ? overlay : null;
-};
-
-
-/**
- * Get the map interactions. Modifying this collection changes the interactions
- * associated with the map.
- *
- * Interactions are used for e.g. pan, zoom and rotate.
- * @return {ol.Collection.<ol.interaction.Interaction>} Interactions.
- * @api stable
- */
-ol.Map.prototype.getInteractions = function() {
-  return this.interactions_;
-};
-
-
-/**
- * Get the layergroup associated with this map.
- * @return {ol.layer.Group} A layer group containing the layers in this map.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getLayerGroup = function() {
-  return /** @type {ol.layer.Group} */ (this.get(ol.Map.Property.LAYERGROUP));
-};
-
-
-/**
- * Get the collection of layers associated with this map.
- * @return {!ol.Collection.<ol.layer.Base>} Layers.
- * @api stable
- */
-ol.Map.prototype.getLayers = function() {
-  var layers = this.getLayerGroup().getLayers();
-  return layers;
-};
-
-
-/**
- * Get the pixel for a coordinate.  This takes a coordinate in the map view
- * projection and returns the corresponding pixel.
- * @param {ol.Coordinate} coordinate A map coordinate.
- * @return {ol.Pixel} A pixel position in the map viewport.
- * @api stable
- */
-ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
-  var frameState = this.frameState_;
-  if (!frameState) {
-    return null;
-  } else {
-    return ol.transform.apply(frameState.coordinateToPixelTransform,
-        coordinate.slice(0, 2));
-  }
-};
-
-
-/**
- * Get the map renderer.
- * @return {ol.renderer.Map} Renderer
- */
-ol.Map.prototype.getRenderer = function() {
-  return this.renderer_;
-};
-
-
-/**
- * Get the size of this map.
- * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getSize = function() {
-  return /** @type {ol.Size|undefined} */ (this.get(ol.Map.Property.SIZE));
-};
-
-
-/**
- * Get the view associated with this map. A view manages properties such as
- * center and resolution.
- * @return {ol.View} The view that controls this map.
- * @observable
- * @api stable
- */
-ol.Map.prototype.getView = function() {
-  return /** @type {ol.View} */ (this.get(ol.Map.Property.VIEW));
-};
-
-
-/**
- * Get the element that serves as the map viewport.
- * @return {Element} Viewport.
- * @api stable
- */
-ol.Map.prototype.getViewport = function() {
-  return this.viewport_;
-};
-
-
-/**
- * Get the element that serves as the container for overlays.  Elements added to
- * this container will let mousedown and touchstart events through to the map,
- * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent}
- * events.
- * @return {!Element} The map's overlay container.
- */
-ol.Map.prototype.getOverlayContainer = function() {
-  return this.overlayContainer_;
-};
-
-
-/**
- * Get the element that serves as a container for overlays that don't allow
- * event propagation. Elements added to this container won't let mousedown and
- * touchstart events through to the map, so clicks and gestures on an overlay
- * don't trigger any {@link ol.MapBrowserEvent}.
- * @return {!Element} The map's overlay container that stops events.
- */
-ol.Map.prototype.getOverlayContainerStopEvent = function() {
-  return this.overlayContainerStopEvent_;
-};
-
-
-/**
- * @param {ol.Tile} tile Tile.
- * @param {string} tileSourceKey Tile source key.
- * @param {ol.Coordinate} tileCenter Tile center.
- * @param {number} tileResolution Tile resolution.
- * @return {number} Tile priority.
- */
-ol.Map.prototype.getTilePriority = function(tile, tileSourceKey, tileCenter, tileResolution) {
-  // Filter out tiles at higher zoom levels than the current zoom level, or that
-  // are outside the visible extent.
-  var frameState = this.frameState_;
-  if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
-    return ol.structs.PriorityQueue.DROP;
-  }
-  if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
-    return ol.structs.PriorityQueue.DROP;
-  }
-  // Prioritize the highest zoom level tiles closest to the focus.
-  // Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
-  // Within a zoom level, tiles are prioritized by the distance in pixels
-  // between the center of the tile and the focus.  The factor of 65536 means
-  // that the prioritization should behave as desired for tiles up to
-  // 65536 * Math.log(2) = 45426 pixels from the focus.
-  var deltaX = tileCenter[0] - frameState.focus[0];
-  var deltaY = tileCenter[1] - frameState.focus[1];
-  return 65536 * Math.log(tileResolution) +
-      Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
-};
-
-
-/**
- * @param {Event} browserEvent Browser event.
- * @param {string=} opt_type Type.
- */
-ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
-  var type = opt_type || browserEvent.type;
-  var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent);
-  this.handleMapBrowserEvent(mapBrowserEvent);
-};
-
-
-/**
- * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
- */
-ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) {
-  if (!this.frameState_) {
-    // With no view defined, we cannot translate pixels into geographical
-    // coordinates so interactions cannot be used.
-    return;
-  }
-  this.focus_ = mapBrowserEvent.coordinate;
-  mapBrowserEvent.frameState = this.frameState_;
-  var interactionsArray = this.getInteractions().getArray();
-  var i;
-  if (this.dispatchEvent(mapBrowserEvent) !== false) {
-    for (i = interactionsArray.length - 1; i >= 0; i--) {
-      var interaction = interactionsArray[i];
-      if (!interaction.getActive()) {
-        continue;
-      }
-      var cont = interaction.handleEvent(mapBrowserEvent);
-      if (!cont) {
-        break;
-      }
-    }
-  }
-};
-
-
-/**
- * @protected
- */
-ol.Map.prototype.handlePostRender = function() {
-
-  var frameState = this.frameState_;
-
-  // Manage the tile queue
-  // Image loads are expensive and a limited resource, so try to use them
-  // efficiently:
-  // * When the view is static we allow a large number of parallel tile loads
-  //   to complete the frame as quickly as possible.
-  // * When animating or interacting, image loads can cause janks, so we reduce
-  //   the maximum number of loads per frame and limit the number of parallel
-  //   tile loads to remain reactive to view changes and to reduce the chance of
-  //   loading tiles that will quickly disappear from view.
-  var tileQueue = this.tileQueue_;
-  if (!tileQueue.isEmpty()) {
-    var maxTotalLoading = 16;
-    var maxNewLoads = maxTotalLoading;
-    if (frameState) {
-      var hints = frameState.viewHints;
-      if (hints[ol.View.Hint.ANIMATING]) {
-        maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0;
-        maxNewLoads = 2;
-      }
-      if (hints[ol.View.Hint.INTERACTING]) {
-        maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0;
-        maxNewLoads = 2;
-      }
-    }
-    if (tileQueue.getTilesLoading() < maxTotalLoading) {
-      tileQueue.reprioritize(); // FIXME only call if view has changed
-      tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
-    }
-  }
-
-  var postRenderFunctions = this.postRenderFunctions_;
-  var i, ii;
-  for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
-    postRenderFunctions[i](this, frameState);
-  }
-  postRenderFunctions.length = 0;
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleSizeChanged_ = function() {
-  this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleTargetChanged_ = function() {
-  // target may be undefined, null, a string or an Element.
-  // If it's a string we convert it to an Element before proceeding.
-  // If it's not now an Element we remove the viewport from the DOM.
-  // If it's an Element we append the viewport element to it.
-
-  var targetElement;
-  if (this.getTarget()) {
-    targetElement = this.getTargetElement();
-    ol.DEBUG && console.assert(targetElement !== null,
-        'expects a non-null value for targetElement');
-  }
-
-  if (this.keyHandlerKeys_) {
-    for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
-      ol.events.unlistenByKey(this.keyHandlerKeys_[i]);
-    }
-    this.keyHandlerKeys_ = null;
-  }
-
-  if (!targetElement) {
-    ol.dom.removeNode(this.viewport_);
-    if (this.handleResize_ !== undefined) {
-      window.removeEventListener(ol.events.EventType.RESIZE,
-          this.handleResize_, false);
-      this.handleResize_ = undefined;
-    }
-  } else {
-    targetElement.appendChild(this.viewport_);
-
-    var keyboardEventTarget = !this.keyboardEventTarget_ ?
-        targetElement : this.keyboardEventTarget_;
-    this.keyHandlerKeys_ = [
-      ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN,
-          this.handleBrowserEvent, this),
-      ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS,
-          this.handleBrowserEvent, this)
-    ];
-
-    if (!this.handleResize_) {
-      this.handleResize_ = this.updateSize.bind(this);
-      window.addEventListener(ol.events.EventType.RESIZE,
-          this.handleResize_, false);
-    }
-  }
-
-  this.updateSize();
-  // updateSize calls setSize, so no need to call this.render
-  // ourselves here.
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleTileChange_ = function() {
-  this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleViewPropertyChanged_ = function() {
-  this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleViewChanged_ = function() {
-  if (this.viewPropertyListenerKey_) {
-    ol.events.unlistenByKey(this.viewPropertyListenerKey_);
-    this.viewPropertyListenerKey_ = null;
-  }
-  var view = this.getView();
-  if (view) {
-    this.viewPropertyListenerKey_ = ol.events.listen(
-        view, ol.ObjectEventType.PROPERTYCHANGE,
-        this.handleViewPropertyChanged_, this);
-  }
-  this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleLayerGroupChanged_ = function() {
-  if (this.layerGroupPropertyListenerKeys_) {
-    this.layerGroupPropertyListenerKeys_.forEach(ol.events.unlistenByKey);
-    this.layerGroupPropertyListenerKeys_ = null;
-  }
-  var layerGroup = this.getLayerGroup();
-  if (layerGroup) {
-    this.layerGroupPropertyListenerKeys_ = [
-      ol.events.listen(
-          layerGroup, ol.ObjectEventType.PROPERTYCHANGE,
-          this.render, this),
-      ol.events.listen(
-          layerGroup, ol.events.EventType.CHANGE,
-          this.render, this)
-    ];
-  }
-  this.render();
-};
-
-
-/**
- * @return {boolean} Is rendered.
- */
-ol.Map.prototype.isRendered = function() {
-  return !!this.frameState_;
-};
-
-
-/**
- * Requests an immediate render in a synchronous manner.
- * @api stable
- */
-ol.Map.prototype.renderSync = function() {
-  if (this.animationDelayKey_) {
-    cancelAnimationFrame(this.animationDelayKey_);
-  }
-  this.animationDelay_();
-};
-
-
-/**
- * Request a map rendering (at the next animation frame).
- * @api stable
- */
-ol.Map.prototype.render = function() {
-  if (this.animationDelayKey_ === undefined) {
-    this.animationDelayKey_ = requestAnimationFrame(
-        this.animationDelay_);
-  }
-};
-
-
-/**
- * Remove the given control from the map.
- * @param {ol.control.Control} control Control.
- * @return {ol.control.Control|undefined} The removed control (or undefined
- *     if the control was not found).
- * @api stable
- */
-ol.Map.prototype.removeControl = function(control) {
-  return this.getControls().remove(control);
-};
-
-
-/**
- * Remove the given interaction from the map.
- * @param {ol.interaction.Interaction} interaction Interaction to remove.
- * @return {ol.interaction.Interaction|undefined} The removed interaction (or
- *     undefined if the interaction was not found).
- * @api stable
- */
-ol.Map.prototype.removeInteraction = function(interaction) {
-  return this.getInteractions().remove(interaction);
-};
-
-
-/**
- * Removes the given layer from the map.
- * @param {ol.layer.Base} layer Layer.
- * @return {ol.layer.Base|undefined} The removed layer (or undefined if the
- *     layer was not found).
- * @api stable
- */
-ol.Map.prototype.removeLayer = function(layer) {
-  var layers = this.getLayerGroup().getLayers();
-  return layers.remove(layer);
-};
-
-
-/**
- * Remove the given overlay from the map.
- * @param {ol.Overlay} overlay Overlay.
- * @return {ol.Overlay|undefined} The removed overlay (or undefined
- *     if the overlay was not found).
- * @api stable
- */
-ol.Map.prototype.removeOverlay = function(overlay) {
-  return this.getOverlays().remove(overlay);
-};
-
-
-/**
- * @param {number} time Time.
- * @private
- */
-ol.Map.prototype.renderFrame_ = function(time) {
-
-  var i, ii, viewState;
-
-  var size = this.getSize();
-  var view = this.getView();
-  var extent = ol.extent.createEmpty();
-  /** @type {?olx.FrameState} */
-  var frameState = null;
-  if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) {
-    var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
-    var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
-    var layerStates = {};
-    for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-      layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
-    }
-    viewState = view.getState();
-    frameState = /** @type {olx.FrameState} */ ({
-      animate: false,
-      attributions: {},
-      coordinateToPixelTransform: this.coordinateToPixelTransform_,
-      extent: extent,
-      focus: !this.focus_ ? viewState.center : this.focus_,
-      index: this.frameIndex_++,
-      layerStates: layerStates,
-      layerStatesArray: layerStatesArray,
-      logos: ol.obj.assign({}, this.logos_),
-      pixelRatio: this.pixelRatio_,
-      pixelToCoordinateTransform: this.pixelToCoordinateTransform_,
-      postRenderFunctions: [],
-      size: size,
-      skippedFeatureUids: this.skippedFeatureUids_,
-      tileQueue: this.tileQueue_,
-      time: time,
-      usedTiles: {},
-      viewState: viewState,
-      viewHints: viewHints,
-      wantedTiles: {}
-    });
-  }
-
-  if (frameState) {
-    var preRenderFunctions = this.preRenderFunctions_;
-    var n = 0, preRenderFunction;
-    for (i = 0, ii = preRenderFunctions.length; i < ii; ++i) {
-      preRenderFunction = preRenderFunctions[i];
-      if (preRenderFunction(this, frameState)) {
-        preRenderFunctions[n++] = preRenderFunction;
-      }
-    }
-    preRenderFunctions.length = n;
-
-    frameState.extent = ol.extent.getForViewAndSize(viewState.center,
-        viewState.resolution, viewState.rotation, frameState.size, extent);
-  }
-
-  this.frameState_ = frameState;
-  this.renderer_.renderFrame(frameState);
-
-  if (frameState) {
-    if (frameState.animate) {
-      this.render();
-    }
-    Array.prototype.push.apply(
-        this.postRenderFunctions_, frameState.postRenderFunctions);
-
-    var idle = this.preRenderFunctions_.length === 0 &&
-        !frameState.viewHints[ol.View.Hint.ANIMATING] &&
-        !frameState.viewHints[ol.View.Hint.INTERACTING] &&
-        !ol.extent.equals(frameState.extent, this.previousExtent_);
-
-    if (idle) {
-      this.dispatchEvent(
-          new ol.MapEvent(ol.MapEvent.Type.MOVEEND, this, frameState));
-      ol.extent.clone(frameState.extent, this.previousExtent_);
-    }
-  }
-
-  this.dispatchEvent(
-      new ol.MapEvent(ol.MapEvent.Type.POSTRENDER, this, frameState));
-
-  setTimeout(this.handlePostRender.bind(this), 0);
-
-};
-
-
-/**
- * Sets the layergroup of this map.
- * @param {ol.layer.Group} layerGroup A layer group containing the layers in
- *     this map.
- * @observable
- * @api stable
- */
-ol.Map.prototype.setLayerGroup = function(layerGroup) {
-  this.set(ol.Map.Property.LAYERGROUP, layerGroup);
-};
-
-
-/**
- * Set the size of this map.
- * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
- * @observable
- * @api
- */
-ol.Map.prototype.setSize = function(size) {
-  this.set(ol.Map.Property.SIZE, size);
-};
-
-
-/**
- * Set the target element to render this map into.
- * @param {Element|string|undefined} target The Element or id of the Element
- *     that the map is rendered in.
- * @observable
- * @api stable
- */
-ol.Map.prototype.setTarget = function(target) {
-  this.set(ol.Map.Property.TARGET, target);
-};
-
-
-/**
- * Set the view for this map.
- * @param {ol.View} view The view that controls this map.
- * @observable
- * @api stable
- */
-ol.Map.prototype.setView = function(view) {
-  this.set(ol.Map.Property.VIEW, view);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- */
-ol.Map.prototype.skipFeature = function(feature) {
-  var featureUid = ol.getUid(feature).toString();
-  this.skippedFeatureUids_[featureUid] = true;
-  this.render();
-};
-
-
-/**
- * Force a recalculation of the map viewport size.  This should be called when
- * third-party code changes the size of the map viewport.
- * @api stable
- */
-ol.Map.prototype.updateSize = function() {
-  var targetElement = this.getTargetElement();
-
-  if (!targetElement) {
-    this.setSize(undefined);
-  } else {
-    var computedStyle = getComputedStyle(targetElement);
-    this.setSize([
-      targetElement.offsetWidth -
-          parseFloat(computedStyle['borderLeftWidth']) -
-          parseFloat(computedStyle['paddingLeft']) -
-          parseFloat(computedStyle['paddingRight']) -
-          parseFloat(computedStyle['borderRightWidth']),
-      targetElement.offsetHeight -
-          parseFloat(computedStyle['borderTopWidth']) -
-          parseFloat(computedStyle['paddingTop']) -
-          parseFloat(computedStyle['paddingBottom']) -
-          parseFloat(computedStyle['borderBottomWidth'])
-    ]);
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- */
-ol.Map.prototype.unskipFeature = function(feature) {
-  var featureUid = ol.getUid(feature).toString();
-  delete this.skippedFeatureUids_[featureUid];
-  this.render();
-};
-
-
-/**
- * @param {olx.MapOptions} options Map options.
- * @return {ol.MapOptionsInternal} Internal map options.
- */
-ol.Map.createOptionsInternal = function(options) {
-
-  /**
-   * @type {Element|Document}
-   */
-  var keyboardEventTarget = null;
-  if (options.keyboardEventTarget !== undefined) {
-    keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ?
-        document.getElementById(options.keyboardEventTarget) :
-        options.keyboardEventTarget;
-  }
-
-  /**
-   * @type {Object.<string, *>}
-   */
-  var values = {};
-
-  var logos = {};
-  if (options.logo === undefined ||
-      (typeof options.logo === 'boolean' && options.logo)) {
-    logos[ol.OL3_LOGO_URL] = ol.OL3_URL;
-  } else {
-    var logo = options.logo;
-    if (typeof logo === 'string') {
-      logos[logo] = '';
-    } else if (logo instanceof HTMLElement) {
-      logos[ol.getUid(logo).toString()] = logo;
-    } else if (logo) {
-      ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
-      ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
-      logos[logo.src] = logo.href;
-    }
-  }
-
-  var layerGroup = (options.layers instanceof ol.layer.Group) ?
-      options.layers : new ol.layer.Group({layers: options.layers});
-  values[ol.Map.Property.LAYERGROUP] = layerGroup;
-
-  values[ol.Map.Property.TARGET] = options.target;
-
-  values[ol.Map.Property.VIEW] = options.view !== undefined ?
-      options.view : new ol.View();
-
-  /**
-   * @type {function(new: ol.renderer.Map, Element, ol.Map)}
-   */
-  var rendererConstructor = ol.renderer.Map;
-
-  /**
-   * @type {Array.<ol.renderer.Type>}
-   */
-  var rendererTypes;
-  if (options.renderer !== undefined) {
-    if (Array.isArray(options.renderer)) {
-      rendererTypes = options.renderer;
-    } else if (typeof options.renderer === 'string') {
-      rendererTypes = [options.renderer];
-    } else {
-      ol.asserts.assert(false, 46); // Incorrect format for `renderer` option
-    }
-    if (rendererTypes.indexOf(/** @type {ol.renderer.Type} */ ('dom')) >= 0) {
-      ol.DEBUG && console.assert(false, 'The DOM render has been removed');
-      rendererTypes = rendererTypes.concat(ol.DEFAULT_RENDERER_TYPES);
-    }
-  } else {
-    rendererTypes = ol.DEFAULT_RENDERER_TYPES;
-  }
-
-  var i, ii;
-  for (i = 0, ii = rendererTypes.length; i < ii; ++i) {
-    /** @type {ol.renderer.Type} */
-    var rendererType = rendererTypes[i];
-    if (ol.ENABLE_CANVAS && rendererType == ol.renderer.Type.CANVAS) {
-      if (ol.has.CANVAS) {
-        rendererConstructor = ol.renderer.canvas.Map;
-        break;
-      }
-    } else if (ol.ENABLE_WEBGL && rendererType == ol.renderer.Type.WEBGL) {
-      if (ol.has.WEBGL) {
-        rendererConstructor = ol.renderer.webgl.Map;
-        break;
-      }
-    }
-  }
-
-  var controls;
-  if (options.controls !== undefined) {
-    if (Array.isArray(options.controls)) {
-      controls = new ol.Collection(options.controls.slice());
-    } else {
-      ol.asserts.assert(options.controls instanceof ol.Collection,
-          47); // Expected `controls` to be an array or an `ol.Collection`
-      controls = options.controls;
-    }
-  } else {
-    controls = ol.control.defaults();
-  }
-
-  var interactions;
-  if (options.interactions !== undefined) {
-    if (Array.isArray(options.interactions)) {
-      interactions = new ol.Collection(options.interactions.slice());
-    } else {
-      ol.asserts.assert(options.interactions instanceof ol.Collection,
-          48); // Expected `interactions` to be an array or an `ol.Collection`
-      interactions = options.interactions;
-    }
-  } else {
-    interactions = ol.interaction.defaults();
-  }
-
-  var overlays;
-  if (options.overlays !== undefined) {
-    if (Array.isArray(options.overlays)) {
-      overlays = new ol.Collection(options.overlays.slice());
-    } else {
-      ol.asserts.assert(options.overlays instanceof ol.Collection,
-          49); // Expected `overlays` to be an array or an `ol.Collection`
-      overlays = options.overlays;
-    }
-  } else {
-    overlays = new ol.Collection();
-  }
-
-  return {
-    controls: controls,
-    interactions: interactions,
-    keyboardEventTarget: keyboardEventTarget,
-    logos: logos,
-    overlays: overlays,
-    rendererConstructor: rendererConstructor,
-    values: values
-  };
-
-};
-
-/**
- * @enum {string}
- */
-ol.Map.Property = {
-  LAYERGROUP: 'layergroup',
-  SIZE: 'size',
-  TARGET: 'target',
-  VIEW: 'view'
-};
-
-
-ol.proj.common.add();
-
-goog.provide('ol.Overlay');
-
-goog.require('ol');
-goog.require('ol.MapEvent');
-goog.require('ol.Object');
-goog.require('ol.animation');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.extent');
-
-
-/**
- * @classdesc
- * An element to be displayed over the map and attached to a single map
- * location.  Like {@link ol.control.Control}, Overlays are visible widgets.
- * Unlike Controls, they are not in a fixed position on the screen, but are tied
- * to a geographical coordinate, so panning the map will move an Overlay but not
- * a Control.
- *
- * Example:
- *
- *     var popup = new ol.Overlay({
- *       element: document.getElementById('popup')
- *     });
- *     popup.setPosition(coordinate);
- *     map.addOverlay(popup);
- *
- * @constructor
- * @extends {ol.Object}
- * @param {olx.OverlayOptions} options Overlay options.
- * @api stable
- */
-ol.Overlay = function(options) {
-
-  ol.Object.call(this);
-
-  /**
-   * @private
-   * @type {number|string|undefined}
-   */
-  this.id_ = options.id;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.insertFirst_ = options.insertFirst !== undefined ?
-      options.insertFirst : true;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true;
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.element_ = document.createElement('DIV');
-  this.element_.className = 'ol-overlay-container';
-  this.element_.style.position = 'absolute';
-
-  /**
-   * @protected
-   * @type {boolean}
-   */
-  this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
-
-  /**
-   * @private
-   * @type {olx.animation.PanOptions}
-   */
-  this.autoPanAnimation_ = options.autoPanAnimation !== undefined ?
-      options.autoPanAnimation : /** @type {olx.animation.PanOptions} */ ({});
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.autoPanMargin_ = options.autoPanMargin !== undefined ?
-      options.autoPanMargin : 20;
-
-  /**
-   * @private
-   * @type {{bottom_: string,
-   *         left_: string,
-   *         right_: string,
-   *         top_: string,
-   *         visible: boolean}}
-   */
-  this.rendered_ = {
-    bottom_: '',
-    left_: '',
-    right_: '',
-    top_: '',
-    visible: true
-  };
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.mapPostrenderListenerKey_ = null;
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Overlay.Property.ELEMENT),
-      this.handleElementChanged, this);
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Overlay.Property.MAP),
-      this.handleMapChanged, this);
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Overlay.Property.OFFSET),
-      this.handleOffsetChanged, this);
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Overlay.Property.POSITION),
-      this.handlePositionChanged, this);
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Overlay.Property.POSITIONING),
-      this.handlePositioningChanged, this);
-
-  if (options.element !== undefined) {
-    this.setElement(options.element);
-  }
-
-  this.setOffset(options.offset !== undefined ? options.offset : [0, 0]);
-
-  this.setPositioning(options.positioning !== undefined ?
-      /** @type {ol.Overlay.Positioning} */ (options.positioning) :
-      ol.Overlay.Positioning.TOP_LEFT);
-
-  if (options.position !== undefined) {
-    this.setPosition(options.position);
-  }
-
-};
-ol.inherits(ol.Overlay, ol.Object);
-
-
-/**
- * Get the DOM element of this overlay.
- * @return {Element|undefined} The Element containing the overlay.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.getElement = function() {
-  return /** @type {Element|undefined} */ (
-      this.get(ol.Overlay.Property.ELEMENT));
-};
-
-
-/**
- * Get the overlay identifier which is set on constructor.
- * @return {number|string|undefined} Id.
- * @api
- */
-ol.Overlay.prototype.getId = function() {
-  return this.id_;
-};
-
-
-/**
- * Get the map associated with this overlay.
- * @return {ol.Map|undefined} The map that the overlay is part of.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.getMap = function() {
-  return /** @type {ol.Map|undefined} */ (
-      this.get(ol.Overlay.Property.MAP));
-};
-
-
-/**
- * Get the offset of this overlay.
- * @return {Array.<number>} The offset.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.getOffset = function() {
-  return /** @type {Array.<number>} */ (
-      this.get(ol.Overlay.Property.OFFSET));
-};
-
-
-/**
- * Get the current position of this overlay.
- * @return {ol.Coordinate|undefined} The spatial point that the overlay is
- *     anchored at.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.getPosition = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.Overlay.Property.POSITION));
-};
-
-
-/**
- * Get the current positioning of this overlay.
- * @return {ol.Overlay.Positioning} How the overlay is positioned
- *     relative to its point on the map.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.getPositioning = function() {
-  return /** @type {ol.Overlay.Positioning} */ (
-      this.get(ol.Overlay.Property.POSITIONING));
-};
-
-
-/**
- * @protected
- */
-ol.Overlay.prototype.handleElementChanged = function() {
-  ol.dom.removeChildren(this.element_);
-  var element = this.getElement();
-  if (element) {
-    this.element_.appendChild(element);
-  }
-};
-
-
-/**
- * @protected
- */
-ol.Overlay.prototype.handleMapChanged = function() {
-  if (this.mapPostrenderListenerKey_) {
-    ol.dom.removeNode(this.element_);
-    ol.events.unlistenByKey(this.mapPostrenderListenerKey_);
-    this.mapPostrenderListenerKey_ = null;
-  }
-  var map = this.getMap();
-  if (map) {
-    this.mapPostrenderListenerKey_ = ol.events.listen(map,
-        ol.MapEvent.Type.POSTRENDER, this.render, this);
-    this.updatePixelPosition();
-    var container = this.stopEvent_ ?
-        map.getOverlayContainerStopEvent() : map.getOverlayContainer();
-    if (this.insertFirst_) {
-      container.insertBefore(this.element_, container.childNodes[0] || null);
-    } else {
-      container.appendChild(this.element_);
-    }
-  }
-};
-
-
-/**
- * @protected
- */
-ol.Overlay.prototype.render = function() {
-  this.updatePixelPosition();
-};
-
-
-/**
- * @protected
- */
-ol.Overlay.prototype.handleOffsetChanged = function() {
-  this.updatePixelPosition();
-};
-
-
-/**
- * @protected
- */
-ol.Overlay.prototype.handlePositionChanged = function() {
-  this.updatePixelPosition();
-  if (this.get(ol.Overlay.Property.POSITION) !== undefined && this.autoPan) {
-    this.panIntoView_();
-  }
-};
-
-
-/**
- * @protected
- */
-ol.Overlay.prototype.handlePositioningChanged = function() {
-  this.updatePixelPosition();
-};
-
-
-/**
- * Set the DOM element to be associated with this overlay.
- * @param {Element|undefined} element The Element containing the overlay.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.setElement = function(element) {
-  this.set(ol.Overlay.Property.ELEMENT, element);
-};
-
-
-/**
- * Set the map to be associated with this overlay.
- * @param {ol.Map|undefined} map The map that the overlay is part of.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.setMap = function(map) {
-  this.set(ol.Overlay.Property.MAP, map);
-};
-
-
-/**
- * Set the offset for this overlay.
- * @param {Array.<number>} offset Offset.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.setOffset = function(offset) {
-  this.set(ol.Overlay.Property.OFFSET, offset);
-};
-
-
-/**
- * Set the position for this overlay. If the position is `undefined` the
- * overlay is hidden.
- * @param {ol.Coordinate|undefined} position The spatial point that the overlay
- *     is anchored at.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.setPosition = function(position) {
-  this.set(ol.Overlay.Property.POSITION, position);
-};
-
-
-/**
- * Pan the map so that the overlay is entirely visible in the current viewport
- * (if necessary).
- * @private
- */
-ol.Overlay.prototype.panIntoView_ = function() {
-  var map = this.getMap();
-
-  if (map === undefined || !map.getTargetElement()) {
-    return;
-  }
-
-  var mapRect = this.getRect_(map.getTargetElement(), map.getSize());
-  var element = /** @type {!Element} */ (this.getElement());
-  var overlayRect = this.getRect_(element,
-      [ol.dom.outerWidth(element), ol.dom.outerHeight(element)]);
-
-  var margin = this.autoPanMargin_;
-  if (!ol.extent.containsExtent(mapRect, overlayRect)) {
-    // the overlay is not completely inside the viewport, so pan the map
-    var offsetLeft = overlayRect[0] - mapRect[0];
-    var offsetRight = mapRect[2] - overlayRect[2];
-    var offsetTop = overlayRect[1] - mapRect[1];
-    var offsetBottom = mapRect[3] - overlayRect[3];
-
-    var delta = [0, 0];
-    if (offsetLeft < 0) {
-      // move map to the left
-      delta[0] = offsetLeft - margin;
-    } else if (offsetRight < 0) {
-      // move map to the right
-      delta[0] = Math.abs(offsetRight) + margin;
-    }
-    if (offsetTop < 0) {
-      // move map up
-      delta[1] = offsetTop - margin;
-    } else if (offsetBottom < 0) {
-      // move map down
-      delta[1] = Math.abs(offsetBottom) + margin;
-    }
-
-    if (delta[0] !== 0 || delta[1] !== 0) {
-      var center = /** @type {ol.Coordinate} */ (map.getView().getCenter());
-      var centerPx = map.getPixelFromCoordinate(center);
-      var newCenterPx = [
-        centerPx[0] + delta[0],
-        centerPx[1] + delta[1]
-      ];
-
-      if (this.autoPanAnimation_) {
-        this.autoPanAnimation_.source = center;
-        map.beforeRender(ol.animation.pan(this.autoPanAnimation_));
-      }
-      map.getView().setCenter(map.getCoordinateFromPixel(newCenterPx));
-    }
-  }
-};
-
-
-/**
- * Get the extent of an element relative to the document
- * @param {Element|undefined} element The element.
- * @param {ol.Size|undefined} size The size of the element.
- * @return {ol.Extent} The extent.
- * @private
- */
-ol.Overlay.prototype.getRect_ = function(element, size) {
-  var box = element.getBoundingClientRect();
-  var offsetX = box.left + window.pageXOffset;
-  var offsetY = box.top + window.pageYOffset;
-  return [
-    offsetX,
-    offsetY,
-    offsetX + size[0],
-    offsetY + size[1]
-  ];
-};
-
-
-/**
- * Set the positioning for this overlay.
- * @param {ol.Overlay.Positioning} positioning how the overlay is
- *     positioned relative to its point on the map.
- * @observable
- * @api stable
- */
-ol.Overlay.prototype.setPositioning = function(positioning) {
-  this.set(ol.Overlay.Property.POSITIONING, positioning);
-};
-
-
-/**
- * Modify the visibility of the element.
- * @param {boolean} visible Element visibility.
- * @protected
- */
-ol.Overlay.prototype.setVisible = function(visible) {
-  if (this.rendered_.visible !== visible) {
-    this.element_.style.display = visible ? '' : 'none';
-    this.rendered_.visible = visible;
-  }
-};
-
-
-/**
- * Update pixel position.
- * @protected
- */
-ol.Overlay.prototype.updatePixelPosition = function() {
-  var map = this.getMap();
-  var position = this.getPosition();
-  if (map === undefined || !map.isRendered() || position === undefined) {
-    this.setVisible(false);
-    return;
-  }
-
-  var pixel = map.getPixelFromCoordinate(position);
-  var mapSize = map.getSize();
-  this.updateRenderedPosition(pixel, mapSize);
-};
-
-
-/**
- * @param {ol.Pixel} pixel The pixel location.
- * @param {ol.Size|undefined} mapSize The map size.
- * @protected
- */
-ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) {
-  var style = this.element_.style;
-  var offset = this.getOffset();
-
-  var positioning = this.getPositioning();
-  ol.DEBUG && console.assert(positioning !== undefined,
-      'positioning should be defined');
-
-  var offsetX = offset[0];
-  var offsetY = offset[1];
-  if (positioning == ol.Overlay.Positioning.BOTTOM_RIGHT ||
-      positioning == ol.Overlay.Positioning.CENTER_RIGHT ||
-      positioning == ol.Overlay.Positioning.TOP_RIGHT) {
-    if (this.rendered_.left_ !== '') {
-      this.rendered_.left_ = style.left = '';
-    }
-    var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px';
-    if (this.rendered_.right_ != right) {
-      this.rendered_.right_ = style.right = right;
-    }
-  } else {
-    if (this.rendered_.right_ !== '') {
-      this.rendered_.right_ = style.right = '';
-    }
-    if (positioning == ol.Overlay.Positioning.BOTTOM_CENTER ||
-        positioning == ol.Overlay.Positioning.CENTER_CENTER ||
-        positioning == ol.Overlay.Positioning.TOP_CENTER) {
-      offsetX -= this.element_.offsetWidth / 2;
-    }
-    var left = Math.round(pixel[0] + offsetX) + 'px';
-    if (this.rendered_.left_ != left) {
-      this.rendered_.left_ = style.left = left;
-    }
-  }
-  if (positioning == ol.Overlay.Positioning.BOTTOM_LEFT ||
-      positioning == ol.Overlay.Positioning.BOTTOM_CENTER ||
-      positioning == ol.Overlay.Positioning.BOTTOM_RIGHT) {
-    if (this.rendered_.top_ !== '') {
-      this.rendered_.top_ = style.top = '';
-    }
-    var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px';
-    if (this.rendered_.bottom_ != bottom) {
-      this.rendered_.bottom_ = style.bottom = bottom;
-    }
-  } else {
-    if (this.rendered_.bottom_ !== '') {
-      this.rendered_.bottom_ = style.bottom = '';
-    }
-    if (positioning == ol.Overlay.Positioning.CENTER_LEFT ||
-        positioning == ol.Overlay.Positioning.CENTER_CENTER ||
-        positioning == ol.Overlay.Positioning.CENTER_RIGHT) {
-      offsetY -= this.element_.offsetHeight / 2;
-    }
-    var top = Math.round(pixel[1] + offsetY) + 'px';
-    if (this.rendered_.top_ != top) {
-      this.rendered_.top_ = style.top = top;
-    }
-  }
-
-  this.setVisible(true);
-};
-
-
-/**
- * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
- * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
- * `'top-center'`, `'top-right'`
- * @enum {string}
- */
-ol.Overlay.Positioning = {
-  BOTTOM_LEFT: 'bottom-left',
-  BOTTOM_CENTER: 'bottom-center',
-  BOTTOM_RIGHT: 'bottom-right',
-  CENTER_LEFT: 'center-left',
-  CENTER_CENTER: 'center-center',
-  CENTER_RIGHT: 'center-right',
-  TOP_LEFT: 'top-left',
-  TOP_CENTER: 'top-center',
-  TOP_RIGHT: 'top-right'
-};
-
-
-/**
- * @enum {string}
- */
-ol.Overlay.Property = {
-  ELEMENT: 'element',
-  MAP: 'map',
-  OFFSET: 'offset',
-  POSITION: 'position',
-  POSITIONING: 'positioning'
-};
-
-goog.provide('ol.control.OverviewMap');
-
-goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.Map');
-goog.require('ol.MapEvent');
-goog.require('ol.Object');
-goog.require('ol.ObjectEventType');
-goog.require('ol.Overlay');
-goog.require('ol.View');
-goog.require('ol.control.Control');
-goog.require('ol.coordinate');
-goog.require('ol.css');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-
-
-/**
- * Create a new control with a map acting as an overview map for an other
- * defined map.
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options.
- * @api
- */
-ol.control.OverviewMap = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.collapsible_ = options.collapsible !== undefined ?
-      options.collapsible : true;
-
-  if (!this.collapsible_) {
-    this.collapsed_ = false;
-  }
-
-  var className = options.className !== undefined ? options.className : 'ol-overviewmap';
-
-  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Overview map';
-
-  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00AB';
-
-  if (typeof collapseLabel === 'string') {
-    /**
-     * @private
-     * @type {Node}
-     */
-    this.collapseLabel_ = document.createElement('span');
-    this.collapseLabel_.textContent = collapseLabel;
-  } else {
-    this.collapseLabel_ = collapseLabel;
-  }
-
-  var label = options.label !== undefined ? options.label : '\u00BB';
-
-
-  if (typeof label === 'string') {
-    /**
-     * @private
-     * @type {Node}
-     */
-    this.label_ = document.createElement('span');
-    this.label_.textContent = label;
-  } else {
-    this.label_ = label;
-  }
-
-  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
-      this.collapseLabel_ : this.label_;
-  var button = document.createElement('button');
-  button.setAttribute('type', 'button');
-  button.title = tipLabel;
-  button.appendChild(activeLabel);
-
-  ol.events.listen(button, ol.events.EventType.CLICK,
-      this.handleClick_, this);
-
-  var ovmapDiv = document.createElement('DIV');
-  ovmapDiv.className = 'ol-overviewmap-map';
-
-  /**
-   * @type {ol.Map}
-   * @private
-   */
-  this.ovmap_ = new ol.Map({
-    controls: new ol.Collection(),
-    interactions: new ol.Collection(),
-    target: ovmapDiv,
-    view: options.view
-  });
-  var ovmap = this.ovmap_;
-
-  if (options.layers) {
-    options.layers.forEach(
-        /**
-       * @param {ol.layer.Layer} layer Layer.
-       */
-        function(layer) {
-          ovmap.addLayer(layer);
-        }, this);
-  }
-
-  var box = document.createElement('DIV');
-  box.className = 'ol-overviewmap-box';
-  box.style.boxSizing = 'border-box';
-
-  /**
-   * @type {ol.Overlay}
-   * @private
-   */
-  this.boxOverlay_ = new ol.Overlay({
-    position: [0, 0],
-    positioning: ol.Overlay.Positioning.BOTTOM_LEFT,
-    element: box
-  });
-  this.ovmap_.addOverlay(this.boxOverlay_);
-
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL +
-      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
-      (this.collapsible_ ? '' : ' ol-uncollapsible');
-  var element = document.createElement('div');
-  element.className = cssClasses;
-  element.appendChild(ovmapDiv);
-  element.appendChild(button);
-
-  var render = options.render ? options.render : ol.control.OverviewMap.render;
-
-  ol.control.Control.call(this, {
-    element: element,
-    render: render,
-    target: options.target
-  });
-};
-ol.inherits(ol.control.OverviewMap, ol.control.Control);
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.control.OverviewMap.prototype.setMap = function(map) {
-  var oldMap = this.getMap();
-  if (map === oldMap) {
-    return;
-  }
-  if (oldMap) {
-    var oldView = oldMap.getView();
-    if (oldView) {
-      this.unbindView_(oldView);
-    }
-  }
-  ol.control.Control.prototype.setMap.call(this, map);
-
-  if (map) {
-    this.listenerKeys.push(ol.events.listen(
-        map, ol.ObjectEventType.PROPERTYCHANGE,
-        this.handleMapPropertyChange_, this));
-
-    // TODO: to really support map switching, this would need to be reworked
-    if (this.ovmap_.getLayers().getLength() === 0) {
-      this.ovmap_.setLayerGroup(map.getLayerGroup());
-    }
-
-    var view = map.getView();
-    if (view) {
-      this.bindView_(view);
-      if (view.isDef()) {
-        this.ovmap_.updateSize();
-        this.resetExtent_();
-      }
-    }
-  }
-};
-
-
-/**
- * Handle map property changes.  This only deals with changes to the map's view.
- * @param {ol.ObjectEvent} event The propertychange event.
- * @private
- */
-ol.control.OverviewMap.prototype.handleMapPropertyChange_ = function(event) {
-  if (event.key === ol.Map.Property.VIEW) {
-    var oldView = /** @type {ol.View} */ (event.oldValue);
-    if (oldView) {
-      this.unbindView_(oldView);
-    }
-    var newView = this.getMap().getView();
-    this.bindView_(newView);
-  }
-};
-
-
-/**
- * Register listeners for view property changes.
- * @param {ol.View} view The view.
- * @private
- */
-ol.control.OverviewMap.prototype.bindView_ = function(view) {
-  ol.events.listen(view,
-      ol.Object.getChangeEventType(ol.View.Property.ROTATION),
-      this.handleRotationChanged_, this);
-};
-
-
-/**
- * Unregister listeners for view property changes.
- * @param {ol.View} view The view.
- * @private
- */
-ol.control.OverviewMap.prototype.unbindView_ = function(view) {
-  ol.events.unlisten(view,
-      ol.Object.getChangeEventType(ol.View.Property.ROTATION),
-      this.handleRotationChanged_, this);
-};
-
-
-/**
- * Handle rotation changes to the main map.
- * TODO: This should rotate the extent rectrangle instead of the
- * overview map's view.
- * @private
- */
-ol.control.OverviewMap.prototype.handleRotationChanged_ = function() {
-  this.ovmap_.getView().setRotation(this.getMap().getView().getRotation());
-};
-
-
-/**
- * Update the overview map element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.OverviewMap}
- * @api
- */
-ol.control.OverviewMap.render = function(mapEvent) {
-  this.validateExtent_();
-  this.updateBox_();
-};
-
-
-/**
- * Reset the overview map extent if the box size (width or
- * height) is less than the size of the overview map size times minRatio
- * or is greater than the size of the overview size times maxRatio.
- *
- * If the map extent was not reset, the box size can fits in the defined
- * ratio sizes. This method then checks if is contained inside the overview
- * map current extent. If not, recenter the overview map to the current
- * main map center location.
- * @private
- */
-ol.control.OverviewMap.prototype.validateExtent_ = function() {
-  var map = this.getMap();
-  var ovmap = this.ovmap_;
-
-  if (!map.isRendered() || !ovmap.isRendered()) {
-    return;
-  }
-
-  var mapSize = /** @type {ol.Size} */ (map.getSize());
-
-  var view = map.getView();
-  var extent = view.calculateExtent(mapSize);
-
-  var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize());
-
-  var ovview = ovmap.getView();
-  var ovextent = ovview.calculateExtent(ovmapSize);
-
-  var topLeftPixel =
-      ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent));
-  var bottomRightPixel =
-      ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent));
-
-  var boxWidth = Math.abs(topLeftPixel[0] - bottomRightPixel[0]);
-  var boxHeight = Math.abs(topLeftPixel[1] - bottomRightPixel[1]);
-
-  var ovmapWidth = ovmapSize[0];
-  var ovmapHeight = ovmapSize[1];
-
-  if (boxWidth < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO ||
-      boxHeight < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO ||
-      boxWidth > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO ||
-      boxHeight > ovmapHeight * ol.OVERVIEWMAP_MAX_RATIO) {
-    this.resetExtent_();
-  } else if (!ol.extent.containsExtent(ovextent, extent)) {
-    this.recenter_();
-  }
-};
-
-
-/**
- * Reset the overview map extent to half calculated min and max ratio times
- * the extent of the main map.
- * @private
- */
-ol.control.OverviewMap.prototype.resetExtent_ = function() {
-  if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) {
-    return;
-  }
-
-  var map = this.getMap();
-  var ovmap = this.ovmap_;
-
-  var mapSize = /** @type {ol.Size} */ (map.getSize());
-
-  var view = map.getView();
-  var extent = view.calculateExtent(mapSize);
-
-  var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize());
-
-  var ovview = ovmap.getView();
-
-  // get how many times the current map overview could hold different
-  // box sizes using the min and max ratio, pick the step in the middle used
-  // to calculate the extent from the main map to set it to the overview map,
-  var steps = Math.log(
-      ol.OVERVIEWMAP_MAX_RATIO / ol.OVERVIEWMAP_MIN_RATIO) / Math.LN2;
-  var ratio = 1 / (Math.pow(2, steps / 2) * ol.OVERVIEWMAP_MIN_RATIO);
-  ol.extent.scaleFromCenter(extent, ratio);
-  ovview.fit(extent, ovmapSize);
-};
-
-
-/**
- * Set the center of the overview map to the map center without changing its
- * resolution.
- * @private
- */
-ol.control.OverviewMap.prototype.recenter_ = function() {
-  var map = this.getMap();
-  var ovmap = this.ovmap_;
-
-  var view = map.getView();
-
-  var ovview = ovmap.getView();
-
-  ovview.setCenter(view.getCenter());
-};
-
-
-/**
- * Update the box using the main map extent
- * @private
- */
-ol.control.OverviewMap.prototype.updateBox_ = function() {
-  var map = this.getMap();
-  var ovmap = this.ovmap_;
-
-  if (!map.isRendered() || !ovmap.isRendered()) {
-    return;
-  }
-
-  var mapSize = /** @type {ol.Size} */ (map.getSize());
-
-  var view = map.getView();
-
-  var ovview = ovmap.getView();
-
-  var rotation = view.getRotation();
-
-  var overlay = this.boxOverlay_;
-  var box = this.boxOverlay_.getElement();
-  var extent = view.calculateExtent(mapSize);
-  var ovresolution = ovview.getResolution();
-  var bottomLeft = ol.extent.getBottomLeft(extent);
-  var topRight = ol.extent.getTopRight(extent);
-
-  // set position using bottom left coordinates
-  var rotateBottomLeft = this.calculateCoordinateRotate_(rotation, bottomLeft);
-  overlay.setPosition(rotateBottomLeft);
-
-  // set box size calculated from map extent size and overview map resolution
-  if (box) {
-    box.style.width = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution) + 'px';
-    box.style.height = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution) + 'px';
-  }
-};
-
-
-/**
- * @param {number} rotation Target rotation.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor.
- * @private
- */
-ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function(
-    rotation, coordinate) {
-  var coordinateRotate;
-
-  var map = this.getMap();
-  var view = map.getView();
-
-  var currentCenter = view.getCenter();
-
-  if (currentCenter) {
-    coordinateRotate = [
-      coordinate[0] - currentCenter[0],
-      coordinate[1] - currentCenter[1]
-    ];
-    ol.coordinate.rotate(coordinateRotate, rotation);
-    ol.coordinate.add(coordinateRotate, currentCenter);
-  }
-  return coordinateRotate;
-};
-
-
-/**
- * @param {Event} event The event to handle
- * @private
- */
-ol.control.OverviewMap.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleToggle_();
-};
-
-
-/**
- * @private
- */
-ol.control.OverviewMap.prototype.handleToggle_ = function() {
-  this.element.classList.toggle('ol-collapsed');
-  if (this.collapsed_) {
-    ol.dom.replaceNode(this.collapseLabel_, this.label_);
-  } else {
-    ol.dom.replaceNode(this.label_, this.collapseLabel_);
-  }
-  this.collapsed_ = !this.collapsed_;
-
-  // manage overview map if it had not been rendered before and control
-  // is expanded
-  var ovmap = this.ovmap_;
-  if (!this.collapsed_ && !ovmap.isRendered()) {
-    ovmap.updateSize();
-    this.resetExtent_();
-    ol.events.listenOnce(ovmap, ol.MapEvent.Type.POSTRENDER,
-        function(event) {
-          this.updateBox_();
-        },
-        this);
-  }
-};
-
-
-/**
- * Return `true` if the overview map is collapsible, `false` otherwise.
- * @return {boolean} True if the widget is collapsible.
- * @api stable
- */
-ol.control.OverviewMap.prototype.getCollapsible = function() {
-  return this.collapsible_;
-};
-
-
-/**
- * Set whether the overview map should be collapsible.
- * @param {boolean} collapsible True if the widget is collapsible.
- * @api stable
- */
-ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) {
-  if (this.collapsible_ === collapsible) {
-    return;
-  }
-  this.collapsible_ = collapsible;
-  this.element.classList.toggle('ol-uncollapsible');
-  if (!collapsible && this.collapsed_) {
-    this.handleToggle_();
-  }
-};
-
-
-/**
- * Collapse or expand the overview map according to the passed parameter. Will
- * not do anything if the overview map isn't collapsible or if the current
- * collapsed state is already the one requested.
- * @param {boolean} collapsed True if the widget is collapsed.
- * @api stable
- */
-ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) {
-  if (!this.collapsible_ || this.collapsed_ === collapsed) {
-    return;
-  }
-  this.handleToggle_();
-};
-
-
-/**
- * Determine if the overview map is collapsed.
- * @return {boolean} The overview map is collapsed.
- * @api stable
- */
-ol.control.OverviewMap.prototype.getCollapsed = function() {
-  return this.collapsed_;
-};
-
-
-/**
- * Return the overview map.
- * @return {ol.Map} Overview map.
- * @api
- */
-ol.control.OverviewMap.prototype.getOverviewMap = function() {
-  return this.ovmap_;
-};
-
-goog.provide('ol.control.ScaleLine');
-
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.asserts');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.events');
-goog.require('ol.proj.METERS_PER_UNIT');
-goog.require('ol.proj.Units');
-
-
-/**
- * @classdesc
- * A control displaying rough y-axis distances, calculated for the center of the
- * viewport. For conformal projections (e.g. EPSG:3857, the default view
- * projection in OpenLayers), the scale is valid for all directions.
- * No scale line will be shown when the y-axis distance of a pixel at the
- * viewport center cannot be calculated in the view projection.
- * By default the scale line will show in the bottom left portion of the map,
- * but this can be changed by using the css selector `.ol-scale-line`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ScaleLineOptions=} opt_options Scale line options.
- * @api stable
- */
-ol.control.ScaleLine = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  var className = options.className !== undefined ? options.className : 'ol-scale-line';
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.innerElement_ = document.createElement('DIV');
-  this.innerElement_.className = className + '-inner';
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.element_ = document.createElement('DIV');
-  this.element_.className = className + ' ' + ol.css.CLASS_UNSELECTABLE;
-  this.element_.appendChild(this.innerElement_);
-
-  /**
-   * @private
-   * @type {?olx.ViewState}
-   */
-  this.viewState_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.minWidth_ = options.minWidth !== undefined ? options.minWidth : 64;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderedVisible_ = false;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.renderedWidth_ = undefined;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.renderedHTML_ = '';
-
-  var render = options.render ? options.render : ol.control.ScaleLine.render;
-
-  ol.control.Control.call(this, {
-    element: this.element_,
-    render: render,
-    target: options.target
-  });
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.control.ScaleLine.Property.UNITS),
-      this.handleUnitsChanged_, this);
-
-  this.setUnits(/** @type {ol.control.ScaleLine.Units} */ (options.units) ||
-      ol.control.ScaleLine.Units.METRIC);
-
-};
-ol.inherits(ol.control.ScaleLine, ol.control.Control);
-
-
-/**
- * @const
- * @type {Array.<number>}
- */
-ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5];
-
-
-/**
- * Return the units to use in the scale line.
- * @return {ol.control.ScaleLine.Units|undefined} The units to use in the scale
- *     line.
- * @observable
- * @api stable
- */
-ol.control.ScaleLine.prototype.getUnits = function() {
-  return /** @type {ol.control.ScaleLine.Units|undefined} */ (
-      this.get(ol.control.ScaleLine.Property.UNITS));
-};
-
-
-/**
- * Update the scale line element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.ScaleLine}
- * @api
- */
-ol.control.ScaleLine.render = function(mapEvent) {
-  var frameState = mapEvent.frameState;
-  if (!frameState) {
-    this.viewState_ = null;
-  } else {
-    this.viewState_ = frameState.viewState;
-  }
-  this.updateElement_();
-};
-
-
-/**
- * @private
- */
-ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() {
-  this.updateElement_();
-};
-
-
-/**
- * Set the units to use in the scale line.
- * @param {ol.control.ScaleLine.Units} units The units to use in the scale line.
- * @observable
- * @api stable
- */
-ol.control.ScaleLine.prototype.setUnits = function(units) {
-  this.set(ol.control.ScaleLine.Property.UNITS, units);
-};
-
-
-/**
- * @private
- */
-ol.control.ScaleLine.prototype.updateElement_ = function() {
-  var viewState = this.viewState_;
-
-  if (!viewState) {
-    if (this.renderedVisible_) {
-      this.element_.style.display = 'none';
-      this.renderedVisible_ = false;
-    }
-    return;
-  }
-
-  var center = viewState.center;
-  var projection = viewState.projection;
-  var metersPerUnit = projection.getMetersPerUnit();
-  var pointResolution =
-      projection.getPointResolution(viewState.resolution, center) *
-      metersPerUnit;
-
-  var nominalCount = this.minWidth_ * pointResolution;
-  var suffix = '';
-  var units = this.getUnits();
-  if (units == ol.control.ScaleLine.Units.DEGREES) {
-    var metersPerDegree = ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES];
-    pointResolution /= metersPerDegree;
-    if (nominalCount < metersPerDegree / 60) {
-      suffix = '\u2033'; // seconds
-      pointResolution *= 3600;
-    } else if (nominalCount < metersPerDegree) {
-      suffix = '\u2032'; // minutes
-      pointResolution *= 60;
-    } else {
-      suffix = '\u00b0'; // degrees
-    }
-  } else if (units == ol.control.ScaleLine.Units.IMPERIAL) {
-    if (nominalCount < 0.9144) {
-      suffix = 'in';
-      pointResolution /= 0.0254;
-    } else if (nominalCount < 1609.344) {
-      suffix = 'ft';
-      pointResolution /= 0.3048;
-    } else {
-      suffix = 'mi';
-      pointResolution /= 1609.344;
-    }
-  } else if (units == ol.control.ScaleLine.Units.NAUTICAL) {
-    pointResolution /= 1852;
-    suffix = 'nm';
-  } else if (units == ol.control.ScaleLine.Units.METRIC) {
-    if (nominalCount < 1) {
-      suffix = 'mm';
-      pointResolution *= 1000;
-    } else if (nominalCount < 1000) {
-      suffix = 'm';
-    } else {
-      suffix = 'km';
-      pointResolution /= 1000;
-    }
-  } else if (units == ol.control.ScaleLine.Units.US) {
-    if (nominalCount < 0.9144) {
-      suffix = 'in';
-      pointResolution *= 39.37;
-    } else if (nominalCount < 1609.344) {
-      suffix = 'ft';
-      pointResolution /= 0.30480061;
-    } else {
-      suffix = 'mi';
-      pointResolution /= 1609.3472;
-    }
-  } else {
-    ol.asserts.assert(false, 33); // Invalid units
-  }
-
-  var i = 3 * Math.floor(
-      Math.log(this.minWidth_ * pointResolution) / Math.log(10));
-  var count, width;
-  while (true) {
-    count = ol.control.ScaleLine.LEADING_DIGITS[((i % 3) + 3) % 3] *
-        Math.pow(10, Math.floor(i / 3));
-    width = Math.round(count / pointResolution);
-    if (isNaN(width)) {
-      this.element_.style.display = 'none';
-      this.renderedVisible_ = false;
-      return;
-    } else if (width >= this.minWidth_) {
-      break;
-    }
-    ++i;
-  }
-
-  var html = count + ' ' + suffix;
-  if (this.renderedHTML_ != html) {
-    this.innerElement_.innerHTML = html;
-    this.renderedHTML_ = html;
-  }
-
-  if (this.renderedWidth_ != width) {
-    this.innerElement_.style.width = width + 'px';
-    this.renderedWidth_ = width;
-  }
-
-  if (!this.renderedVisible_) {
-    this.element_.style.display = '';
-    this.renderedVisible_ = true;
-  }
-
-};
-
-
-/**
- * @enum {string}
- * @api
- */
-ol.control.ScaleLine.Property = {
-  UNITS: 'units'
-};
-
-
-/**
- * Units for the scale line. Supported values are `'degrees'`, `'imperial'`,
- * `'nautical'`, `'metric'`, `'us'`.
- * @enum {string}
- */
-ol.control.ScaleLine.Units = {
-  DEGREES: 'degrees',
-  IMPERIAL: 'imperial',
-  NAUTICAL: 'nautical',
-  METRIC: 'metric',
-  US: 'us'
-};
-
-// FIXME should possibly show tooltip when dragging?
-
-goog.provide('ol.control.ZoomSlider');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.animation');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-goog.require('ol.easing');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.EventType');
-goog.require('ol.math');
-goog.require('ol.pointer.EventType');
-goog.require('ol.pointer.PointerEventHandler');
-
-
-/**
- * @classdesc
- * A slider type of control for zooming.
- *
- * Example:
- *
- *     map.addControl(new ol.control.ZoomSlider());
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options.
- * @api stable
- */
-ol.control.ZoomSlider = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * Will hold the current resolution of the view.
-   *
-   * @type {number|undefined}
-   * @private
-   */
-  this.currentResolution_ = undefined;
-
-  /**
-   * The direction of the slider. Will be determined from actual display of the
-   * container and defaults to ol.control.ZoomSlider.direction.VERTICAL.
-   *
-   * @type {ol.control.ZoomSlider.direction}
-   * @private
-   */
-  this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.dragging_;
-
-  /**
-   * @type {!Array.<ol.EventsKey>}
-   * @private
-   */
-  this.dragListenerKeys_ = [];
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.heightLimit_ = 0;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.widthLimit_ = 0;
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.previousX_;
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.previousY_;
-
-  /**
-   * The calculated thumb size (border box plus margins).  Set when initSlider_
-   * is called.
-   * @type {ol.Size}
-   * @private
-   */
-  this.thumbSize_ = null;
-
-  /**
-   * Whether the slider is initialized.
-   * @type {boolean}
-   * @private
-   */
-  this.sliderInitialized_ = false;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 200;
-
-  var className = options.className !== undefined ? options.className : 'ol-zoomslider';
-  var thumbElement = document.createElement('button');
-  thumbElement.setAttribute('type', 'button');
-  thumbElement.className = className + '-thumb ' + ol.css.CLASS_UNSELECTABLE;
-  var containerElement = document.createElement('div');
-  containerElement.className = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + ol.css.CLASS_CONTROL;
-  containerElement.appendChild(thumbElement);
-  /**
-   * @type {ol.pointer.PointerEventHandler}
-   * @private
-   */
-  this.dragger_ = new ol.pointer.PointerEventHandler(containerElement);
-
-  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERDOWN,
-      this.handleDraggerStart_, this);
-  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERMOVE,
-      this.handleDraggerDrag_, this);
-  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERUP,
-      this.handleDraggerEnd_, this);
-
-  ol.events.listen(containerElement, ol.events.EventType.CLICK,
-      this.handleContainerClick_, this);
-  ol.events.listen(thumbElement, ol.events.EventType.CLICK,
-      ol.events.Event.stopPropagation);
-
-  var render = options.render ? options.render : ol.control.ZoomSlider.render;
-
-  ol.control.Control.call(this, {
-    element: containerElement,
-    render: render
-  });
-};
-ol.inherits(ol.control.ZoomSlider, ol.control.Control);
-
-
-/**
- * @inheritDoc
- */
-ol.control.ZoomSlider.prototype.disposeInternal = function() {
-  this.dragger_.dispose();
-  ol.control.Control.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * The enum for available directions.
- *
- * @enum {number}
- */
-ol.control.ZoomSlider.direction = {
-  VERTICAL: 0,
-  HORIZONTAL: 1
-};
-
-
-/**
- * @inheritDoc
- */
-ol.control.ZoomSlider.prototype.setMap = function(map) {
-  ol.control.Control.prototype.setMap.call(this, map);
-  if (map) {
-    map.render();
-  }
-};
-
-
-/**
- * Initializes the slider element. This will determine and set this controls
- * direction_ and also constrain the dragging of the thumb to always be within
- * the bounds of the container.
- *
- * @private
- */
-ol.control.ZoomSlider.prototype.initSlider_ = function() {
-  var container = this.element;
-  var containerSize = {
-    width: container.offsetWidth, height: container.offsetHeight
-  };
-
-  var thumb = container.firstElementChild;
-  var computedStyle = getComputedStyle(thumb);
-  var thumbWidth = thumb.offsetWidth +
-      parseFloat(computedStyle['marginRight']) +
-      parseFloat(computedStyle['marginLeft']);
-  var thumbHeight = thumb.offsetHeight +
-      parseFloat(computedStyle['marginTop']) +
-      parseFloat(computedStyle['marginBottom']);
-  this.thumbSize_ = [thumbWidth, thumbHeight];
-
-  if (containerSize.width > containerSize.height) {
-    this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL;
-    this.widthLimit_ = containerSize.width - thumbWidth;
-  } else {
-    this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;
-    this.heightLimit_ = containerSize.height - thumbHeight;
-  }
-  this.sliderInitialized_ = true;
-};
-
-
-/**
- * Update the zoomslider element.
- * @param {ol.MapEvent} mapEvent Map event.
- * @this {ol.control.ZoomSlider}
- * @api
- */
-ol.control.ZoomSlider.render = function(mapEvent) {
-  if (!mapEvent.frameState) {
-    return;
-  }
-  if (!this.sliderInitialized_) {
-    this.initSlider_();
-  }
-  var res = mapEvent.frameState.viewState.resolution;
-  if (res !== this.currentResolution_) {
-    this.currentResolution_ = res;
-    this.setThumbPosition_(res);
-  }
-};
-
-
-/**
- * @param {Event} event The browser event to handle.
- * @private
- */
-ol.control.ZoomSlider.prototype.handleContainerClick_ = function(event) {
-  var map = this.getMap();
-  var view = map.getView();
-  var currentResolution = view.getResolution();
-  map.beforeRender(ol.animation.zoom({
-    resolution: /** @type {number} */ (currentResolution),
-    duration: this.duration_,
-    easing: ol.easing.easeOut
-  }));
-  var relativePosition = this.getRelativePosition_(
-      event.offsetX - this.thumbSize_[0] / 2,
-      event.offsetY - this.thumbSize_[1] / 2);
-  var resolution = this.getResolutionForPosition_(relativePosition);
-  view.setResolution(view.constrainResolution(resolution));
-};
-
-
-/**
- * Handle dragger start events.
- * @param {ol.pointer.PointerEvent} event The drag event.
- * @private
- */
-ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) {
-  if (!this.dragging_ &&
-      event.originalEvent.target === this.element.firstElementChild) {
-    this.getMap().getView().setHint(ol.View.Hint.INTERACTING, 1);
-    this.previousX_ = event.clientX;
-    this.previousY_ = event.clientY;
-    this.dragging_ = true;
-
-    if (this.dragListenerKeys_.length === 0) {
-      var drag = this.handleDraggerDrag_;
-      var end = this.handleDraggerEnd_;
-      this.dragListenerKeys_.push(
-        ol.events.listen(document, ol.events.EventType.MOUSEMOVE, drag, this),
-        ol.events.listen(document, ol.events.EventType.TOUCHMOVE, drag, this),
-        ol.events.listen(document, ol.pointer.EventType.POINTERMOVE, drag, this),
-        ol.events.listen(document, ol.events.EventType.MOUSEUP, end, this),
-        ol.events.listen(document, ol.events.EventType.TOUCHEND, end, this),
-        ol.events.listen(document, ol.pointer.EventType.POINTERUP, end, this)
-      );
-    }
-  }
-};
-
-
-/**
- * Handle dragger drag events.
- *
- * @param {ol.pointer.PointerEvent|Event} event The drag event.
- * @private
- */
-ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) {
-  if (this.dragging_) {
-    var element = this.element.firstElementChild;
-    var deltaX = event.clientX - this.previousX_ + parseInt(element.style.left, 10);
-    var deltaY = event.clientY - this.previousY_ + parseInt(element.style.top, 10);
-    var relativePosition = this.getRelativePosition_(deltaX, deltaY);
-    this.currentResolution_ = this.getResolutionForPosition_(relativePosition);
-    this.getMap().getView().setResolution(this.currentResolution_);
-    this.setThumbPosition_(this.currentResolution_);
-    this.previousX_ = event.clientX;
-    this.previousY_ = event.clientY;
-  }
-};
-
-
-/**
- * Handle dragger end events.
- * @param {ol.pointer.PointerEvent|Event} event The drag event.
- * @private
- */
-ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) {
-  if (this.dragging_) {
-    var map = this.getMap();
-    var view = map.getView();
-    view.setHint(ol.View.Hint.INTERACTING, -1);
-    map.beforeRender(ol.animation.zoom({
-      resolution: /** @type {number} */ (this.currentResolution_),
-      duration: this.duration_,
-      easing: ol.easing.easeOut
-    }));
-    var resolution = view.constrainResolution(this.currentResolution_);
-    view.setResolution(resolution);
-    this.dragging_ = false;
-    this.previousX_ = undefined;
-    this.previousY_ = undefined;
-    this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
-    this.dragListenerKeys_.length = 0;
-  }
-};
-
-
-/**
- * Positions the thumb inside its container according to the given resolution.
- *
- * @param {number} res The res.
- * @private
- */
-ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) {
-  var position = this.getPositionForResolution_(res);
-  var thumb = this.element.firstElementChild;
-
-  if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) {
-    thumb.style.left = this.widthLimit_ * position + 'px';
-  } else {
-    thumb.style.top = this.heightLimit_ * position + 'px';
-  }
-};
-
-
-/**
- * Calculates the relative position of the thumb given x and y offsets.  The
- * relative position scales from 0 to 1.  The x and y offsets are assumed to be
- * in pixel units within the dragger limits.
- *
- * @param {number} x Pixel position relative to the left of the slider.
- * @param {number} y Pixel position relative to the top of the slider.
- * @return {number} The relative position of the thumb.
- * @private
- */
-ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) {
-  var amount;
-  if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) {
-    amount = x / this.widthLimit_;
-  } else {
-    amount = y / this.heightLimit_;
-  }
-  return ol.math.clamp(amount, 0, 1);
-};
-
-
-/**
- * Calculates the corresponding resolution of the thumb given its relative
- * position (where 0 is the minimum and 1 is the maximum).
- *
- * @param {number} position The relative position of the thumb.
- * @return {number} The corresponding resolution.
- * @private
- */
-ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) {
-  var fn = this.getMap().getView().getResolutionForValueFunction();
-  return fn(1 - position);
-};
-
-
-/**
- * Determines the relative position of the slider for the given resolution.  A
- * relative position of 0 corresponds to the minimum view resolution.  A
- * relative position of 1 corresponds to the maximum view resolution.
- *
- * @param {number} res The resolution.
- * @return {number} The relative position value (between 0 and 1).
- * @private
- */
-ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) {
-  var fn = this.getMap().getView().getValueForResolutionFunction();
-  return 1 - fn(res);
-};
-
-goog.provide('ol.control.ZoomToExtent');
-
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.control.Control');
-goog.require('ol.css');
-
-
-/**
- * @classdesc
- * A button control which, when pressed, changes the map view to a specific
- * extent. To style this control use the css selector `.ol-zoom-extent`.
- *
- * @constructor
- * @extends {ol.control.Control}
- * @param {olx.control.ZoomToExtentOptions=} opt_options Options.
- * @api stable
- */
-ol.control.ZoomToExtent = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @type {ol.Extent}
-   * @private
-   */
-  this.extent_ = options.extent ? options.extent : null;
-
-  var className = options.className !== undefined ? options.className :
-      'ol-zoom-extent';
-
-  var label = options.label !== undefined ? options.label : 'E';
-  var tipLabel = options.tipLabel !== undefined ?
-      options.tipLabel : 'Fit to extent';
-  var button = document.createElement('button');
-  button.setAttribute('type', 'button');
-  button.title = tipLabel;
-  button.appendChild(
-    typeof label === 'string' ? document.createTextNode(label) : label
-  );
-
-  ol.events.listen(button, ol.events.EventType.CLICK,
-      this.handleClick_, this);
-
-  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
-      ol.css.CLASS_CONTROL;
-  var element = document.createElement('div');
-  element.className = cssClasses;
-  element.appendChild(button);
-
-  ol.control.Control.call(this, {
-    element: element,
-    target: options.target
-  });
-};
-ol.inherits(ol.control.ZoomToExtent, ol.control.Control);
-
-
-/**
- * @param {Event} event The event to handle
- * @private
- */
-ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
-  event.preventDefault();
-  this.handleZoomToExtent_();
-};
-
-
-/**
- * @private
- */
-ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() {
-  var map = this.getMap();
-  var view = map.getView();
-  var extent = !this.extent_ ? view.getProjection().getExtent() : this.extent_;
-  var size = /** @type {ol.Size} */ (map.getSize());
-  view.fit(extent, size);
-};
-
-goog.provide('ol.DeviceOrientation');
-
-goog.require('ol.events');
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.has');
-goog.require('ol.math');
-
-
-/**
- * @classdesc
- * The ol.DeviceOrientation class provides access to information from
- * DeviceOrientation events.  See the [HTML 5 DeviceOrientation Specification](
- * http://www.w3.org/TR/orientation-event/) for more details.
- *
- * Many new computers, and especially mobile phones
- * and tablets, provide hardware support for device orientation. Web
- * developers targeting mobile devices will be especially interested in this
- * class.
- *
- * Device orientation data are relative to a common starting point. For mobile
- * devices, the starting point is to lay your phone face up on a table with the
- * top of the phone pointing north. This represents the zero state. All
- * angles are then relative to this state. For computers, it is the same except
- * the screen is open at 90 degrees.
- *
- * Device orientation is reported as three angles - `alpha`, `beta`, and
- * `gamma` - relative to the starting position along the three planar axes X, Y
- * and Z. The X axis runs from the left edge to the right edge through the
- * middle of the device. Similarly, the Y axis runs from the bottom to the top
- * of the device through the middle. The Z axis runs from the back to the front
- * through the middle. In the starting position, the X axis points to the
- * right, the Y axis points away from you and the Z axis points straight up
- * from the device lying flat.
- *
- * The three angles representing the device orientation are relative to the
- * three axes. `alpha` indicates how much the device has been rotated around the
- * Z axis, which is commonly interpreted as the compass heading (see note
- * below). `beta` indicates how much the device has been rotated around the X
- * axis, or how much it is tilted from front to back.  `gamma` indicates how
- * much the device has been rotated around the Y axis, or how much it is tilted
- * from left to right.
- *
- * For most browsers, the `alpha` value returns the compass heading so if the
- * device points north, it will be 0.  With Safari on iOS, the 0 value of
- * `alpha` is calculated from when device orientation was first requested.
- * ol.DeviceOrientation provides the `heading` property which normalizes this
- * behavior across all browsers for you.
- *
- * It is important to note that the HTML 5 DeviceOrientation specification
- * indicates that `alpha`, `beta` and `gamma` are in degrees while the
- * equivalent properties in ol.DeviceOrientation are in radians for consistency
- * with all other uses of angles throughout OpenLayers.
- *
- * To get notified of device orientation changes, register a listener for the
- * generic `change` event on your `ol.DeviceOrientation` instance.
- *
- * @see {@link http://www.w3.org/TR/orientation-event/}
- *
- * @constructor
- * @extends {ol.Object}
- * @param {olx.DeviceOrientationOptions=} opt_options Options.
- * @api
- */
-ol.DeviceOrientation = function(opt_options) {
-
-  ol.Object.call(this);
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.listenerKey_ = null;
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.DeviceOrientation.Property.TRACKING),
-      this.handleTrackingChanged_, this);
-
-  this.setTracking(options.tracking !== undefined ? options.tracking : false);
-
-};
-ol.inherits(ol.DeviceOrientation, ol.Object);
-
-
-/**
- * @inheritDoc
- */
-ol.DeviceOrientation.prototype.disposeInternal = function() {
-  this.setTracking(false);
-  ol.Object.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * @private
- * @param {Event} originalEvent Event.
- */
-ol.DeviceOrientation.prototype.orientationChange_ = function(originalEvent) {
-  var event = /** @type {DeviceOrientationEvent} */ (originalEvent);
-  if (event.alpha !== null) {
-    var alpha = ol.math.toRadians(event.alpha);
-    this.set(ol.DeviceOrientation.Property.ALPHA, alpha);
-    // event.absolute is undefined in iOS.
-    if (typeof event.absolute === 'boolean' && event.absolute) {
-      this.set(ol.DeviceOrientation.Property.HEADING, alpha);
-    } else if (typeof event.webkitCompassHeading === 'number' &&
-               event.webkitCompassAccuracy != -1) {
-      var heading = ol.math.toRadians(event.webkitCompassHeading);
-      this.set(ol.DeviceOrientation.Property.HEADING, heading);
-    }
-  }
-  if (event.beta !== null) {
-    this.set(ol.DeviceOrientation.Property.BETA,
-        ol.math.toRadians(event.beta));
-  }
-  if (event.gamma !== null) {
-    this.set(ol.DeviceOrientation.Property.GAMMA,
-        ol.math.toRadians(event.gamma));
-  }
-  this.changed();
-};
-
-
-/**
- * Rotation around the device z-axis (in radians).
- * @return {number|undefined} The euler angle in radians of the device from the
- *     standard Z axis.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.getAlpha = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property.ALPHA));
-};
-
-
-/**
- * Rotation around the device x-axis (in radians).
- * @return {number|undefined} The euler angle in radians of the device from the
- *     planar X axis.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.getBeta = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property.BETA));
-};
-
-
-/**
- * Rotation around the device y-axis (in radians).
- * @return {number|undefined} The euler angle in radians of the device from the
- *     planar Y axis.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.getGamma = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property.GAMMA));
-};
-
-
-/**
- * The heading of the device relative to north (in radians).
- * @return {number|undefined} The heading of the device relative to north, in
- *     radians, normalizing for different browser behavior.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.getHeading = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.DeviceOrientation.Property.HEADING));
-};
-
-
-/**
- * Determine if orientation is being tracked.
- * @return {boolean} Changes in device orientation are being tracked.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.getTracking = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.DeviceOrientation.Property.TRACKING));
-};
-
-
-/**
- * @private
- */
-ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() {
-  if (ol.has.DEVICE_ORIENTATION) {
-    var tracking = this.getTracking();
-    if (tracking && !this.listenerKey_) {
-      this.listenerKey_ = ol.events.listen(window, 'deviceorientation',
-          this.orientationChange_, this);
-    } else if (!tracking && this.listenerKey_ !== null) {
-      ol.events.unlistenByKey(this.listenerKey_);
-      this.listenerKey_ = null;
-    }
-  }
-};
-
-
-/**
- * Enable or disable tracking of device orientation events.
- * @param {boolean} tracking The status of tracking changes to alpha, beta and
- *     gamma. If true, changes are tracked and reported immediately.
- * @observable
- * @api
- */
-ol.DeviceOrientation.prototype.setTracking = function(tracking) {
-  this.set(ol.DeviceOrientation.Property.TRACKING, tracking);
-};
-
-
-/**
- * @enum {string}
- */
-ol.DeviceOrientation.Property = {
-  ALPHA: 'alpha',
-  BETA: 'beta',
-  GAMMA: 'gamma',
-  HEADING: 'heading',
-  TRACKING: 'tracking'
-};
-
-goog.provide('ol.Feature');
-
-goog.require('ol.asserts');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.geom.Geometry');
-goog.require('ol.style.Style');
-
-
-/**
- * @classdesc
- * A vector object for geographic features with a geometry and other
- * attribute properties, similar to the features in vector file formats like
- * GeoJSON.
- *
- * Features can be styled individually with `setStyle`; otherwise they use the
- * style of their vector layer.
- *
- * Note that attribute properties are set as {@link ol.Object} properties on
- * the feature object, so they are observable, and have get/set accessors.
- *
- * Typically, a feature has a single geometry property. You can set the
- * geometry using the `setGeometry` method and get it with `getGeometry`.
- * It is possible to store more than one geometry on a feature using attribute
- * properties. By default, the geometry used for rendering is identified by
- * the property name `geometry`. If you want to use another geometry property
- * for rendering, use the `setGeometryName` method to change the attribute
- * property associated with the geometry for the feature.  For example:
- *
- * ```js
- * var feature = new ol.Feature({
- *   geometry: new ol.geom.Polygon(polyCoords),
- *   labelPoint: new ol.geom.Point(labelCoords),
- *   name: 'My Polygon'
- * });
- *
- * // get the polygon geometry
- * var poly = feature.getGeometry();
- *
- * // Render the feature as a point using the coordinates from labelPoint
- * feature.setGeometryName('labelPoint');
- *
- * // get the point geometry
- * var point = feature.getGeometry();
- * ```
- *
- * @constructor
- * @extends {ol.Object}
- * @param {ol.geom.Geometry|Object.<string, *>=} opt_geometryOrProperties
- *     You may pass a Geometry object directly, or an object literal
- *     containing properties.  If you pass an object literal, you may
- *     include a Geometry associated with a `geometry` key.
- * @api stable
- */
-ol.Feature = function(opt_geometryOrProperties) {
-
-  ol.Object.call(this);
-
-  /**
-   * @private
-   * @type {number|string|undefined}
-   */
-  this.id_ = undefined;
-
-  /**
-   * @type {string}
-   * @private
-   */
-  this.geometryName_ = 'geometry';
-
-  /**
-   * User provided style.
-   * @private
-   * @type {ol.style.Style|Array.<ol.style.Style>|
-   *     ol.FeatureStyleFunction}
-   */
-  this.style_ = null;
-
-  /**
-   * @private
-   * @type {ol.FeatureStyleFunction|undefined}
-   */
-  this.styleFunction_ = undefined;
-
-  /**
-   * @private
-   * @type {?ol.EventsKey}
-   */
-  this.geometryChangeKey_ = null;
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(this.geometryName_),
-      this.handleGeometryChanged_, this);
-
-  if (opt_geometryOrProperties !== undefined) {
-    if (opt_geometryOrProperties instanceof ol.geom.Geometry ||
-        !opt_geometryOrProperties) {
-      var geometry = opt_geometryOrProperties;
-      this.setGeometry(geometry);
-    } else {
-      /** @type {Object.<string, *>} */
-      var properties = opt_geometryOrProperties;
-      this.setProperties(properties);
-    }
-  }
-};
-ol.inherits(ol.Feature, ol.Object);
-
-
-/**
- * Clone this feature. If the original feature has a geometry it
- * is also cloned. The feature id is not set in the clone.
- * @return {ol.Feature} The clone.
- * @api stable
- */
-ol.Feature.prototype.clone = function() {
-  var clone = new ol.Feature(this.getProperties());
-  clone.setGeometryName(this.getGeometryName());
-  var geometry = this.getGeometry();
-  if (geometry) {
-    clone.setGeometry(geometry.clone());
-  }
-  var style = this.getStyle();
-  if (style) {
-    clone.setStyle(style);
-  }
-  return clone;
-};
-
-
-/**
- * Get the feature's default geometry.  A feature may have any number of named
- * geometries.  The "default" geometry (the one that is rendered by default) is
- * set when calling {@link ol.Feature#setGeometry}.
- * @return {ol.geom.Geometry|undefined} The default geometry for the feature.
- * @api stable
- * @observable
- */
-ol.Feature.prototype.getGeometry = function() {
-  return /** @type {ol.geom.Geometry|undefined} */ (
-      this.get(this.geometryName_));
-};
-
-
-/**
- * Get the feature identifier.  This is a stable identifier for the feature and
- * is either set when reading data from a remote source or set explicitly by
- * calling {@link ol.Feature#setId}.
- * @return {number|string|undefined} Id.
- * @api stable
- * @observable
- */
-ol.Feature.prototype.getId = function() {
-  return this.id_;
-};
-
-
-/**
- * Get the name of the feature's default geometry.  By default, the default
- * geometry is named `geometry`.
- * @return {string} Get the property name associated with the default geometry
- *     for this feature.
- * @api stable
- */
-ol.Feature.prototype.getGeometryName = function() {
-  return this.geometryName_;
-};
-
-
-/**
- * Get the feature's style. Will return what was provided to the
- * {@link ol.Feature#setStyle} method.
- * @return {ol.style.Style|Array.<ol.style.Style>|
- *     ol.FeatureStyleFunction} The feature style.
- * @api stable
- * @observable
- */
-ol.Feature.prototype.getStyle = function() {
-  return this.style_;
-};
-
-
-/**
- * Get the feature's style function.
- * @return {ol.FeatureStyleFunction|undefined} Return a function
- * representing the current style of this feature.
- * @api stable
- */
-ol.Feature.prototype.getStyleFunction = function() {
-  return this.styleFunction_;
-};
-
-
-/**
- * @private
- */
-ol.Feature.prototype.handleGeometryChange_ = function() {
-  this.changed();
-};
-
-
-/**
- * @private
- */
-ol.Feature.prototype.handleGeometryChanged_ = function() {
-  if (this.geometryChangeKey_) {
-    ol.events.unlistenByKey(this.geometryChangeKey_);
-    this.geometryChangeKey_ = null;
-  }
-  var geometry = this.getGeometry();
-  if (geometry) {
-    this.geometryChangeKey_ = ol.events.listen(geometry,
-        ol.events.EventType.CHANGE, this.handleGeometryChange_, this);
-  }
-  this.changed();
-};
-
-
-/**
- * Set the default geometry for the feature.  This will update the property
- * with the name returned by {@link ol.Feature#getGeometryName}.
- * @param {ol.geom.Geometry|undefined} geometry The new geometry.
- * @api stable
- * @observable
- */
-ol.Feature.prototype.setGeometry = function(geometry) {
-  this.set(this.geometryName_, geometry);
-};
-
-
-/**
- * Set the style for the feature.  This can be a single style object, an array
- * of styles, or a function that takes a resolution and returns an array of
- * styles. If it is `null` the feature has no style (a `null` style).
- * @param {ol.style.Style|Array.<ol.style.Style>|
- *     ol.FeatureStyleFunction} style Style for this feature.
- * @api stable
- * @observable
- */
-ol.Feature.prototype.setStyle = function(style) {
-  this.style_ = style;
-  this.styleFunction_ = !style ?
-      undefined : ol.Feature.createStyleFunction(style);
-  this.changed();
-};
-
-
-/**
- * Set the feature id.  The feature id is considered stable and may be used when
- * requesting features or comparing identifiers returned from a remote source.
- * The feature id can be used with the {@link ol.source.Vector#getFeatureById}
- * method.
- * @param {number|string|undefined} id The feature id.
- * @api stable
- * @observable
- */
-ol.Feature.prototype.setId = function(id) {
-  this.id_ = id;
-  this.changed();
-};
-
-
-/**
- * Set the property name to be used when getting the feature's default geometry.
- * When calling {@link ol.Feature#getGeometry}, the value of the property with
- * this name will be returned.
- * @param {string} name The property name of the default geometry.
- * @api stable
- */
-ol.Feature.prototype.setGeometryName = function(name) {
-  ol.events.unlisten(
-      this, ol.Object.getChangeEventType(this.geometryName_),
-      this.handleGeometryChanged_, this);
-  this.geometryName_ = name;
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(this.geometryName_),
-      this.handleGeometryChanged_, this);
-  this.handleGeometryChanged_();
-};
-
-
-/**
- * Convert the provided object into a feature style function.  Functions passed
- * through unchanged.  Arrays of ol.style.Style or single style objects wrapped
- * in a new feature style function.
- * @param {ol.FeatureStyleFunction|!Array.<ol.style.Style>|!ol.style.Style} obj
- *     A feature style function, a single style, or an array of styles.
- * @return {ol.FeatureStyleFunction} A style function.
- */
-ol.Feature.createStyleFunction = function(obj) {
-  var styleFunction;
-
-  if (typeof obj === 'function') {
-    styleFunction = obj;
-  } else {
-    /**
-     * @type {Array.<ol.style.Style>}
-     */
-    var styles;
-    if (Array.isArray(obj)) {
-      styles = obj;
-    } else {
-      ol.asserts.assert(obj instanceof ol.style.Style,
-          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
-      styles = [obj];
-    }
-    styleFunction = function() {
-      return styles;
-    };
-  }
-  return styleFunction;
-};
-
-goog.provide('ol.format.FormatType');
-
-
-/**
- * @enum {string}
- */
-ol.format.FormatType = {
-  ARRAY_BUFFER: 'arraybuffer',
-  JSON: 'json',
-  TEXT: 'text',
-  XML: 'xml'
-};
-
-goog.provide('ol.xml');
-
-goog.require('ol');
-goog.require('ol.array');
-
-
-/**
- * This document should be used when creating nodes for XML serializations. This
- * document is also used by {@link ol.xml.createElementNS} and
- * {@link ol.xml.setAttributeNS}
- * @const
- * @type {Document}
- */
-ol.xml.DOCUMENT = document.implementation.createDocument('', '', null);
-
-
-/**
- * @param {string} namespaceURI Namespace URI.
- * @param {string} qualifiedName Qualified name.
- * @return {Node} Node.
- */
-ol.xml.createElementNS = function(namespaceURI, qualifiedName) {
-  return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName);
-};
-
-
-/**
- * Recursively grab all text content of child nodes into a single string.
- * @param {Node} node Node.
- * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
- * breaks.
- * @return {string} All text content.
- * @api
- */
-ol.xml.getAllTextContent = function(node, normalizeWhitespace) {
-  return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join('');
-};
-
-
-/**
- * Recursively grab all text content of child nodes into a single string.
- * @param {Node} node Node.
- * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
- * breaks.
- * @param {Array.<string>} accumulator Accumulator.
- * @private
- * @return {Array.<string>} Accumulator.
- */
-ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) {
-  if (node.nodeType == Node.CDATA_SECTION_NODE ||
-      node.nodeType == Node.TEXT_NODE) {
-    if (normalizeWhitespace) {
-      accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
-    } else {
-      accumulator.push(node.nodeValue);
-    }
-  } else {
-    var n;
-    for (n = node.firstChild; n; n = n.nextSibling) {
-      ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator);
-    }
-  }
-  return accumulator;
-};
-
-
-/**
- * @param {?} value Value.
- * @return {boolean} Is document.
- */
-ol.xml.isDocument = function(value) {
-  return value instanceof Document;
-};
-
-
-/**
- * @param {?} value Value.
- * @return {boolean} Is node.
- */
-ol.xml.isNode = function(value) {
-  return value instanceof Node;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @return {string} Value
- */
-ol.xml.getAttributeNS = function(node, namespaceURI, name) {
-  return node.getAttributeNS(namespaceURI, name) || '';
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {?string} namespaceURI Namespace URI.
- * @param {string} name Attribute name.
- * @param {string|number} value Value.
- */
-ol.xml.setAttributeNS = function(node, namespaceURI, name, value) {
-  node.setAttributeNS(namespaceURI, name, value);
-};
-
-
-/**
- * Parse an XML string to an XML Document.
- * @param {string} xml XML.
- * @return {Document} Document.
- * @api
- */
-ol.xml.parse = function(xml) {
-  return new DOMParser().parseFromString(xml, 'application/xml');
-};
-
-
-/**
- * Make an array extender function for extending the array at the top of the
- * object stack.
- * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)}
- *     valueReader Value reader.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.XmlParser} Parser.
- * @template T
- */
-ol.xml.makeArrayExtender = function(valueReader, opt_this) {
-  return (
-      /**
-       * @param {Node} node Node.
-       * @param {Array.<*>} objectStack Object stack.
-       */
-      function(node, objectStack) {
-        var value = valueReader.call(opt_this, node, objectStack);
-        if (value !== undefined) {
-          ol.DEBUG && console.assert(Array.isArray(value),
-              'valueReader function is expected to return an array of values');
-          var array = /** @type {Array.<*>} */
-              (objectStack[objectStack.length - 1]);
-          ol.DEBUG && console.assert(Array.isArray(array),
-              'objectStack is supposed to be an array of arrays');
-          ol.array.extend(array, value);
-        }
-      });
-};
-
-
-/**
- * Make an array pusher function for pushing to the array at the top of the
- * object stack.
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.XmlParser} Parser.
- * @template T
- */
-ol.xml.makeArrayPusher = function(valueReader, opt_this) {
-  return (
-      /**
-       * @param {Node} node Node.
-       * @param {Array.<*>} objectStack Object stack.
-       */
-      function(node, objectStack) {
-        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
-            node, objectStack);
-        if (value !== undefined) {
-          var array = objectStack[objectStack.length - 1];
-          ol.DEBUG && console.assert(Array.isArray(array),
-              'objectStack is supposed to be an array of arrays');
-          array.push(value);
-        }
-      });
-};
-
-
-/**
- * Make an object stack replacer function for replacing the object at the
- * top of the stack.
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.XmlParser} Parser.
- * @template T
- */
-ol.xml.makeReplacer = function(valueReader, opt_this) {
-  return (
-      /**
-       * @param {Node} node Node.
-       * @param {Array.<*>} objectStack Object stack.
-       */
-      function(node, objectStack) {
-        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
-            node, objectStack);
-        if (value !== undefined) {
-          objectStack[objectStack.length - 1] = value;
-        }
-      });
-};
-
-
-/**
- * Make an object property pusher function for adding a property to the
- * object at the top of the stack.
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {string=} opt_property Property.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.XmlParser} Parser.
- * @template T
- */
-ol.xml.makeObjectPropertyPusher = function(valueReader, opt_property, opt_this) {
-  ol.DEBUG && console.assert(valueReader !== undefined,
-      'undefined valueReader, expected function(this: T, Node, Array.<*>)');
-  return (
-      /**
-       * @param {Node} node Node.
-       * @param {Array.<*>} objectStack Object stack.
-       */
-      function(node, objectStack) {
-        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
-            node, objectStack);
-        if (value !== undefined) {
-          var object = /** @type {Object} */
-              (objectStack[objectStack.length - 1]);
-          var property = opt_property !== undefined ?
-              opt_property : node.localName;
-          var array;
-          if (property in object) {
-            array = object[property];
-          } else {
-            array = object[property] = [];
-          }
-          array.push(value);
-        }
-      });
-};
-
-
-/**
- * Make an object property setter function.
- * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
- * @param {string=} opt_property Property.
- * @param {T=} opt_this The object to use as `this` in `valueReader`.
- * @return {ol.XmlParser} Parser.
- * @template T
- */
-ol.xml.makeObjectPropertySetter = function(valueReader, opt_property, opt_this) {
-  ol.DEBUG && console.assert(valueReader !== undefined,
-      'undefined valueReader, expected function(this: T, Node, Array.<*>)');
-  return (
-      /**
-       * @param {Node} node Node.
-       * @param {Array.<*>} objectStack Object stack.
-       */
-      function(node, objectStack) {
-        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
-            node, objectStack);
-        if (value !== undefined) {
-          var object = /** @type {Object} */
-              (objectStack[objectStack.length - 1]);
-          var property = opt_property !== undefined ?
-              opt_property : node.localName;
-          object[property] = value;
-        }
-      });
-};
-
-
-/**
- * Create a serializer that appends nodes written by its `nodeWriter` to its
- * designated parent. The parent is the `node` of the
- * {@link ol.XmlNodeStackItem} at the top of the `objectStack`.
- * @param {function(this: T, Node, V, Array.<*>)}
- *     nodeWriter Node writer.
- * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
- * @return {ol.XmlSerializer} Serializer.
- * @template T, V
- */
-ol.xml.makeChildAppender = function(nodeWriter, opt_this) {
-  return function(node, value, objectStack) {
-    nodeWriter.call(opt_this !== undefined ? opt_this : this,
-        node, value, objectStack);
-    var parent = objectStack[objectStack.length - 1];
-    var parentNode = parent.node;
-    ol.DEBUG && console.assert(ol.xml.isNode(parentNode) ||
-        ol.xml.isDocument(parentNode),
-        'expected parentNode %s to be a Node or a Document', parentNode);
-    parentNode.appendChild(node);
-  };
-};
-
-
-/**
- * Create a serializer that calls the provided `nodeWriter` from
- * {@link ol.xml.serialize}. This can be used by the parent writer to have the
- * 'nodeWriter' called with an array of values when the `nodeWriter` was
- * designed to serialize a single item. An example would be a LineString
- * geometry writer, which could be reused for writing MultiLineString
- * geometries.
- * @param {function(this: T, Node, V, Array.<*>)}
- *     nodeWriter Node writer.
- * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
- * @return {ol.XmlSerializer} Serializer.
- * @template T, V
- */
-ol.xml.makeArraySerializer = function(nodeWriter, opt_this) {
-  var serializersNS, nodeFactory;
-  return function(node, value, objectStack) {
-    if (serializersNS === undefined) {
-      serializersNS = {};
-      var serializers = {};
-      serializers[node.localName] = nodeWriter;
-      serializersNS[node.namespaceURI] = serializers;
-      nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName);
-    }
-    ol.xml.serialize(serializersNS, nodeFactory, value, objectStack);
-  };
-};
-
-
-/**
- * Create a node factory which can use the `opt_keys` passed to
- * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names,
- * or a fixed node name. The namespace of the created nodes can either be fixed,
- * or the parent namespace will be used.
- * @param {string=} opt_nodeName Fixed node name which will be used for all
- *     created nodes. If not provided, the 3rd argument to the resulting node
- *     factory needs to be provided and will be the nodeName.
- * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for
- *     all created nodes. If not provided, the namespace of the parent node will
- *     be used.
- * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory.
- */
-ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) {
-  var fixedNodeName = opt_nodeName;
-  return (
-      /**
-       * @param {*} value Value.
-       * @param {Array.<*>} objectStack Object stack.
-       * @param {string=} opt_nodeName Node name.
-       * @return {Node} Node.
-       */
-      function(value, objectStack, opt_nodeName) {
-        var context = objectStack[objectStack.length - 1];
-        var node = context.node;
-        ol.DEBUG && console.assert(ol.xml.isNode(node) || ol.xml.isDocument(node),
-            'expected node %s to be a Node or a Document', node);
-        var nodeName = fixedNodeName;
-        if (nodeName === undefined) {
-          nodeName = opt_nodeName;
-        }
-        var namespaceURI = opt_namespaceURI;
-        if (opt_namespaceURI === undefined) {
-          namespaceURI = node.namespaceURI;
-        }
-        ol.DEBUG && console.assert(nodeName !== undefined, 'nodeName was undefined');
-        return ol.xml.createElementNS(namespaceURI, /** @type {string} */ (nodeName));
-      }
-  );
-};
-
-
-/**
- * A node factory that creates a node using the parent's `namespaceURI` and the
- * `nodeName` passed by {@link ol.xml.serialize} or
- * {@link ol.xml.pushSerializeAndPop} to the node factory.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- */
-ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory();
-
-
-/**
- * Create an array of `values` to be used with {@link ol.xml.serialize} or
- * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as
- * `opt_key` argument.
- * @param {Object.<string, V>} object Key-value pairs for the sequence. Keys can
- *     be a subset of the `orderedKeys`.
- * @param {Array.<string>} orderedKeys Keys in the order of the sequence.
- * @return {Array.<V>} Values in the order of the sequence. The resulting array
- *     has the same length as the `orderedKeys` array. Values that are not
- *     present in `object` will be `undefined` in the resulting array.
- * @template V
- */
-ol.xml.makeSequence = function(object, orderedKeys) {
-  var length = orderedKeys.length;
-  var sequence = new Array(length);
-  for (var i = 0; i < length; ++i) {
-    sequence[i] = object[orderedKeys[i]];
-  }
-  return sequence;
-};
-
-
-/**
- * Create a namespaced structure, using the same values for each namespace.
- * This can be used as a starting point for versioned parsers, when only a few
- * values are version specific.
- * @param {Array.<string>} namespaceURIs Namespace URIs.
- * @param {T} structure Structure.
- * @param {Object.<string, T>=} opt_structureNS Namespaced structure to add to.
- * @return {Object.<string, T>} Namespaced structure.
- * @template T
- */
-ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) {
-  /**
-   * @type {Object.<string, *>}
-   */
-  var structureNS = opt_structureNS !== undefined ? opt_structureNS : {};
-  var i, ii;
-  for (i = 0, ii = namespaceURIs.length; i < ii; ++i) {
-    structureNS[namespaceURIs[i]] = structure;
-  }
-  return structureNS;
-};
-
-
-/**
- * Parse a node using the parsers and object stack.
- * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS
- *     Parsers by namespace.
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @param {*=} opt_this The object to use as `this`.
- */
-ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) {
-  var n;
-  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
-    var parsers = parsersNS[n.namespaceURI];
-    if (parsers !== undefined) {
-      var parser = parsers[n.localName];
-      if (parser !== undefined) {
-        parser.call(opt_this, n, objectStack);
-      }
-    }
-  }
-};
-
-
-/**
- * Push an object on top of the stack, parse and return the popped object.
- * @param {T} object Object.
- * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS
- *     Parsers by namespace.
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @param {*=} opt_this The object to use as `this`.
- * @return {T} Object.
- * @template T
- */
-ol.xml.pushParseAndPop = function(
-    object, parsersNS, node, objectStack, opt_this) {
-  objectStack.push(object);
-  ol.xml.parseNode(parsersNS, node, objectStack, opt_this);
-  return objectStack.pop();
-};
-
-
-/**
- * Walk through an array of `values` and call a serializer for each value.
- * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS
- *     Namespaced serializers.
- * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
- *     Node factory. The `nodeFactory` creates the node whose namespace and name
- *     will be used to choose a node writer from `serializersNS`. This
- *     separation allows us to decide what kind of node to create, depending on
- *     the value we want to serialize. An example for this would be different
- *     geometry writers based on the geometry type.
- * @param {Array.<*>} values Values to serialize. An example would be an array
- *     of {@link ol.Feature} instances.
- * @param {Array.<*>} objectStack Node stack.
- * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
- *     `nodeFactory`. This is used for serializing object literals where the
- *     node name relates to the property key. The array length of `opt_keys` has
- *     to match the length of `values`. For serializing a sequence, `opt_keys`
- *     determines the order of the sequence.
- * @param {T=} opt_this The object to use as `this` for the node factory and
- *     serializers.
- * @template T
- */
-ol.xml.serialize = function(
-    serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
-  var length = (opt_keys !== undefined ? opt_keys : values).length;
-  var value, node;
-  for (var i = 0; i < length; ++i) {
-    value = values[i];
-    if (value !== undefined) {
-      node = nodeFactory.call(opt_this, value, objectStack,
-          opt_keys !== undefined ? opt_keys[i] : undefined);
-      if (node !== undefined) {
-        serializersNS[node.namespaceURI][node.localName]
-            .call(opt_this, node, value, objectStack);
-      }
-    }
-  }
-};
-
-
-/**
- * @param {O} object Object.
- * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS
- *     Namespaced serializers.
- * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
- *     Node factory. The `nodeFactory` creates the node whose namespace and name
- *     will be used to choose a node writer from `serializersNS`. This
- *     separation allows us to decide what kind of node to create, depending on
- *     the value we want to serialize. An example for this would be different
- *     geometry writers based on the geometry type.
- * @param {Array.<*>} values Values to serialize. An example would be an array
- *     of {@link ol.Feature} instances.
- * @param {Array.<*>} objectStack Node stack.
- * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
- *     `nodeFactory`. This is used for serializing object literals where the
- *     node name relates to the property key. The array length of `opt_keys` has
- *     to match the length of `values`. For serializing a sequence, `opt_keys`
- *     determines the order of the sequence.
- * @param {T=} opt_this The object to use as `this` for the node factory and
- *     serializers.
- * @return {O|undefined} Object.
- * @template O, T
- */
-ol.xml.pushSerializeAndPop = function(object,
-    serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
-  objectStack.push(object);
-  ol.xml.serialize(
-      serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this);
-  return objectStack.pop();
-};
-
-goog.provide('ol.featureloader');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.format.FormatType');
-goog.require('ol.xml');
-
-
-/**
- * @param {string|ol.FeatureUrlFunction} url Feature URL service.
- * @param {ol.format.Feature} format Feature format.
- * @param {function(this:ol.VectorTile, Array.<ol.Feature>, ol.proj.Projection)|function(this:ol.source.Vector, Array.<ol.Feature>)} success
- *     Function called with the loaded features and optionally with the data
- *     projection. Called with the vector tile or source as `this`.
- * @param {function(this:ol.VectorTile)|function(this:ol.source.Vector)} failure
- *     Function called when loading failed. Called with the vector tile or
- *     source as `this`.
- * @return {ol.FeatureLoader} The feature loader.
- */
-ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) {
-  return (
-      /**
-       * @param {ol.Extent} extent Extent.
-       * @param {number} resolution Resolution.
-       * @param {ol.proj.Projection} projection Projection.
-       * @this {ol.source.Vector|ol.VectorTile}
-       */
-      function(extent, resolution, projection) {
-        var xhr = new XMLHttpRequest();
-        xhr.open('GET',
-            typeof url === 'function' ? url(extent, resolution, projection) : url,
-            true);
-        if (format.getType() == ol.format.FormatType.ARRAY_BUFFER) {
-          xhr.responseType = 'arraybuffer';
-        }
-        /**
-         * @param {Event} event Event.
-         * @private
-         */
-        xhr.onload = function(event) {
-          // status will be 0 for file:// urls
-          if (!xhr.status || xhr.status >= 200 && xhr.status < 300) {
-            var type = format.getType();
-            /** @type {Document|Node|Object|string|undefined} */
-            var source;
-            if (type == ol.format.FormatType.JSON ||
-                type == ol.format.FormatType.TEXT) {
-              source = xhr.responseText;
-            } else if (type == ol.format.FormatType.XML) {
-              source = xhr.responseXML;
-              if (!source) {
-                source = ol.xml.parse(xhr.responseText);
-              }
-            } else if (type == ol.format.FormatType.ARRAY_BUFFER) {
-              source = /** @type {ArrayBuffer} */ (xhr.response);
-            }
-            if (source) {
-              success.call(this, format.readFeatures(source,
-                  {featureProjection: projection}),
-                  format.readProjection(source));
-            } else {
-              failure.call(this);
-            }
-          } else {
-            failure.call(this);
-          }
-        }.bind(this);
-        xhr.send();
-      });
-};
-
-
-/**
- * Create an XHR feature loader for a `url` and `format`. The feature loader
- * loads features (with XHR), parses the features, and adds them to the
- * vector tile.
- * @param {string|ol.FeatureUrlFunction} url Feature URL service.
- * @param {ol.format.Feature} format Feature format.
- * @return {ol.FeatureLoader} The feature loader.
- * @api
- */
-ol.featureloader.tile = function(url, format) {
-  return ol.featureloader.loadFeaturesXhr(url, format,
-      /**
-       * @param {Array.<ol.Feature>} features The loaded features.
-       * @param {ol.proj.Projection} dataProjection Data projection.
-       * @this {ol.VectorTile}
-       */
-      function(features, dataProjection) {
-        this.setProjection(dataProjection);
-        this.setFeatures(features);
-      },
-      /**
-       * @this {ol.VectorTile}
-       */
-      function() {
-        this.setState(ol.Tile.State.ERROR);
-      });
-};
-
-
-/**
- * Create an XHR feature loader for a `url` and `format`. The feature loader
- * loads features (with XHR), parses the features, and adds them to the
- * vector source.
- * @param {string|ol.FeatureUrlFunction} url Feature URL service.
- * @param {ol.format.Feature} format Feature format.
- * @return {ol.FeatureLoader} The feature loader.
- * @api
- */
-ol.featureloader.xhr = function(url, format) {
-  return ol.featureloader.loadFeaturesXhr(url, format,
-      /**
-       * @param {Array.<ol.Feature>} features The loaded features.
-       * @param {ol.proj.Projection} dataProjection Data projection.
-       * @this {ol.source.Vector}
-       */
-      function(features, dataProjection) {
-        this.addFeatures(features);
-      }, /* FIXME handle error */ ol.nullFunction);
-};
-
-goog.provide('ol.format.Feature');
-
-goog.require('ol.geom.Geometry');
-goog.require('ol.obj');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for feature formats.
- * {ol.format.Feature} subclasses provide the ability to decode and encode
- * {@link ol.Feature} objects from a variety of commonly used geospatial
- * file formats.  See the documentation for each format for more details.
- *
- * @constructor
- * @api stable
- */
-ol.format.Feature = function() {
-
-  /**
-   * @protected
-   * @type {ol.proj.Projection}
-   */
-  this.defaultDataProjection = null;
-
-  /**
-   * @protected
-   * @type {ol.proj.Projection}
-   */
-  this.defaultFeatureProjection = null;
-
-};
-
-
-/**
- * @abstract
- * @return {Array.<string>} Extensions.
- */
-ol.format.Feature.prototype.getExtensions = function() {};
-
-
-/**
- * Adds the data projection to the read options.
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {olx.format.ReadOptions|undefined} Options.
- * @protected
- */
-ol.format.Feature.prototype.getReadOptions = function(source, opt_options) {
-  var options;
-  if (opt_options) {
-    options = {
-      dataProjection: opt_options.dataProjection ?
-          opt_options.dataProjection : this.readProjection(source),
-      featureProjection: opt_options.featureProjection
-    };
-  }
-  return this.adaptOptions(options);
-};
-
-
-/**
- * Sets the `defaultDataProjection` on the options, if no `dataProjection`
- * is set.
- * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options
- *     Options.
- * @protected
- * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined}
- *     Updated options.
- */
-ol.format.Feature.prototype.adaptOptions = function(options) {
-  return ol.obj.assign({
-    dataProjection: this.defaultDataProjection,
-    featureProjection: this.defaultFeatureProjection
-  }, options);
-};
-
-
-/**
- * @abstract
- * @return {ol.format.FormatType} Format.
- */
-ol.format.Feature.prototype.getType = function() {};
-
-
-/**
- * Read a single feature from a source.
- *
- * @abstract
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- */
-ol.format.Feature.prototype.readFeature = function(source, opt_options) {};
-
-
-/**
- * Read all features from a source.
- *
- * @abstract
- * @param {Document|Node|ArrayBuffer|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.Feature.prototype.readFeatures = function(source, opt_options) {};
-
-
-/**
- * Read a single geometry from a source.
- *
- * @abstract
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.Feature.prototype.readGeometry = function(source, opt_options) {};
-
-
-/**
- * Read the projection from a source.
- *
- * @abstract
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.Feature.prototype.readProjection = function(source) {};
-
-
-/**
- * Encode a feature in this format.
- *
- * @abstract
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Result.
- */
-ol.format.Feature.prototype.writeFeature = function(feature, opt_options) {};
-
-
-/**
- * Encode an array of features in this format.
- *
- * @abstract
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Result.
- */
-ol.format.Feature.prototype.writeFeatures = function(features, opt_options) {};
-
-
-/**
- * Write a single geometry in this format.
- *
- * @abstract
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Result.
- */
-ol.format.Feature.prototype.writeGeometry = function(geometry, opt_options) {};
-
-
-/**
- * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
- * @param {boolean} write Set to true for writing, false for reading.
- * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options
- *     Options.
- * @return {ol.geom.Geometry|ol.Extent} Transformed geometry.
- * @protected
- */
-ol.format.Feature.transformWithOptions = function(
-    geometry, write, opt_options) {
-  var featureProjection = opt_options ?
-      ol.proj.get(opt_options.featureProjection) : null;
-  var dataProjection = opt_options ?
-      ol.proj.get(opt_options.dataProjection) : null;
-  /**
-   * @type {ol.geom.Geometry|ol.Extent}
-   */
-  var transformed;
-  if (featureProjection && dataProjection &&
-      !ol.proj.equivalent(featureProjection, dataProjection)) {
-    if (geometry instanceof ol.geom.Geometry) {
-      transformed = (write ? geometry.clone() : geometry).transform(
-          write ? featureProjection : dataProjection,
-          write ? dataProjection : featureProjection);
-    } else {
-      // FIXME this is necessary because ol.format.GML treats extents
-      // as geometries
-      transformed = ol.proj.transformExtent(
-          write ? geometry.slice() : geometry,
-          write ? featureProjection : dataProjection,
-          write ? dataProjection : featureProjection);
-    }
-  } else {
-    transformed = geometry;
-  }
-  if (write && opt_options && opt_options.decimals) {
-    var power = Math.pow(10, opt_options.decimals);
-    // if decimals option on write, round each coordinate appropriately
-    /**
-     * @param {Array.<number>} coordinates Coordinates.
-     * @return {Array.<number>} Transformed coordinates.
-     */
-    var transform = function(coordinates) {
-      for (var i = 0, ii = coordinates.length; i < ii; ++i) {
-        coordinates[i] = Math.round(coordinates[i] * power) / power;
-      }
-      return coordinates;
-    };
-    if (Array.isArray(transformed)) {
-      transform(transformed);
-    } else {
-      transformed.applyTransform(transform);
-    }
-  }
-  return transformed;
-};
-
-goog.provide('ol.format.JSONFeature');
-
-goog.require('ol');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for JSON feature formats.
- *
- * @constructor
- * @extends {ol.format.Feature}
- */
-ol.format.JSONFeature = function() {
-  ol.format.Feature.call(this);
-};
-ol.inherits(ol.format.JSONFeature, ol.format.Feature);
-
-
-/**
- * @param {Document|Node|Object|string} source Source.
- * @private
- * @return {Object} Object.
- */
-ol.format.JSONFeature.prototype.getObject_ = function(source) {
-  if (typeof source === 'string') {
-    var object = JSON.parse(source);
-    return object ? /** @type {Object} */ (object) : null;
-  } else if (source !== null) {
-    return source;
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.getType = function() {
-  return ol.format.FormatType.JSON;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) {
-  return this.readFeatureFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) {
-  return this.readFeaturesFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
-};
-
-
-/**
- * @abstract
- * @param {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.Feature} Feature.
- */
-ol.format.JSONFeature.prototype.readFeatureFromObject = function(object, opt_options) {};
-
-
-/**
- * @abstract
- * @param {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.JSONFeature.prototype.readFeaturesFromObject = function(object, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) {
-  return this.readGeometryFromObject(
-      this.getObject_(source), this.getReadOptions(source, opt_options));
-};
-
-
-/**
- * @abstract
- * @param {Object} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.JSONFeature.prototype.readGeometryFromObject = function(object, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.readProjection = function(source) {
-  return this.readProjectionFromObject(this.getObject_(source));
-};
-
-
-/**
- * @abstract
- * @param {Object} object Object.
- * @protected
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.JSONFeature.prototype.readProjectionFromObject = function(object) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) {
-  return JSON.stringify(this.writeFeatureObject(feature, opt_options));
-};
-
-
-/**
- * @abstract
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
- */
-ol.format.JSONFeature.prototype.writeFeatureObject = function(feature, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.writeFeatures = function(features, opt_options) {
-  return JSON.stringify(this.writeFeaturesObject(features, opt_options));
-};
-
-
-/**
- * @abstract
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
- */
-ol.format.JSONFeature.prototype.writeFeaturesObject = function(features, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.JSONFeature.prototype.writeGeometry = function(geometry, opt_options) {
-  return JSON.stringify(this.writeGeometryObject(geometry, opt_options));
-};
-
-
-/**
- * @abstract
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
- */
-ol.format.JSONFeature.prototype.writeGeometryObject = function(geometry, opt_options) {};
-
-goog.provide('ol.geom.flat.interpolate');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.math');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} fraction Fraction.
- * @param {Array.<number>=} opt_dest Destination.
- * @return {Array.<number>} Destination.
- */
-ol.geom.flat.interpolate.lineString = function(flatCoordinates, offset, end, stride, fraction, opt_dest) {
-  // FIXME does not work when vertices are repeated
-  // FIXME interpolate extra dimensions
-  ol.DEBUG && console.assert(0 <= fraction && fraction <= 1,
-      'fraction should be in between 0 and 1');
-  var pointX = NaN;
-  var pointY = NaN;
-  var n = (end - offset) / stride;
-  if (n === 0) {
-    ol.DEBUG && console.assert(false, 'n cannot be 0');
-  } else if (n == 1) {
-    pointX = flatCoordinates[offset];
-    pointY = flatCoordinates[offset + 1];
-  } else if (n == 2) {
-    pointX = (1 - fraction) * flatCoordinates[offset] +
-        fraction * flatCoordinates[offset + stride];
-    pointY = (1 - fraction) * flatCoordinates[offset + 1] +
-        fraction * flatCoordinates[offset + stride + 1];
-  } else {
-    var x1 = flatCoordinates[offset];
-    var y1 = flatCoordinates[offset + 1];
-    var length = 0;
-    var cumulativeLengths = [0];
-    var i;
-    for (i = offset + stride; i < end; i += stride) {
-      var x2 = flatCoordinates[i];
-      var y2 = flatCoordinates[i + 1];
-      length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
-      cumulativeLengths.push(length);
-      x1 = x2;
-      y1 = y2;
-    }
-    var target = fraction * length;
-    var index = ol.array.binarySearch(cumulativeLengths, target);
-    if (index < 0) {
-      var t = (target - cumulativeLengths[-index - 2]) /
-          (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]);
-      var o = offset + (-index - 2) * stride;
-      pointX = ol.math.lerp(
-          flatCoordinates[o], flatCoordinates[o + stride], t);
-      pointY = ol.math.lerp(
-          flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t);
-    } else {
-      pointX = flatCoordinates[offset + index * stride];
-      pointY = flatCoordinates[offset + index * stride + 1];
-    }
-  }
-  if (opt_dest) {
-    opt_dest[0] = pointX;
-    opt_dest[1] = pointY;
-    return opt_dest;
-  } else {
-    return [pointX, pointY];
-  }
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {number} m M.
- * @param {boolean} extrapolate Extrapolate.
- * @return {ol.Coordinate} Coordinate.
- */
-ol.geom.flat.lineStringCoordinateAtM = function(flatCoordinates, offset, end, stride, m, extrapolate) {
-  if (end == offset) {
-    return null;
-  }
-  var coordinate;
-  if (m < flatCoordinates[offset + stride - 1]) {
-    if (extrapolate) {
-      coordinate = flatCoordinates.slice(offset, offset + stride);
-      coordinate[stride - 1] = m;
-      return coordinate;
-    } else {
-      return null;
-    }
-  } else if (flatCoordinates[end - 1] < m) {
-    if (extrapolate) {
-      coordinate = flatCoordinates.slice(end - stride, end);
-      coordinate[stride - 1] = m;
-      return coordinate;
-    } else {
-      return null;
-    }
-  }
-  // FIXME use O(1) search
-  if (m == flatCoordinates[offset + stride - 1]) {
-    return flatCoordinates.slice(offset, offset + stride);
-  }
-  var lo = offset / stride;
-  var hi = end / stride;
-  while (lo < hi) {
-    var mid = (lo + hi) >> 1;
-    if (m < flatCoordinates[(mid + 1) * stride - 1]) {
-      hi = mid;
-    } else {
-      lo = mid + 1;
-    }
-  }
-  var m0 = flatCoordinates[lo * stride - 1];
-  if (m == m0) {
-    return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride);
-  }
-  var m1 = flatCoordinates[(lo + 1) * stride - 1];
-  ol.DEBUG && console.assert(m0 < m, 'm0 should be less than m');
-  ol.DEBUG && console.assert(m <= m1, 'm should be less than or equal to m1');
-  var t = (m - m0) / (m1 - m0);
-  coordinate = [];
-  var i;
-  for (i = 0; i < stride - 1; ++i) {
-    coordinate.push(ol.math.lerp(flatCoordinates[(lo - 1) * stride + i],
-        flatCoordinates[lo * stride + i], t));
-  }
-  coordinate.push(m);
-  ol.DEBUG && console.assert(coordinate.length == stride,
-      'length of coordinate array should match stride');
-  return coordinate;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<number>} ends Ends.
- * @param {number} stride Stride.
- * @param {number} m M.
- * @param {boolean} extrapolate Extrapolate.
- * @param {boolean} interpolate Interpolate.
- * @return {ol.Coordinate} Coordinate.
- */
-ol.geom.flat.lineStringsCoordinateAtM = function(
-    flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) {
-  if (interpolate) {
-    return ol.geom.flat.lineStringCoordinateAtM(
-        flatCoordinates, offset, ends[ends.length - 1], stride, m, extrapolate);
-  }
-  var coordinate;
-  if (m < flatCoordinates[stride - 1]) {
-    if (extrapolate) {
-      coordinate = flatCoordinates.slice(0, stride);
-      coordinate[stride - 1] = m;
-      return coordinate;
-    } else {
-      return null;
-    }
-  }
-  if (flatCoordinates[flatCoordinates.length - 1] < m) {
-    if (extrapolate) {
-      coordinate = flatCoordinates.slice(flatCoordinates.length - stride);
-      coordinate[stride - 1] = m;
-      return coordinate;
-    } else {
-      return null;
-    }
-  }
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    if (offset == end) {
-      continue;
-    }
-    if (m < flatCoordinates[offset + stride - 1]) {
-      return null;
-    } else if (m <= flatCoordinates[end - 1]) {
-      return ol.geom.flat.lineStringCoordinateAtM(
-          flatCoordinates, offset, end, stride, m, false);
-    }
-    offset = end;
-  }
-  ol.DEBUG && console.assert(false,
-      'ol.geom.flat.lineStringsCoordinateAtM should have returned');
-  return null;
-};
-
-goog.provide('ol.geom.flat.length');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {number} Length.
- */
-ol.geom.flat.length.lineString = function(flatCoordinates, offset, end, stride) {
-  var x1 = flatCoordinates[offset];
-  var y1 = flatCoordinates[offset + 1];
-  var length = 0;
-  var i;
-  for (i = offset + stride; i < end; i += stride) {
-    var x2 = flatCoordinates[i];
-    var y2 = flatCoordinates[i + 1];
-    length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
-    x1 = x2;
-    y1 = y2;
-  }
-  return length;
-};
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @return {number} Perimeter.
- */
-ol.geom.flat.length.linearRing = function(flatCoordinates, offset, end, stride) {
-  var perimeter =
-      ol.geom.flat.length.lineString(flatCoordinates, offset, end, stride);
-  var dx = flatCoordinates[end - stride] - flatCoordinates[offset];
-  var dy = flatCoordinates[end - stride + 1] - flatCoordinates[offset + 1];
-  perimeter += Math.sqrt(dx * dx + dy * dy);
-  return perimeter;
-};
-
-goog.provide('ol.geom.LineString');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.closest');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.geom.flat.interpolate');
-goog.require('ol.geom.flat.intersectsextent');
-goog.require('ol.geom.flat.length');
-goog.require('ol.geom.flat.segments');
-goog.require('ol.geom.flat.simplify');
-
-
-/**
- * @classdesc
- * Linestring geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.LineString = function(coordinates, opt_layout) {
-
-  ol.geom.SimpleGeometry.call(this);
-
-  /**
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.flatMidpoint_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.flatMidpointRevision_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
-
-  this.setCoordinates(coordinates, opt_layout);
-
-};
-ol.inherits(ol.geom.LineString, ol.geom.SimpleGeometry);
-
-
-/**
- * Append the passed coordinate to the coordinates of the linestring.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @api stable
- */
-ol.geom.LineString.prototype.appendCoordinate = function(coordinate) {
-  ol.DEBUG && console.assert(coordinate.length == this.stride,
-      'length of coordinate array should match stride');
-  if (!this.flatCoordinates) {
-    this.flatCoordinates = coordinate.slice();
-  } else {
-    ol.array.extend(this.flatCoordinates, coordinate);
-  }
-  this.changed();
-};
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.LineString} Clone.
- * @api stable
- */
-ol.geom.LineString.prototype.clone = function() {
-  var lineString = new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return lineString;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.LineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  if (this.maxDeltaRevision_ != this.getRevision()) {
-    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta(
-        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0));
-    this.maxDeltaRevision_ = this.getRevision();
-  }
-  return ol.geom.flat.closest.getClosestPoint(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
-};
-
-
-/**
- * Iterate over each segment, calling the provided callback.
- * If the callback returns a truthy value the function returns that
- * value immediately. Otherwise the function returns `false`.
- *
- * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function
- *     called for each segment.
- * @param {S=} opt_this The object to be used as the value of 'this'
- *     within callback.
- * @return {T|boolean} Value.
- * @template T,S
- * @api
- */
-ol.geom.LineString.prototype.forEachSegment = function(callback, opt_this) {
-  return ol.geom.flat.segments.forEach(this.flatCoordinates, 0,
-      this.flatCoordinates.length, this.stride, callback, opt_this);
-};
-
-
-/**
- * Returns the coordinate at `m` using linear interpolation, or `null` if no
- * such coordinate exists.
- *
- * `opt_extrapolate` controls extrapolation beyond the range of Ms in the
- * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first
- * M will return the first coordinate and Ms greater than the last M will
- * return the last coordinate.
- *
- * @param {number} m M.
- * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
- */
-ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) {
-  if (this.layout != ol.geom.GeometryLayout.XYM &&
-      this.layout != ol.geom.GeometryLayout.XYZM) {
-    return null;
-  }
-  var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false;
-  return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0,
-      this.flatCoordinates.length, this.stride, m, extrapolate);
-};
-
-
-/**
- * Return the coordinates of the linestring.
- * @return {Array.<ol.Coordinate>} Coordinates.
- * @api stable
- */
-ol.geom.LineString.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
-
-
-/**
- * Return the coordinate at the provided fraction along the linestring.
- * The `fraction` is a number between 0 and 1, where 0 is the start of the
- * linestring and 1 is the end.
- * @param {number} fraction Fraction.
- * @param {ol.Coordinate=} opt_dest Optional coordinate whose values will
- *     be modified. If not provided, a new coordinate will be returned.
- * @return {ol.Coordinate} Coordinate of the interpolated point.
- * @api
- */
-ol.geom.LineString.prototype.getCoordinateAt = function(fraction, opt_dest) {
-  return ol.geom.flat.interpolate.lineString(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      fraction, opt_dest);
-};
-
-
-/**
- * Return the length of the linestring on projected plane.
- * @return {number} Length (on projected plane).
- * @api stable
- */
-ol.geom.LineString.prototype.getLength = function() {
-  return ol.geom.flat.length.lineString(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
-
-
-/**
- * @return {Array.<number>} Flat midpoint.
- */
-ol.geom.LineString.prototype.getFlatMidpoint = function() {
-  if (this.flatMidpointRevision_ != this.getRevision()) {
-    this.flatMidpoint_ = this.getCoordinateAt(0.5, this.flatMidpoint_);
-    this.flatMidpointRevision_ = this.getRevision();
-  }
-  return this.flatMidpoint_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.LineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
-  var simplifiedFlatCoordinates = [];
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      squaredTolerance, simplifiedFlatCoordinates, 0);
-  var simplifiedLineString = new ol.geom.LineString(null);
-  simplifiedLineString.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates);
-  return simplifiedLineString;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.LineString.prototype.getType = function() {
-  return ol.geom.GeometryType.LINE_STRING;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.LineString.prototype.intersectsExtent = function(extent) {
-  return ol.geom.flat.intersectsextent.lineString(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
-      extent);
-};
-
-
-/**
- * Set the coordinates of the linestring.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.LineString.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 1);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- */
-ol.geom.LineString.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
-};
-
-goog.provide('ol.geom.MultiLineString');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.closest');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.geom.flat.interpolate');
-goog.require('ol.geom.flat.intersectsextent');
-goog.require('ol.geom.flat.simplify');
-
-
-/**
- * @classdesc
- * Multi-linestring geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.MultiLineString = function(coordinates, opt_layout) {
-
-  ol.geom.SimpleGeometry.call(this);
-
-  /**
-   * @type {Array.<number>}
-   * @private
-   */
-  this.ends_ = [];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
-
-  this.setCoordinates(coordinates, opt_layout);
-
-};
-ol.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry);
-
-
-/**
- * Append the passed linestring to the multilinestring.
- * @param {ol.geom.LineString} lineString LineString.
- * @api stable
- */
-ol.geom.MultiLineString.prototype.appendLineString = function(lineString) {
-  ol.DEBUG && console.assert(lineString.getLayout() == this.layout,
-      'layout of lineString should match the layout');
-  if (!this.flatCoordinates) {
-    this.flatCoordinates = lineString.getFlatCoordinates().slice();
-  } else {
-    ol.array.extend(
-        this.flatCoordinates, lineString.getFlatCoordinates().slice());
-  }
-  this.ends_.push(this.flatCoordinates.length);
-  this.changed();
-};
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.MultiLineString} Clone.
- * @api stable
- */
-ol.geom.MultiLineString.prototype.clone = function() {
-  var multiLineString = new ol.geom.MultiLineString(null);
-  multiLineString.setFlatCoordinates(
-      this.layout, this.flatCoordinates.slice(), this.ends_.slice());
-  return multiLineString;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.MultiLineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  if (this.maxDeltaRevision_ != this.getRevision()) {
-    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta(
-        this.flatCoordinates, 0, this.ends_, this.stride, 0));
-    this.maxDeltaRevision_ = this.getRevision();
-  }
-  return ol.geom.flat.closest.getsClosestPoint(
-      this.flatCoordinates, 0, this.ends_, this.stride,
-      this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
-};
-
-
-/**
- * Returns the coordinate at `m` using linear interpolation, or `null` if no
- * such coordinate exists.
- *
- * `opt_extrapolate` controls extrapolation beyond the range of Ms in the
- * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first
- * M will return the first coordinate and Ms greater than the last M will
- * return the last coordinate.
- *
- * `opt_interpolate` controls interpolation between consecutive LineStrings
- * within the MultiLineString. If `opt_interpolate` is `true` the coordinates
- * will be linearly interpolated between the last coordinate of one LineString
- * and the first coordinate of the next LineString.  If `opt_interpolate` is
- * `false` then the function will return `null` for Ms falling between
- * LineStrings.
- *
- * @param {number} m M.
- * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
- * @param {boolean=} opt_interpolate Interpolate. Default is `false`.
- * @return {ol.Coordinate} Coordinate.
- * @api stable
- */
-ol.geom.MultiLineString.prototype.getCoordinateAtM = function(m, opt_extrapolate, opt_interpolate) {
-  if ((this.layout != ol.geom.GeometryLayout.XYM &&
-       this.layout != ol.geom.GeometryLayout.XYZM) ||
-      this.flatCoordinates.length === 0) {
-    return null;
-  }
-  var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false;
-  var interpolate = opt_interpolate !== undefined ? opt_interpolate : false;
-  return ol.geom.flat.lineStringsCoordinateAtM(this.flatCoordinates, 0,
-      this.ends_, this.stride, m, extrapolate, interpolate);
-};
-
-
-/**
- * Return the coordinates of the multilinestring.
- * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
- * @api stable
- */
-ol.geom.MultiLineString.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinatess(
-      this.flatCoordinates, 0, this.ends_, this.stride);
-};
-
-
-/**
- * @return {Array.<number>} Ends.
- */
-ol.geom.MultiLineString.prototype.getEnds = function() {
-  return this.ends_;
-};
-
-
-/**
- * Return the linestring at the specified index.
- * @param {number} index Index.
- * @return {ol.geom.LineString} LineString.
- * @api stable
- */
-ol.geom.MultiLineString.prototype.getLineString = function(index) {
-  ol.DEBUG && console.assert(0 <= index && index < this.ends_.length,
-      'index should be in between 0 and length of the this.ends_ array');
-  if (index < 0 || this.ends_.length <= index) {
-    return null;
-  }
-  var lineString = new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
-      index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]));
-  return lineString;
-};
-
-
-/**
- * Return the linestrings of this multilinestring.
- * @return {Array.<ol.geom.LineString>} LineStrings.
- * @api stable
- */
-ol.geom.MultiLineString.prototype.getLineStrings = function() {
-  var flatCoordinates = this.flatCoordinates;
-  var ends = this.ends_;
-  var layout = this.layout;
-  /** @type {Array.<ol.geom.LineString>} */
-  var lineStrings = [];
-  var offset = 0;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var lineString = new ol.geom.LineString(null);
-    lineString.setFlatCoordinates(layout, flatCoordinates.slice(offset, end));
-    lineStrings.push(lineString);
-    offset = end;
-  }
-  return lineStrings;
-};
-
-
-/**
- * @return {Array.<number>} Flat midpoints.
- */
-ol.geom.MultiLineString.prototype.getFlatMidpoints = function() {
-  var midpoints = [];
-  var flatCoordinates = this.flatCoordinates;
-  var offset = 0;
-  var ends = this.ends_;
-  var stride = this.stride;
-  var i, ii;
-  for (i = 0, ii = ends.length; i < ii; ++i) {
-    var end = ends[i];
-    var midpoint = ol.geom.flat.interpolate.lineString(
-        flatCoordinates, offset, end, stride, 0.5);
-    ol.array.extend(midpoints, midpoint);
-    offset = end;
-  }
-  return midpoints;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.MultiLineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
-  var simplifiedFlatCoordinates = [];
-  var simplifiedEnds = [];
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeuckers(
-      this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance,
-      simplifiedFlatCoordinates, 0, simplifiedEnds);
-  var simplifiedMultiLineString = new ol.geom.MultiLineString(null);
-  simplifiedMultiLineString.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds);
-  return simplifiedMultiLineString;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.MultiLineString.prototype.getType = function() {
-  return ol.geom.GeometryType.MULTI_LINE_STRING;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) {
-  return ol.geom.flat.intersectsextent.lineStrings(
-      this.flatCoordinates, 0, this.ends_, this.stride, extent);
-};
-
-
-/**
- * Set the coordinates of the multilinestring.
- * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.MultiLineString.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
-  } else {
-    this.setLayout(opt_layout, coordinates, 2);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    var ends = ol.geom.flat.deflate.coordinatess(
-        this.flatCoordinates, 0, coordinates, this.stride, this.ends_);
-    this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1];
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<number>} ends Ends.
- */
-ol.geom.MultiLineString.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
-  if (!flatCoordinates) {
-    ol.DEBUG && console.assert(ends && ends.length === 0,
-        'ends must be truthy and ends.length should be 0');
-  } else if (ends.length === 0) {
-    ol.DEBUG && console.assert(flatCoordinates.length === 0,
-        'flatCoordinates should be an empty array');
-  } else {
-    ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1],
-        'length of flatCoordinates array should match the last value of ends');
-  }
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.ends_ = ends;
-  this.changed();
-};
-
-
-/**
- * @param {Array.<ol.geom.LineString>} lineStrings LineStrings.
- */
-ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) {
-  var layout = this.getLayout();
-  var flatCoordinates = [];
-  var ends = [];
-  var i, ii;
-  for (i = 0, ii = lineStrings.length; i < ii; ++i) {
-    var lineString = lineStrings[i];
-    if (i === 0) {
-      layout = lineString.getLayout();
-    } else {
-      // FIXME better handle the case of non-matching layouts
-      ol.DEBUG && console.assert(lineString.getLayout() == layout,
-          'layout of lineString should match layout');
-    }
-    ol.array.extend(flatCoordinates, lineString.getFlatCoordinates());
-    ends.push(flatCoordinates.length);
-  }
-  this.setFlatCoordinates(layout, flatCoordinates, ends);
-};
-
-goog.provide('ol.geom.MultiPoint');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.math');
-
-
-/**
- * @classdesc
- * Multi-point geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.MultiPoint = function(coordinates, opt_layout) {
-  ol.geom.SimpleGeometry.call(this);
-  this.setCoordinates(coordinates, opt_layout);
-};
-ol.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry);
-
-
-/**
- * Append the passed point to this multipoint.
- * @param {ol.geom.Point} point Point.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.appendPoint = function(point) {
-  ol.DEBUG && console.assert(point.getLayout() == this.layout,
-      'the layout of point should match layout');
-  if (!this.flatCoordinates) {
-    this.flatCoordinates = point.getFlatCoordinates().slice();
-  } else {
-    ol.array.extend(this.flatCoordinates, point.getFlatCoordinates());
-  }
-  this.changed();
-};
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.MultiPoint} Clone.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.clone = function() {
-  var multiPoint = new ol.geom.MultiPoint(null);
-  multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return multiPoint;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.MultiPoint.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  var flatCoordinates = this.flatCoordinates;
-  var stride = this.stride;
-  var i, ii, j;
-  for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
-    var squaredDistance = ol.math.squaredDistance(
-        x, y, flatCoordinates[i], flatCoordinates[i + 1]);
-    if (squaredDistance < minSquaredDistance) {
-      minSquaredDistance = squaredDistance;
-      for (j = 0; j < stride; ++j) {
-        closestPoint[j] = flatCoordinates[i + j];
-      }
-      closestPoint.length = stride;
-    }
-  }
-  return minSquaredDistance;
-};
-
-
-/**
- * Return the coordinates of the multipoint.
- * @return {Array.<ol.Coordinate>} Coordinates.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.getCoordinates = function() {
-  return ol.geom.flat.inflate.coordinates(
-      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
-};
-
-
-/**
- * Return the point at the specified index.
- * @param {number} index Index.
- * @return {ol.geom.Point} Point.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.getPoint = function(index) {
-  var n = !this.flatCoordinates ?
-      0 : this.flatCoordinates.length / this.stride;
-  ol.DEBUG && console.assert(0 <= index && index < n,
-      'index should be in between 0 and n');
-  if (index < 0 || n <= index) {
-    return null;
-  }
-  var point = new ol.geom.Point(null);
-  point.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
-      index * this.stride, (index + 1) * this.stride));
-  return point;
-};
-
-
-/**
- * Return the points of this multipoint.
- * @return {Array.<ol.geom.Point>} Points.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.getPoints = function() {
-  var flatCoordinates = this.flatCoordinates;
-  var layout = this.layout;
-  var stride = this.stride;
-  /** @type {Array.<ol.geom.Point>} */
-  var points = [];
-  var i, ii;
-  for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
-    var point = new ol.geom.Point(null);
-    point.setFlatCoordinates(layout, flatCoordinates.slice(i, i + stride));
-    points.push(point);
-  }
-  return points;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.MultiPoint.prototype.getType = function() {
-  return ol.geom.GeometryType.MULTI_POINT;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.MultiPoint.prototype.intersectsExtent = function(extent) {
-  var flatCoordinates = this.flatCoordinates;
-  var stride = this.stride;
-  var i, ii, x, y;
-  for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
-    x = flatCoordinates[i];
-    y = flatCoordinates[i + 1];
-    if (ol.extent.containsXY(extent, x, y)) {
-      return true;
-    }
-  }
-  return false;
-};
-
-
-/**
- * Set the coordinates of the multipoint.
- * @param {Array.<ol.Coordinate>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.MultiPoint.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, coordinates, 1);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
-        this.flatCoordinates, 0, coordinates, this.stride);
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- */
-ol.geom.MultiPoint.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
-};
-
-goog.provide('ol.geom.flat.center');
-
-goog.require('ol.extent');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {Array.<Array.<number>>} endss Endss.
- * @param {number} stride Stride.
- * @return {Array.<number>} Flat centers.
- */
-ol.geom.flat.center.linearRingss = function(flatCoordinates, offset, endss, stride) {
-  var flatCenters = [];
-  var i, ii;
-  var extent = ol.extent.createEmpty();
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i];
-    extent = ol.extent.createOrUpdateFromFlatCoordinates(
-        flatCoordinates, offset, ends[0], stride);
-    flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2);
-    offset = ends[ends.length - 1];
-  }
-  return flatCenters;
-};
-
-goog.provide('ol.geom.MultiPolygon');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.Polygon');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.area');
-goog.require('ol.geom.flat.center');
-goog.require('ol.geom.flat.closest');
-goog.require('ol.geom.flat.contains');
-goog.require('ol.geom.flat.deflate');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.geom.flat.interiorpoint');
-goog.require('ol.geom.flat.intersectsextent');
-goog.require('ol.geom.flat.orient');
-goog.require('ol.geom.flat.simplify');
-
-
-/**
- * @classdesc
- * Multi-polygon geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.MultiPolygon = function(coordinates, opt_layout) {
-
-  ol.geom.SimpleGeometry.call(this);
-
-  /**
-   * @type {Array.<Array.<number>>}
-   * @private
-   */
-  this.endss_ = [];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.flatInteriorPointsRevision_ = -1;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.flatInteriorPoints_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDelta_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxDeltaRevision_ = -1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.orientedRevision_ = -1;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.orientedFlatCoordinates_ = null;
-
-  this.setCoordinates(coordinates, opt_layout);
-
-};
-ol.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry);
-
-
-/**
- * Append the passed polygon to this multipolygon.
- * @param {ol.geom.Polygon} polygon Polygon.
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) {
-  ol.DEBUG && console.assert(polygon.getLayout() == this.layout,
-      'layout of polygon should match layout');
-  /** @type {Array.<number>} */
-  var ends;
-  if (!this.flatCoordinates) {
-    this.flatCoordinates = polygon.getFlatCoordinates().slice();
-    ends = polygon.getEnds().slice();
-    this.endss_.push();
-  } else {
-    var offset = this.flatCoordinates.length;
-    ol.array.extend(this.flatCoordinates, polygon.getFlatCoordinates());
-    ends = polygon.getEnds().slice();
-    var i, ii;
-    for (i = 0, ii = ends.length; i < ii; ++i) {
-      ends[i] += offset;
-    }
-  }
-  this.endss_.push(ends);
-  this.changed();
-};
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.MultiPolygon} Clone.
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.clone = function() {
-  var multiPolygon = new ol.geom.MultiPolygon(null);
-
-  var len = this.endss_.length;
-  var newEndss = new Array(len);
-  for (var i = 0; i < len; ++i) {
-    newEndss[i] = this.endss_[i].slice();
-  }
-
-  multiPolygon.setFlatCoordinates(
-      this.layout, this.flatCoordinates.slice(), newEndss);
-  return multiPolygon;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.MultiPolygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  if (this.maxDeltaRevision_ != this.getRevision()) {
-    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getssMaxSquaredDelta(
-        this.flatCoordinates, 0, this.endss_, this.stride, 0));
-    this.maxDeltaRevision_ = this.getRevision();
-  }
-  return ol.geom.flat.closest.getssClosestPoint(
-      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride,
-      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.MultiPolygon.prototype.containsXY = function(x, y) {
-  return ol.geom.flat.contains.linearRingssContainsXY(
-      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y);
-};
-
-
-/**
- * Return the area of the multipolygon on projected plane.
- * @return {number} Area (on projected plane).
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.getArea = function() {
-  return ol.geom.flat.area.linearRingss(
-      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride);
-};
-
-
-/**
- * Get the coordinate array for this geometry.  This array has the structure
- * of a GeoJSON coordinate array for multi-polygons.
- *
- * @param {boolean=} opt_right Orient coordinates according to the right-hand
- *     rule (counter-clockwise for exterior and clockwise for interior rings).
- *     If `false`, coordinates will be oriented according to the left-hand rule
- *     (clockwise for exterior and counter-clockwise for interior rings).
- *     By default, coordinate orientation will depend on how the geometry was
- *     constructed.
- * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates.
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.getCoordinates = function(opt_right) {
-  var flatCoordinates;
-  if (opt_right !== undefined) {
-    flatCoordinates = this.getOrientedFlatCoordinates().slice();
-    ol.geom.flat.orient.orientLinearRingss(
-        flatCoordinates, 0, this.endss_, this.stride, opt_right);
-  } else {
-    flatCoordinates = this.flatCoordinates;
-  }
-
-  return ol.geom.flat.inflate.coordinatesss(
-      flatCoordinates, 0, this.endss_, this.stride);
-};
-
-
-/**
- * @return {Array.<Array.<number>>} Endss.
- */
-ol.geom.MultiPolygon.prototype.getEndss = function() {
-  return this.endss_;
-};
-
-
-/**
- * @return {Array.<number>} Flat interior points.
- */
-ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() {
-  if (this.flatInteriorPointsRevision_ != this.getRevision()) {
-    var flatCenters = ol.geom.flat.center.linearRingss(
-        this.flatCoordinates, 0, this.endss_, this.stride);
-    this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss(
-        this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride,
-        flatCenters);
-    this.flatInteriorPointsRevision_ = this.getRevision();
-  }
-  return this.flatInteriorPoints_;
-};
-
-
-/**
- * Return the interior points as {@link ol.geom.MultiPoint multipoint}.
- * @return {ol.geom.MultiPoint} Interior points.
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
-  var interiorPoints = new ol.geom.MultiPoint(null);
-  interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY,
-      this.getFlatInteriorPoints().slice());
-  return interiorPoints;
-};
-
-
-/**
- * @return {Array.<number>} Oriented flat coordinates.
- */
-ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() {
-  if (this.orientedRevision_ != this.getRevision()) {
-    var flatCoordinates = this.flatCoordinates;
-    if (ol.geom.flat.orient.linearRingssAreOriented(
-        flatCoordinates, 0, this.endss_, this.stride)) {
-      this.orientedFlatCoordinates_ = flatCoordinates;
-    } else {
-      this.orientedFlatCoordinates_ = flatCoordinates.slice();
-      this.orientedFlatCoordinates_.length =
-          ol.geom.flat.orient.orientLinearRingss(
-              this.orientedFlatCoordinates_, 0, this.endss_, this.stride);
-    }
-    this.orientedRevision_ = this.getRevision();
-  }
-  return this.orientedFlatCoordinates_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
-  var simplifiedFlatCoordinates = [];
-  var simplifiedEndss = [];
-  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizess(
-      this.flatCoordinates, 0, this.endss_, this.stride,
-      Math.sqrt(squaredTolerance),
-      simplifiedFlatCoordinates, 0, simplifiedEndss);
-  var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null);
-  simplifiedMultiPolygon.setFlatCoordinates(
-      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss);
-  return simplifiedMultiPolygon;
-};
-
-
-/**
- * Return the polygon at the specified index.
- * @param {number} index Index.
- * @return {ol.geom.Polygon} Polygon.
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.getPolygon = function(index) {
-  ol.DEBUG && console.assert(0 <= index && index < this.endss_.length,
-      'index should be in between 0 and the length of this.endss_');
-  if (index < 0 || this.endss_.length <= index) {
-    return null;
-  }
-  var offset;
-  if (index === 0) {
-    offset = 0;
-  } else {
-    var prevEnds = this.endss_[index - 1];
-    offset = prevEnds[prevEnds.length - 1];
-  }
-  var ends = this.endss_[index].slice();
-  var end = ends[ends.length - 1];
-  if (offset !== 0) {
-    var i, ii;
-    for (i = 0, ii = ends.length; i < ii; ++i) {
-      ends[i] -= offset;
-    }
-  }
-  var polygon = new ol.geom.Polygon(null);
-  polygon.setFlatCoordinates(
-      this.layout, this.flatCoordinates.slice(offset, end), ends);
-  return polygon;
-};
-
-
-/**
- * Return the polygons of this multipolygon.
- * @return {Array.<ol.geom.Polygon>} Polygons.
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.getPolygons = function() {
-  var layout = this.layout;
-  var flatCoordinates = this.flatCoordinates;
-  var endss = this.endss_;
-  var polygons = [];
-  var offset = 0;
-  var i, ii, j, jj;
-  for (i = 0, ii = endss.length; i < ii; ++i) {
-    var ends = endss[i].slice();
-    var end = ends[ends.length - 1];
-    if (offset !== 0) {
-      for (j = 0, jj = ends.length; j < jj; ++j) {
-        ends[j] -= offset;
-      }
-    }
-    var polygon = new ol.geom.Polygon(null);
-    polygon.setFlatCoordinates(
-        layout, flatCoordinates.slice(offset, end), ends);
-    polygons.push(polygon);
-    offset = end;
-  }
-  return polygons;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.getType = function() {
-  return ol.geom.GeometryType.MULTI_POLYGON;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) {
-  return ol.geom.flat.intersectsextent.linearRingss(
-      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent);
-};
-
-
-/**
- * Set the coordinates of the multipolygon.
- * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api stable
- */
-ol.geom.MultiPolygon.prototype.setCoordinates = function(coordinates, opt_layout) {
-  if (!coordinates) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_);
-  } else {
-    this.setLayout(opt_layout, coordinates, 3);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    var endss = ol.geom.flat.deflate.coordinatesss(
-        this.flatCoordinates, 0, coordinates, this.stride, this.endss_);
-    if (endss.length === 0) {
-      this.flatCoordinates.length = 0;
-    } else {
-      var lastEnds = endss[endss.length - 1];
-      this.flatCoordinates.length = lastEnds.length === 0 ?
-          0 : lastEnds[lastEnds.length - 1];
-    }
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Array.<Array.<number>>} endss Endss.
- */
-ol.geom.MultiPolygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, endss) {
-  ol.DEBUG && console.assert(endss, 'endss must be truthy');
-  if (!flatCoordinates || flatCoordinates.length === 0) {
-    ol.DEBUG && console.assert(endss.length === 0, 'the length of endss should be 0');
-  } else {
-    ol.DEBUG && console.assert(endss.length > 0, 'endss cannot be an empty array');
-    var ends = endss[endss.length - 1];
-    ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1],
-        'the length of flatCoordinates should be the last value of ends');
-  }
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.endss_ = endss;
-  this.changed();
-};
-
-
-/**
- * @param {Array.<ol.geom.Polygon>} polygons Polygons.
- */
-ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) {
-  var layout = this.getLayout();
-  var flatCoordinates = [];
-  var endss = [];
-  var i, ii, ends;
-  for (i = 0, ii = polygons.length; i < ii; ++i) {
-    var polygon = polygons[i];
-    if (i === 0) {
-      layout = polygon.getLayout();
-    } else {
-      // FIXME better handle the case of non-matching layouts
-      ol.DEBUG && console.assert(polygon.getLayout() == layout,
-          'layout of polygon should be layout');
-    }
-    var offset = flatCoordinates.length;
-    ends = polygon.getEnds();
-    var j, jj;
-    for (j = 0, jj = ends.length; j < jj; ++j) {
-      ends[j] += offset;
-    }
-    ol.array.extend(flatCoordinates, polygon.getFlatCoordinates());
-    endss.push(ends);
-  }
-  this.setFlatCoordinates(layout, flatCoordinates, endss);
-};
-
-goog.provide('ol.format.EsriJSON');
-
-goog.require('ol');
-goog.require('ol.Feature');
-goog.require('ol.array');
-goog.require('ol.asserts');
-goog.require('ol.extent');
-goog.require('ol.format.Feature');
-goog.require('ol.format.JSONFeature');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.geom.flat.orient');
-goog.require('ol.obj');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the EsriJSON format.
- *
- * @constructor
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.EsriJSONOptions=} opt_options Options.
- * @api
- */
-ol.format.EsriJSON = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.JSONFeature.call(this);
-
-  /**
-   * Name of the geometry attribute for features.
-   * @type {string|undefined}
-   * @private
-   */
-  this.geometryName_ = options.geometryName;
-
-};
-ol.inherits(ol.format.EsriJSON, ol.format.JSONFeature);
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @private
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.EsriJSON.readGeometry_ = function(object, opt_options) {
-  if (!object) {
-    return null;
-  }
-  /** @type {ol.geom.GeometryType} */
-  var type;
-  if (typeof object.x === 'number' && typeof object.y === 'number') {
-    type = ol.geom.GeometryType.POINT;
-  } else if (object.points) {
-    type = ol.geom.GeometryType.MULTI_POINT;
-  } else if (object.paths) {
-    if (object.paths.length === 1) {
-      type = ol.geom.GeometryType.LINE_STRING;
-    } else {
-      type = ol.geom.GeometryType.MULTI_LINE_STRING;
-    }
-  } else if (object.rings) {
-    var layout = ol.format.EsriJSON.getGeometryLayout_(object);
-    var rings = ol.format.EsriJSON.convertRings_(object.rings, layout);
-    object = /** @type {EsriJSONGeometry} */(ol.obj.assign({}, object));
-    if (rings.length === 1) {
-      type = ol.geom.GeometryType.POLYGON;
-      object.rings = rings[0];
-    } else {
-      type = ol.geom.GeometryType.MULTI_POLYGON;
-      object.rings = rings;
-    }
-  }
-  var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type];
-  return /** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(
-          geometryReader(object), false, opt_options));
-};
-
-
-/**
- * Determines inner and outer rings.
- * Checks if any polygons in this array contain any other polygons in this
- * array. It is used for checking for holes.
- * Logic inspired by: https://github.com/Esri/terraformer-arcgis-parser
- * @param {Array.<!Array.<!Array.<number>>>} rings Rings.
- * @param {ol.geom.GeometryLayout} layout Geometry layout.
- * @private
- * @return {Array.<!Array.<!Array.<number>>>} Transoformed rings.
- */
-ol.format.EsriJSON.convertRings_ = function(rings, layout) {
-  var outerRings = [];
-  var holes = [];
-  var i, ii;
-  for (i = 0, ii = rings.length; i < ii; ++i) {
-    var flatRing = ol.array.flatten(rings[i]);
-    // is this ring an outer ring? is it clockwise?
-    var clockwise = ol.geom.flat.orient.linearRingIsClockwise(flatRing, 0,
-        flatRing.length, layout.length);
-    if (clockwise) {
-      outerRings.push([rings[i]]);
-    } else {
-      holes.push(rings[i]);
-    }
-  }
-  while (holes.length) {
-    var hole = holes.shift();
-    var matched = false;
-    // loop over all outer rings and see if they contain our hole.
-    for (i = outerRings.length - 1; i >= 0; i--) {
-      var outerRing = outerRings[i][0];
-      if (ol.extent.containsExtent(new ol.geom.LinearRing(
-          outerRing).getExtent(),
-          new ol.geom.LinearRing(hole).getExtent())) {
-        // the hole is contained push it into our polygon
-        outerRings[i].push(hole);
-        matched = true;
-        break;
-      }
-    }
-    if (!matched) {
-      // no outer rings contain this hole turn it into and outer
-      // ring (reverse it)
-      outerRings.push([hole.reverse()]);
-    }
-  }
-  return outerRings;
-};
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Geometry} Point.
- */
-ol.format.EsriJSON.readPointGeometry_ = function(object) {
-  ol.DEBUG && console.assert(typeof object.x === 'number', 'object.x should be number');
-  ol.DEBUG && console.assert(typeof object.y === 'number', 'object.y should be number');
-  var point;
-  if (object.m !== undefined && object.z !== undefined) {
-    point = new ol.geom.Point([object.x, object.y, object.z, object.m],
-        ol.geom.GeometryLayout.XYZM);
-  } else if (object.z !== undefined) {
-    point = new ol.geom.Point([object.x, object.y, object.z],
-        ol.geom.GeometryLayout.XYZ);
-  } else if (object.m !== undefined) {
-    point = new ol.geom.Point([object.x, object.y, object.m],
-        ol.geom.GeometryLayout.XYM);
-  } else {
-    point = new ol.geom.Point([object.x, object.y]);
-  }
-  return point;
-};
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Geometry} LineString.
- */
-ol.format.EsriJSON.readLineStringGeometry_ = function(object) {
-  ol.DEBUG && console.assert(Array.isArray(object.paths),
-      'object.paths should be an array');
-  ol.DEBUG && console.assert(object.paths.length === 1,
-      'object.paths array length should be 1');
-  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
-  return new ol.geom.LineString(object.paths[0], layout);
-};
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Geometry} MultiLineString.
- */
-ol.format.EsriJSON.readMultiLineStringGeometry_ = function(object) {
-  ol.DEBUG && console.assert(Array.isArray(object.paths),
-      'object.paths should be an array');
-  ol.DEBUG && console.assert(object.paths.length > 1,
-      'object.paths array length should be more than 1');
-  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
-  return new ol.geom.MultiLineString(object.paths, layout);
-};
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @private
- * @return {ol.geom.GeometryLayout} The geometry layout to use.
- */
-ol.format.EsriJSON.getGeometryLayout_ = function(object) {
-  var layout = ol.geom.GeometryLayout.XY;
-  if (object.hasZ === true && object.hasM === true) {
-    layout = ol.geom.GeometryLayout.XYZM;
-  } else if (object.hasZ === true) {
-    layout = ol.geom.GeometryLayout.XYZ;
-  } else if (object.hasM === true) {
-    layout = ol.geom.GeometryLayout.XYM;
-  }
-  return layout;
-};
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Geometry} MultiPoint.
- */
-ol.format.EsriJSON.readMultiPointGeometry_ = function(object) {
-  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
-  return new ol.geom.MultiPoint(object.points, layout);
-};
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Geometry} MultiPolygon.
- */
-ol.format.EsriJSON.readMultiPolygonGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.rings.length > 1,
-      'object.rings should have length larger than 1');
-  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
-  return new ol.geom.MultiPolygon(
-      /** @type {Array.<Array.<Array.<Array.<number>>>>} */(object.rings),
-      layout);
-};
-
-
-/**
- * @param {EsriJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Geometry} Polygon.
- */
-ol.format.EsriJSON.readPolygonGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.rings);
-  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
-  return new ol.geom.Polygon(object.rings, layout);
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {EsriJSONGeometry} EsriJSON geometry.
- */
-ol.format.EsriJSON.writePointGeometry_ = function(geometry, opt_options) {
-  var coordinates = /** @type {ol.geom.Point} */ (geometry).getCoordinates();
-  var esriJSON;
-  var layout = /** @type {ol.geom.Point} */ (geometry).getLayout();
-  if (layout === ol.geom.GeometryLayout.XYZ) {
-    esriJSON = /** @type {EsriJSONPoint} */ ({
-      x: coordinates[0],
-      y: coordinates[1],
-      z: coordinates[2]
-    });
-  } else if (layout === ol.geom.GeometryLayout.XYM) {
-    esriJSON = /** @type {EsriJSONPoint} */ ({
-      x: coordinates[0],
-      y: coordinates[1],
-      m: coordinates[2]
-    });
-  } else if (layout === ol.geom.GeometryLayout.XYZM) {
-    esriJSON = /** @type {EsriJSONPoint} */ ({
-      x: coordinates[0],
-      y: coordinates[1],
-      z: coordinates[2],
-      m: coordinates[3]
-    });
-  } else if (layout === ol.geom.GeometryLayout.XY) {
-    esriJSON = /** @type {EsriJSONPoint} */ ({
-      x: coordinates[0],
-      y: coordinates[1]
-    });
-  } else {
-    ol.asserts.assert(false, 34); // Invalid geometry layout
-  }
-  return /** @type {EsriJSONGeometry} */ (esriJSON);
-};
-
-
-/**
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @private
- * @return {Object} Object with boolean hasZ and hasM keys.
- */
-ol.format.EsriJSON.getHasZM_ = function(geometry) {
-  var layout = geometry.getLayout();
-  return {
-    hasZ: (layout === ol.geom.GeometryLayout.XYZ ||
-        layout === ol.geom.GeometryLayout.XYZM),
-    hasM: (layout === ol.geom.GeometryLayout.XYM ||
-        layout === ol.geom.GeometryLayout.XYZM)
-  };
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {EsriJSONPolyline} EsriJSON geometry.
- */
-ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.LineString} */ (geometry));
-  return /** @type {EsriJSONPolyline} */ ({
-    hasZ: hasZM.hasZ,
-    hasM: hasZM.hasM,
-    paths: [
-      /** @type {ol.geom.LineString} */ (geometry).getCoordinates()
-    ]
-  });
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {EsriJSONPolygon} EsriJSON geometry.
- */
-ol.format.EsriJSON.writePolygonGeometry_ = function(geometry, opt_options) {
-  // Esri geometries use the left-hand rule
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.Polygon} */ (geometry));
-  return /** @type {EsriJSONPolygon} */ ({
-    hasZ: hasZM.hasZ,
-    hasM: hasZM.hasM,
-    rings: /** @type {ol.geom.Polygon} */ (geometry).getCoordinates(false)
-  });
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {EsriJSONPolyline} EsriJSON geometry.
- */
-ol.format.EsriJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiLineString} */ (geometry));
-  return /** @type {EsriJSONPolyline} */ ({
-    hasZ: hasZM.hasZ,
-    hasM: hasZM.hasM,
-    paths: /** @type {ol.geom.MultiLineString} */ (geometry).getCoordinates()
-  });
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {EsriJSONMultipoint} EsriJSON geometry.
- */
-ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPoint} */ (geometry));
-  return /** @type {EsriJSONMultipoint} */ ({
-    hasZ: hasZM.hasZ,
-    hasM: hasZM.hasM,
-    points: /** @type {ol.geom.MultiPoint} */ (geometry).getCoordinates()
-  });
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {EsriJSONPolygon} EsriJSON geometry.
- */
-ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry,
-    opt_options) {
-  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPolygon} */ (geometry));
-  var coordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getCoordinates(false);
-  var output = [];
-  for (var i = 0; i < coordinates.length; i++) {
-    for (var x = coordinates[i].length - 1; x >= 0; x--) {
-      output.push(coordinates[i][x]);
-    }
-  }
-  return /** @type {EsriJSONPolygon} */ ({
-    hasZ: hasZM.hasZ,
-    hasM: hasZM.hasM,
-    rings: output
-  });
-};
-
-
-/**
- * @const
- * @private
- * @type {Object.<ol.geom.GeometryType, function(EsriJSONGeometry): ol.geom.Geometry>}
- */
-ol.format.EsriJSON.GEOMETRY_READERS_ = {};
-ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POINT] =
-    ol.format.EsriJSON.readPointGeometry_;
-ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.LINE_STRING] =
-    ol.format.EsriJSON.readLineStringGeometry_;
-ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POLYGON] =
-    ol.format.EsriJSON.readPolygonGeometry_;
-ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POINT] =
-    ol.format.EsriJSON.readMultiPointGeometry_;
-ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_LINE_STRING] =
-    ol.format.EsriJSON.readMultiLineStringGeometry_;
-ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POLYGON] =
-    ol.format.EsriJSON.readMultiPolygonGeometry_;
-
-
-/**
- * @const
- * @private
- * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (EsriJSONGeometry)>}
- */
-ol.format.EsriJSON.GEOMETRY_WRITERS_ = {};
-ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POINT] =
-    ol.format.EsriJSON.writePointGeometry_;
-ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.LINE_STRING] =
-    ol.format.EsriJSON.writeLineStringGeometry_;
-ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POLYGON] =
-    ol.format.EsriJSON.writePolygonGeometry_;
-ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POINT] =
-    ol.format.EsriJSON.writeMultiPointGeometry_;
-ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_LINE_STRING] =
-    ol.format.EsriJSON.writeMultiLineStringGeometry_;
-ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POLYGON] =
-    ol.format.EsriJSON.writeMultiPolygonGeometry_;
-
-
-/**
- * Read a feature from a EsriJSON Feature source.  Only works for Feature,
- * use `readFeatures` to read FeatureCollection source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api
- */
-ol.format.EsriJSON.prototype.readFeature;
-
-
-/**
- * Read all features from a EsriJSON source.  Works with both Feature and
- * FeatureCollection sources.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api
- */
-ol.format.EsriJSON.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.EsriJSON.prototype.readFeatureFromObject = function(
-    object, opt_options) {
-  var esriJSONFeature = /** @type {EsriJSONFeature} */ (object);
-  ol.DEBUG && console.assert(esriJSONFeature.geometry ||
-      esriJSONFeature.attributes,
-      'geometry or attributes should be defined');
-  var geometry = ol.format.EsriJSON.readGeometry_(esriJSONFeature.geometry,
-      opt_options);
-  var feature = new ol.Feature();
-  if (this.geometryName_) {
-    feature.setGeometryName(this.geometryName_);
-  }
-  feature.setGeometry(geometry);
-  if (opt_options && opt_options.idField &&
-      esriJSONFeature.attributes[opt_options.idField]) {
-    ol.DEBUG && console.assert(
-        typeof esriJSONFeature.attributes[opt_options.idField] === 'number',
-        'objectIdFieldName value should be a number');
-    feature.setId(/** @type {number} */(
-        esriJSONFeature.attributes[opt_options.idField]));
-  }
-  if (esriJSONFeature.attributes) {
-    feature.setProperties(esriJSONFeature.attributes);
-  }
-  return feature;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.EsriJSON.prototype.readFeaturesFromObject = function(
-    object, opt_options) {
-  var esriJSONObject = /** @type {EsriJSONObject} */ (object);
-  var options = opt_options ? opt_options : {};
-  if (esriJSONObject.features) {
-    var esriJSONFeatureCollection = /** @type {EsriJSONFeatureCollection} */
-        (object);
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    var esriJSONFeatures = esriJSONFeatureCollection.features;
-    var i, ii;
-    options.idField = object.objectIdFieldName;
-    for (i = 0, ii = esriJSONFeatures.length; i < ii; ++i) {
-      features.push(this.readFeatureFromObject(esriJSONFeatures[i],
-          options));
-    }
-    return features;
-  } else {
-    return [this.readFeatureFromObject(object, options)];
-  }
-};
-
-
-/**
- * Read a geometry from a EsriJSON source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
- * @api
- */
-ol.format.EsriJSON.prototype.readGeometry;
-
-
-/**
- * @inheritDoc
- */
-ol.format.EsriJSON.prototype.readGeometryFromObject = function(
-    object, opt_options) {
-  return ol.format.EsriJSON.readGeometry_(
-      /** @type {EsriJSONGeometry} */ (object), opt_options);
-};
-
-
-/**
- * Read the projection from a EsriJSON source.
- *
- * @function
- * @param {ArrayBuffer|Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api
- */
-ol.format.EsriJSON.prototype.readProjection;
-
-
-/**
- * @inheritDoc
- */
-ol.format.EsriJSON.prototype.readProjectionFromObject = function(object) {
-  var esriJSONObject = /** @type {EsriJSONObject} */ (object);
-  if (esriJSONObject.spatialReference && esriJSONObject.spatialReference.wkid) {
-    var crs = esriJSONObject.spatialReference.wkid;
-    return ol.proj.get('EPSG:' + crs);
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {EsriJSONGeometry} EsriJSON geometry.
- */
-ol.format.EsriJSON.writeGeometry_ = function(geometry, opt_options) {
-  var geometryWriter = ol.format.EsriJSON.GEOMETRY_WRITERS_[geometry.getType()];
-  return geometryWriter(/** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
-      opt_options);
-};
-
-
-/**
- * Encode a geometry as a EsriJSON string.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} EsriJSON.
- * @api
- */
-ol.format.EsriJSON.prototype.writeGeometry;
-
-
-/**
- * Encode a geometry as a EsriJSON object.
- *
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {EsriJSONGeometry} Object.
- * @api
- */
-ol.format.EsriJSON.prototype.writeGeometryObject = function(geometry,
-    opt_options) {
-  return ol.format.EsriJSON.writeGeometry_(geometry,
-      this.adaptOptions(opt_options));
-};
-
-
-/**
- * Encode a feature as a EsriJSON Feature string.
- *
- * @function
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} EsriJSON.
- * @api
- */
-ol.format.EsriJSON.prototype.writeFeature;
-
-
-/**
- * Encode a feature as a esriJSON Feature object.
- *
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} Object.
- * @api
- */
-ol.format.EsriJSON.prototype.writeFeatureObject = function(
-    feature, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  var object = {};
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    object['geometry'] =
-        ol.format.EsriJSON.writeGeometry_(geometry, opt_options);
-  }
-  var properties = feature.getProperties();
-  delete properties[feature.getGeometryName()];
-  if (!ol.obj.isEmpty(properties)) {
-    object['attributes'] = properties;
-  } else {
-    object['attributes'] = {};
-  }
-  if (opt_options && opt_options.featureProjection) {
-    object['spatialReference'] = /** @type {EsriJSONCRS} */({
-      wkid: ol.proj.get(
-          opt_options.featureProjection).getCode().split(':').pop()
-    });
-  }
-  return object;
-};
-
-
-/**
- * Encode an array of features as EsriJSON.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} EsriJSON.
- * @api
- */
-ol.format.EsriJSON.prototype.writeFeatures;
-
-
-/**
- * Encode an array of features as a EsriJSON object.
- *
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {Object} EsriJSON Object.
- * @api
- */
-ol.format.EsriJSON.prototype.writeFeaturesObject = function(features, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  var objects = [];
-  var i, ii;
-  for (i = 0, ii = features.length; i < ii; ++i) {
-    objects.push(this.writeFeatureObject(features[i], opt_options));
-  }
-  return /** @type {EsriJSONFeatureCollection} */ ({
-    'features': objects
-  });
-};
-
-goog.provide('ol.format.filter.Filter');
-
-goog.require('ol');
-
-
-/**
- * @classdesc
- * Abstract class; normally only used for creating subclasses and not instantiated in apps.
- * Base class for WFS GetFeature filters.
- *
- * @constructor
- * @param {!string} tagName The XML tag name for this filter.
- * @struct
- * @api
- */
-ol.format.filter.Filter = function(tagName) {
-
-  /**
-   * @private
-   * @type {!string}
-   */
-  this.tagName_ = tagName;
-};
-
-/**
- * The XML tag name for a filter.
- * @returns {!string} Name.
- */
-ol.format.filter.Filter.prototype.getTagName = function() {
-  return this.tagName_;
-};
-
-goog.provide('ol.format.filter.Logical');
-
-goog.require('ol');
-goog.require('ol.format.filter.Filter');
-
-
-/**
- * @classdesc
- * Abstract class; normally only used for creating subclasses and not instantiated in apps.
- * Base class for WFS GetFeature logical filters.
- *
- * @constructor
- * @param {!string} tagName The XML tag name for this filter.
- * @extends {ol.format.filter.Filter}
- */
-ol.format.filter.Logical = function(tagName) {
-  ol.format.filter.Filter.call(this, tagName);
-};
-ol.inherits(ol.format.filter.Logical, ol.format.filter.Filter);
-
-goog.provide('ol.format.filter.LogicalBinary');
-
-goog.require('ol');
-goog.require('ol.format.filter.Logical');
-
-
-/**
- * @classdesc
- * Abstract class; normally only used for creating subclasses and not instantiated in apps.
- * Base class for WFS GetFeature binary logical filters.
- *
- * @constructor
- * @param {!string} tagName The XML tag name for this filter.
- * @param {!ol.format.filter.Filter} conditionA First filter condition.
- * @param {!ol.format.filter.Filter} conditionB Second filter condition.
- * @extends {ol.format.filter.Logical}
- */
-ol.format.filter.LogicalBinary = function(tagName, conditionA, conditionB) {
-
-  ol.format.filter.Logical.call(this, tagName);
-
-  /**
-   * @public
-   * @type {!ol.format.filter.Filter}
-   */
-  this.conditionA = conditionA;
-
-  /**
-   * @public
-   * @type {!ol.format.filter.Filter}
-   */
-  this.conditionB = conditionB;
-
-};
-ol.inherits(ol.format.filter.LogicalBinary, ol.format.filter.Logical);
-
-goog.provide('ol.format.filter.And');
-
-goog.require('ol');
-goog.require('ol.format.filter.LogicalBinary');
-
-/**
- * @classdesc
- * Represents a logical `<And>` operator between two filter conditions.
- *
- * @constructor
- * @param {!ol.format.filter.Filter} conditionA First filter condition.
- * @param {!ol.format.filter.Filter} conditionB Second filter condition.
- * @extends {ol.format.filter.LogicalBinary}
- * @api
- */
-ol.format.filter.And = function(conditionA, conditionB) {
-  ol.format.filter.LogicalBinary.call(this, 'And', conditionA, conditionB);
-};
-ol.inherits(ol.format.filter.And, ol.format.filter.LogicalBinary);
-
-goog.provide('ol.format.filter.Bbox');
-
-goog.require('ol');
-goog.require('ol.format.filter.Filter');
-
-
-/**
- * @classdesc
- * Represents a `<BBOX>` operator to test whether a geometry-valued property
- * intersects a fixed bounding box
- *
- * @constructor
- * @param {!string} geometryName Geometry name to use.
- * @param {!ol.Extent} extent Extent.
- * @param {string=} opt_srsName SRS name. No srsName attribute will be
- *    set on geometries when this is not provided.
- * @extends {ol.format.filter.Filter}
- * @api
- */
-ol.format.filter.Bbox = function(geometryName, extent, opt_srsName) {
-
-  ol.format.filter.Filter.call(this, 'BBOX');
-
-  /**
-   * @public
-   * @type {!string}
-   */
-  this.geometryName = geometryName;
-
-  /**
-   * @public
-   * @type {ol.Extent}
-   */
-  this.extent = extent;
-
-  /**
-   * @public
-   * @type {string|undefined}
-   */
-  this.srsName = opt_srsName;
-};
-ol.inherits(ol.format.filter.Bbox, ol.format.filter.Filter);
-
-goog.provide('ol.format.filter.Comparison');
-
-goog.require('ol');
-goog.require('ol.format.filter.Filter');
-
-
-/**
- * @classdesc
- * Abstract class; normally only used for creating subclasses and not instantiated in apps.
- * Base class for WFS GetFeature property comparison filters.
- *
- * @constructor
- * @param {!string} tagName The XML tag name for this filter.
- * @param {!string} propertyName Name of the context property to compare.
- * @extends {ol.format.filter.Filter}
- * @api
- */
-ol.format.filter.Comparison = function(tagName, propertyName) {
-
-  ol.format.filter.Filter.call(this, tagName);
-
-  /**
-   * @public
-   * @type {!string}
-   */
-  this.propertyName = propertyName;
-};
-ol.inherits(ol.format.filter.Comparison, ol.format.filter.Filter);
-
-goog.provide('ol.format.filter.ComparisonBinary');
-
-goog.require('ol');
-goog.require('ol.format.filter.Comparison');
-
-
-/**
- * @classdesc
- * Abstract class; normally only used for creating subclasses and not instantiated in apps.
- * Base class for WFS GetFeature property binary comparison filters.
- *
- * @constructor
- * @param {!string} tagName The XML tag name for this filter.
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!(string|number)} expression The value to compare.
- * @param {boolean=} opt_matchCase Case-sensitive?
- * @extends {ol.format.filter.Comparison}
- * @api
- */
-ol.format.filter.ComparisonBinary = function(
-    tagName, propertyName, expression, opt_matchCase) {
-
-  ol.format.filter.Comparison.call(this, tagName, propertyName);
-
-  /**
-   * @public
-   * @type {!(string|number)}
-   */
-  this.expression = expression;
-
-  /**
-   * @public
-   * @type {boolean|undefined}
-   */
-  this.matchCase = opt_matchCase;
-};
-ol.inherits(ol.format.filter.ComparisonBinary, ol.format.filter.Comparison);
-
-goog.provide('ol.format.filter.EqualTo');
-
-goog.require('ol');
-goog.require('ol.format.filter.ComparisonBinary');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsEqualTo>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!(string|number)} expression The value to compare.
- * @param {boolean=} opt_matchCase Case-sensitive?
- * @extends {ol.format.filter.ComparisonBinary}
- * @api
- */
-ol.format.filter.EqualTo = function(propertyName, expression, opt_matchCase) {
-  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsEqualTo', propertyName, expression, opt_matchCase);
-};
-ol.inherits(ol.format.filter.EqualTo, ol.format.filter.ComparisonBinary);
-
-goog.provide('ol.format.filter.GreaterThan');
-
-goog.require('ol');
-goog.require('ol.format.filter.ComparisonBinary');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsGreaterThan>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @extends {ol.format.filter.ComparisonBinary}
- * @api
- */
-ol.format.filter.GreaterThan = function(propertyName, expression) {
-  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThan', propertyName, expression);
-};
-ol.inherits(ol.format.filter.GreaterThan, ol.format.filter.ComparisonBinary);
-
-goog.provide('ol.format.filter.GreaterThanOrEqualTo');
-
-goog.require('ol');
-goog.require('ol.format.filter.ComparisonBinary');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsGreaterThanOrEqualTo>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @extends {ol.format.filter.ComparisonBinary}
- * @api
- */
-ol.format.filter.GreaterThanOrEqualTo = function(propertyName, expression) {
-  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThanOrEqualTo', propertyName, expression);
-};
-ol.inherits(ol.format.filter.GreaterThanOrEqualTo, ol.format.filter.ComparisonBinary);
-
-goog.provide('ol.format.filter.Spatial');
-
-goog.require('ol');
-goog.require('ol.format.filter.Filter');
-
-
-/**
- * @classdesc
- * Represents a spatial operator to test whether a geometry-valued property
- * relates to a given geometry.
- *
- * @constructor
- * @param {!string} tagName The XML tag name for this filter.
- * @param {!string} geometryName Geometry name to use.
- * @param {!ol.geom.Geometry} geometry Geometry.
- * @param {string=} opt_srsName SRS name. No srsName attribute will be
- *    set on geometries when this is not provided.
- * @extends {ol.format.filter.Filter}
- * @api
- */
-ol.format.filter.Spatial = function(tagName, geometryName, geometry, opt_srsName) {
-
-  ol.format.filter.Filter.call(this, tagName);
-
-  /**
-   * @public
-   * @type {!string}
-   */
-  this.geometryName = geometryName || 'the_geom';
-
-  /**
-   * @public
-   * @type {ol.geom.Geometry}
-   */
-  this.geometry = geometry;
-
-  /**
-   * @public
-   * @type {string|undefined}
-   */
-  this.srsName = opt_srsName;
-};
-ol.inherits(ol.format.filter.Spatial, ol.format.filter.Filter);
-
-goog.provide('ol.format.filter.Intersects');
-
-goog.require('ol');
-goog.require('ol.format.filter.Spatial');
-
-
-/**
- * @classdesc
- * Represents a `<Intersects>` operator to test whether a geometry-valued property
- * intersects a given geometry.
- *
- * @constructor
- * @param {!string} geometryName Geometry name to use.
- * @param {!ol.geom.Geometry} geometry Geometry.
- * @param {string=} opt_srsName SRS name. No srsName attribute will be
- *    set on geometries when this is not provided.
- * @extends {ol.format.filter.Spatial}
- * @api
- */
-ol.format.filter.Intersects = function(geometryName, geometry, opt_srsName) {
-
-  ol.format.filter.Spatial.call(this, 'Intersects', geometryName, geometry, opt_srsName);
-
-};
-ol.inherits(ol.format.filter.Intersects, ol.format.filter.Spatial);
-
-goog.provide('ol.format.filter.IsBetween');
-
-goog.require('ol');
-goog.require('ol.format.filter.Comparison');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsBetween>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} lowerBoundary The lower bound of the range.
- * @param {!number} upperBoundary The upper bound of the range.
- * @extends {ol.format.filter.Comparison}
- * @api
- */
-ol.format.filter.IsBetween = function(propertyName, lowerBoundary, upperBoundary) {
-  ol.format.filter.Comparison.call(this, 'PropertyIsBetween', propertyName);
-
-  /**
-   * @public
-   * @type {!number}
-   */
-  this.lowerBoundary = lowerBoundary;
-
-  /**
-   * @public
-   * @type {!number}
-   */
-  this.upperBoundary = upperBoundary;
-};
-ol.inherits(ol.format.filter.IsBetween, ol.format.filter.Comparison);
-
-goog.provide('ol.format.filter.IsLike');
-
-goog.require('ol');
-goog.require('ol.format.filter.Comparison');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsLike>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!string} pattern Text pattern.
- * @param {string=} opt_wildCard Pattern character which matches any sequence of
- *    zero or more string characters. Default is '*'.
- * @param {string=} opt_singleChar pattern character which matches any single
- *    string character. Default is '.'.
- * @param {string=} opt_escapeChar Escape character which can be used to escape
- *    the pattern characters. Default is '!'.
- * @param {boolean=} opt_matchCase Case-sensitive?
- * @extends {ol.format.filter.Comparison}
- * @api
- */
-ol.format.filter.IsLike = function(propertyName, pattern,
-    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) {
-  ol.format.filter.Comparison.call(this, 'PropertyIsLike', propertyName);
-
-  /**
-   * @public
-   * @type {!string}
-   */
-  this.pattern = pattern;
-
-  /**
-   * @public
-   * @type {!string}
-   */
-  this.wildCard = (opt_wildCard !== undefined) ? opt_wildCard : '*';
-
-  /**
-   * @public
-   * @type {!string}
-   */
-  this.singleChar = (opt_singleChar !== undefined) ? opt_singleChar : '.';
-
-  /**
-   * @public
-   * @type {!string}
-   */
-  this.escapeChar = (opt_escapeChar !== undefined) ? opt_escapeChar : '!';
-
-  /**
-   * @public
-   * @type {boolean|undefined}
-   */
-  this.matchCase = opt_matchCase;
-};
-ol.inherits(ol.format.filter.IsLike, ol.format.filter.Comparison);
-
-goog.provide('ol.format.filter.IsNull');
-
-goog.require('ol');
-goog.require('ol.format.filter.Comparison');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsNull>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @extends {ol.format.filter.Comparison}
- * @api
- */
-ol.format.filter.IsNull = function(propertyName) {
-  ol.format.filter.Comparison.call(this, 'PropertyIsNull', propertyName);
-};
-ol.inherits(ol.format.filter.IsNull, ol.format.filter.Comparison);
-
-goog.provide('ol.format.filter.LessThan');
-
-goog.require('ol');
-goog.require('ol.format.filter.ComparisonBinary');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsLessThan>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @extends {ol.format.filter.ComparisonBinary}
- * @api
- */
-ol.format.filter.LessThan = function(propertyName, expression) {
-  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThan', propertyName, expression);
-};
-ol.inherits(ol.format.filter.LessThan, ol.format.filter.ComparisonBinary);
-
-goog.provide('ol.format.filter.LessThanOrEqualTo');
-
-goog.require('ol');
-goog.require('ol.format.filter.ComparisonBinary');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsLessThanOrEqualTo>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @extends {ol.format.filter.ComparisonBinary}
- * @api
- */
-ol.format.filter.LessThanOrEqualTo = function(propertyName, expression) {
-  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThanOrEqualTo', propertyName, expression);
-};
-ol.inherits(ol.format.filter.LessThanOrEqualTo, ol.format.filter.ComparisonBinary);
-
-goog.provide('ol.format.filter.Not');
-
-goog.require('ol');
-goog.require('ol.format.filter.Logical');
-
-
-/**
- * @classdesc
- * Represents a logical `<Not>` operator for a filter condition.
- *
- * @constructor
- * @param {!ol.format.filter.Filter} condition Filter condition.
- * @extends {ol.format.filter.Logical}
- * @api
- */
-ol.format.filter.Not = function(condition) {
-
-  ol.format.filter.Logical.call(this, 'Not');
-
-  /**
-   * @public
-   * @type {!ol.format.filter.Filter}
-   */
-  this.condition = condition;
-};
-ol.inherits(ol.format.filter.Not, ol.format.filter.Logical);
-
-goog.provide('ol.format.filter.NotEqualTo');
-
-goog.require('ol');
-goog.require('ol.format.filter.ComparisonBinary');
-
-
-/**
- * @classdesc
- * Represents a `<PropertyIsNotEqualTo>` comparison operator.
- *
- * @constructor
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!(string|number)} expression The value to compare.
- * @param {boolean=} opt_matchCase Case-sensitive?
- * @extends {ol.format.filter.ComparisonBinary}
- * @api
- */
-ol.format.filter.NotEqualTo = function(propertyName, expression, opt_matchCase) {
-  ol.format.filter.ComparisonBinary.call(this, 'PropertyIsNotEqualTo', propertyName, expression, opt_matchCase);
-};
-ol.inherits(ol.format.filter.NotEqualTo, ol.format.filter.ComparisonBinary);
-
-goog.provide('ol.format.filter.Or');
-
-goog.require('ol');
-goog.require('ol.format.filter.LogicalBinary');
-
-
-/**
- * @classdesc
- * Represents a logical `<Or>` operator between two filter conditions.
- *
- * @constructor
- * @param {!ol.format.filter.Filter} conditionA First filter condition.
- * @param {!ol.format.filter.Filter} conditionB Second filter condition.
- * @extends {ol.format.filter.LogicalBinary}
- * @api
- */
-ol.format.filter.Or = function(conditionA, conditionB) {
-  ol.format.filter.LogicalBinary.call(this, 'Or', conditionA, conditionB);
-};
-ol.inherits(ol.format.filter.Or, ol.format.filter.LogicalBinary);
-
-goog.provide('ol.format.filter.Within');
-
-goog.require('ol');
-goog.require('ol.format.filter.Spatial');
-
-
-/**
- * @classdesc
- * Represents a `<Within>` operator to test whether a geometry-valued property
- * is within a given geometry.
- *
- * @constructor
- * @param {!string} geometryName Geometry name to use.
- * @param {!ol.geom.Geometry} geometry Geometry.
- * @param {string=} opt_srsName SRS name. No srsName attribute will be
- *    set on geometries when this is not provided.
- * @extends {ol.format.filter.Spatial}
- * @api
- */
-ol.format.filter.Within = function(geometryName, geometry, opt_srsName) {
-
-  ol.format.filter.Spatial.call(this, 'Within', geometryName, geometry, opt_srsName);
-
-};
-ol.inherits(ol.format.filter.Within, ol.format.filter.Spatial);
-
-goog.provide('ol.format.filter');
-
-goog.require('ol');
-goog.require('ol.format.filter.And');
-goog.require('ol.format.filter.Bbox');
-goog.require('ol.format.filter.EqualTo');
-goog.require('ol.format.filter.GreaterThan');
-goog.require('ol.format.filter.GreaterThanOrEqualTo');
-goog.require('ol.format.filter.Intersects');
-goog.require('ol.format.filter.IsBetween');
-goog.require('ol.format.filter.IsLike');
-goog.require('ol.format.filter.IsNull');
-goog.require('ol.format.filter.LessThan');
-goog.require('ol.format.filter.LessThanOrEqualTo');
-goog.require('ol.format.filter.Not');
-goog.require('ol.format.filter.NotEqualTo');
-goog.require('ol.format.filter.Or');
-goog.require('ol.format.filter.Within');
-
-
-/**
- * Create a logical `<And>` operator between two filter conditions.
- *
- * @param {!ol.format.filter.Filter} conditionA First filter condition.
- * @param {!ol.format.filter.Filter} conditionB Second filter condition.
- * @returns {!ol.format.filter.And} `<And>` operator.
- * @api
- */
-ol.format.filter.and = function(conditionA, conditionB) {
-  return new ol.format.filter.And(conditionA, conditionB);
-};
-
-
-/**
- * Create a logical `<Or>` operator between two filter conditions.
- *
- * @param {!ol.format.filter.Filter} conditionA First filter condition.
- * @param {!ol.format.filter.Filter} conditionB Second filter condition.
- * @returns {!ol.format.filter.Or} `<Or>` operator.
- * @api
- */
-ol.format.filter.or = function(conditionA, conditionB) {
-  return new ol.format.filter.Or(conditionA, conditionB);
-};
-
-
-/**
- * Represents a logical `<Not>` operator for a filter condition.
- *
- * @param {!ol.format.filter.Filter} condition Filter condition.
- * @returns {!ol.format.filter.Not} `<Not>` operator.
- * @api
- */
-ol.format.filter.not = function(condition) {
-  return new ol.format.filter.Not(condition);
-};
-
-
-/**
- * Create a `<BBOX>` operator to test whether a geometry-valued property
- * intersects a fixed bounding box
- *
- * @param {!string} geometryName Geometry name to use.
- * @param {!ol.Extent} extent Extent.
- * @param {string=} opt_srsName SRS name. No srsName attribute will be
- *    set on geometries when this is not provided.
- * @returns {!ol.format.filter.Bbox} `<BBOX>` operator.
- * @api
- */
-ol.format.filter.bbox = function(geometryName, extent, opt_srsName) {
-  return new ol.format.filter.Bbox(geometryName, extent, opt_srsName);
-};
-
-/**
- * Create a `<Intersects>` operator to test whether a geometry-valued property
- * intersects a given geometry.
- *
- * @param {!string} geometryName Geometry name to use.
- * @param {!ol.geom.Geometry} geometry Geometry.
- * @param {string=} opt_srsName SRS name. No srsName attribute will be
- *    set on geometries when this is not provided.
- * @returns {!ol.format.filter.Intersects} `<Intersects>` operator.
- * @api
- */
-ol.format.filter.intersects = function(geometryName, geometry, opt_srsName) {
-  return new ol.format.filter.Intersects(geometryName, geometry, opt_srsName);
-};
-
-/**
- * Create a `<Within>` operator to test whether a geometry-valued property
- * is within a given geometry.
- *
- * @param {!string} geometryName Geometry name to use.
- * @param {!ol.geom.Geometry} geometry Geometry.
- * @param {string=} opt_srsName SRS name. No srsName attribute will be
- *    set on geometries when this is not provided.
- * @returns {!ol.format.filter.Within} `<Within>` operator.
- * @api
- */
-ol.format.filter.within = function(geometryName, geometry, opt_srsName) {
-  return new ol.format.filter.Within(geometryName, geometry, opt_srsName);
-};
-
-
-/**
- * Creates a `<PropertyIsEqualTo>` comparison operator.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!(string|number)} expression The value to compare.
- * @param {boolean=} opt_matchCase Case-sensitive?
- * @returns {!ol.format.filter.EqualTo} `<PropertyIsEqualTo>` operator.
- * @api
- */
-ol.format.filter.equalTo = function(propertyName, expression, opt_matchCase) {
-  return new ol.format.filter.EqualTo(propertyName, expression, opt_matchCase);
-};
-
-
-/**
- * Creates a `<PropertyIsNotEqualTo>` comparison operator.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!(string|number)} expression The value to compare.
- * @param {boolean=} opt_matchCase Case-sensitive?
- * @returns {!ol.format.filter.NotEqualTo} `<PropertyIsNotEqualTo>` operator.
- * @api
- */
-ol.format.filter.notEqualTo = function(propertyName, expression, opt_matchCase) {
-  return new ol.format.filter.NotEqualTo(propertyName, expression, opt_matchCase);
-};
-
-
-/**
- * Creates a `<PropertyIsLessThan>` comparison operator.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @returns {!ol.format.filter.LessThan} `<PropertyIsLessThan>` operator.
- * @api
- */
-ol.format.filter.lessThan = function(propertyName, expression) {
-  return new ol.format.filter.LessThan(propertyName, expression);
-};
-
-
-/**
- * Creates a `<PropertyIsLessThanOrEqualTo>` comparison operator.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @returns {!ol.format.filter.LessThanOrEqualTo} `<PropertyIsLessThanOrEqualTo>` operator.
- * @api
- */
-ol.format.filter.lessThanOrEqualTo = function(propertyName, expression) {
-  return new ol.format.filter.LessThanOrEqualTo(propertyName, expression);
-};
-
-
-/**
- * Creates a `<PropertyIsGreaterThan>` comparison operator.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @returns {!ol.format.filter.GreaterThan} `<PropertyIsGreaterThan>` operator.
- * @api
- */
-ol.format.filter.greaterThan = function(propertyName, expression) {
-  return new ol.format.filter.GreaterThan(propertyName, expression);
-};
-
-
-/**
- * Creates a `<PropertyIsGreaterThanOrEqualTo>` comparison operator.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} expression The value to compare.
- * @returns {!ol.format.filter.GreaterThanOrEqualTo} `<PropertyIsGreaterThanOrEqualTo>` operator.
- * @api
- */
-ol.format.filter.greaterThanOrEqualTo = function(propertyName, expression) {
-  return new ol.format.filter.GreaterThanOrEqualTo(propertyName, expression);
-};
-
-
-/**
- * Creates a `<PropertyIsNull>` comparison operator to test whether a property value
- * is null.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @returns {!ol.format.filter.IsNull} `<PropertyIsNull>` operator.
- * @api
- */
-ol.format.filter.isNull = function(propertyName) {
-  return new ol.format.filter.IsNull(propertyName);
-};
-
-
-/**
- * Creates a `<PropertyIsBetween>` comparison operator to test whether an expression
- * value lies within a range given by a lower and upper bound (inclusive).
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!number} lowerBoundary The lower bound of the range.
- * @param {!number} upperBoundary The upper bound of the range.
- * @returns {!ol.format.filter.IsBetween} `<PropertyIsBetween>` operator.
- * @api
- */
-ol.format.filter.between = function(propertyName, lowerBoundary, upperBoundary) {
-  return new ol.format.filter.IsBetween(propertyName, lowerBoundary, upperBoundary);
-};
-
-
-/**
- * Represents a `<PropertyIsLike>` comparison operator that matches a string property
- * value against a text pattern.
- *
- * @param {!string} propertyName Name of the context property to compare.
- * @param {!string} pattern Text pattern.
- * @param {string=} opt_wildCard Pattern character which matches any sequence of
- *    zero or more string characters. Default is '*'.
- * @param {string=} opt_singleChar pattern character which matches any single
- *    string character. Default is '.'.
- * @param {string=} opt_escapeChar Escape character which can be used to escape
- *    the pattern characters. Default is '!'.
- * @param {boolean=} opt_matchCase Case-sensitive?
- * @returns {!ol.format.filter.IsLike} `<PropertyIsLike>` operator.
- * @api
- */
-ol.format.filter.like = function(propertyName, pattern,
-    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) {
-  return new ol.format.filter.IsLike(propertyName, pattern,
-    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase);
-};
-
-goog.provide('ol.geom.GeometryCollection');
-
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.obj');
-
-
-/**
- * @classdesc
- * An array of {@link ol.geom.Geometry} objects.
- *
- * @constructor
- * @extends {ol.geom.Geometry}
- * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries.
- * @api stable
- */
-ol.geom.GeometryCollection = function(opt_geometries) {
-
-  ol.geom.Geometry.call(this);
-
-  /**
-   * @private
-   * @type {Array.<ol.geom.Geometry>}
-   */
-  this.geometries_ = opt_geometries ? opt_geometries : null;
-
-  this.listenGeometriesChange_();
-};
-ol.inherits(ol.geom.GeometryCollection, ol.geom.Geometry);
-
-
-/**
- * @param {Array.<ol.geom.Geometry>} geometries Geometries.
- * @private
- * @return {Array.<ol.geom.Geometry>} Cloned geometries.
- */
-ol.geom.GeometryCollection.cloneGeometries_ = function(geometries) {
-  var clonedGeometries = [];
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    clonedGeometries.push(geometries[i].clone());
-  }
-  return clonedGeometries;
-};
-
-
-/**
- * @private
- */
-ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() {
-  var i, ii;
-  if (!this.geometries_) {
-    return;
-  }
-  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
-    ol.events.unlisten(
-        this.geometries_[i], ol.events.EventType.CHANGE,
-        this.changed, this);
-  }
-};
-
-
-/**
- * @private
- */
-ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() {
-  var i, ii;
-  if (!this.geometries_) {
-    return;
-  }
-  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
-    ol.events.listen(
-        this.geometries_[i], ol.events.EventType.CHANGE,
-        this.changed, this);
-  }
-};
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.GeometryCollection} Clone.
- * @api stable
- */
-ol.geom.GeometryCollection.prototype.clone = function() {
-  var geometryCollection = new ol.geom.GeometryCollection(null);
-  geometryCollection.setGeometries(this.geometries_);
-  return geometryCollection;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.GeometryCollection.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  if (minSquaredDistance <
-      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
-    return minSquaredDistance;
-  }
-  var geometries = this.geometries_;
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    minSquaredDistance = geometries[i].closestPointXY(
-        x, y, closestPoint, minSquaredDistance);
-  }
-  return minSquaredDistance;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.GeometryCollection.prototype.containsXY = function(x, y) {
-  var geometries = this.geometries_;
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    if (geometries[i].containsXY(x, y)) {
-      return true;
-    }
-  }
-  return false;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.GeometryCollection.prototype.computeExtent = function(extent) {
-  ol.extent.createOrUpdateEmpty(extent);
-  var geometries = this.geometries_;
-  for (var i = 0, ii = geometries.length; i < ii; ++i) {
-    ol.extent.extend(extent, geometries[i].getExtent());
-  }
-  return extent;
-};
-
-
-/**
- * Return the geometries that make up this geometry collection.
- * @return {Array.<ol.geom.Geometry>} Geometries.
- * @api stable
- */
-ol.geom.GeometryCollection.prototype.getGeometries = function() {
-  return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_);
-};
-
-
-/**
- * @return {Array.<ol.geom.Geometry>} Geometries.
- */
-ol.geom.GeometryCollection.prototype.getGeometriesArray = function() {
-  return this.geometries_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.GeometryCollection.prototype.getSimplifiedGeometry = function(squaredTolerance) {
-  if (this.simplifiedGeometryRevision != this.getRevision()) {
-    ol.obj.clear(this.simplifiedGeometryCache);
-    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
-    this.simplifiedGeometryRevision = this.getRevision();
-  }
-  if (squaredTolerance < 0 ||
-      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
-       squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) {
-    return this;
-  }
-  var key = squaredTolerance.toString();
-  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
-    return this.simplifiedGeometryCache[key];
-  } else {
-    var simplifiedGeometries = [];
-    var geometries = this.geometries_;
-    var simplified = false;
-    var i, ii;
-    for (i = 0, ii = geometries.length; i < ii; ++i) {
-      var geometry = geometries[i];
-      var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
-      simplifiedGeometries.push(simplifiedGeometry);
-      if (simplifiedGeometry !== geometry) {
-        simplified = true;
-      }
-    }
-    if (simplified) {
-      var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null);
-      simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries);
-      this.simplifiedGeometryCache[key] = simplifiedGeometryCollection;
-      return simplifiedGeometryCollection;
-    } else {
-      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
-      return this;
-    }
-  }
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.GeometryCollection.prototype.getType = function() {
-  return ol.geom.GeometryType.GEOMETRY_COLLECTION;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.GeometryCollection.prototype.intersectsExtent = function(extent) {
-  var geometries = this.geometries_;
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    if (geometries[i].intersectsExtent(extent)) {
-      return true;
-    }
-  }
-  return false;
-};
-
-
-/**
- * @return {boolean} Is empty.
- */
-ol.geom.GeometryCollection.prototype.isEmpty = function() {
-  return this.geometries_.length === 0;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.geom.GeometryCollection.prototype.rotate = function(angle, anchor) {
-  var geometries = this.geometries_;
-  for (var i = 0, ii = geometries.length; i < ii; ++i) {
-    geometries[i].rotate(angle, anchor);
-  }
-  this.changed();
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.geom.GeometryCollection.prototype.scale = function(sx, opt_sy, opt_anchor) {
-  var anchor = opt_anchor;
-  if (!anchor) {
-    anchor = ol.extent.getCenter(this.getExtent());
-  }
-  var geometries = this.geometries_;
-  for (var i = 0, ii = geometries.length; i < ii; ++i) {
-    geometries[i].scale(sx, opt_sy, anchor);
-  }
-  this.changed();
-};
-
-
-/**
- * Set the geometries that make up this geometry collection.
- * @param {Array.<ol.geom.Geometry>} geometries Geometries.
- * @api stable
- */
-ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) {
-  this.setGeometriesArray(
-      ol.geom.GeometryCollection.cloneGeometries_(geometries));
-};
-
-
-/**
- * @param {Array.<ol.geom.Geometry>} geometries Geometries.
- */
-ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) {
-  this.unlistenGeometriesChange_();
-  this.geometries_ = geometries;
-  this.listenGeometriesChange_();
-  this.changed();
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.GeometryCollection.prototype.applyTransform = function(transformFn) {
-  var geometries = this.geometries_;
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    geometries[i].applyTransform(transformFn);
-  }
-  this.changed();
-};
-
-
-/**
- * Translate the geometry.
- * @param {number} deltaX Delta X.
- * @param {number} deltaY Delta Y.
- * @api
- */
-ol.geom.GeometryCollection.prototype.translate = function(deltaX, deltaY) {
-  var geometries = this.geometries_;
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    geometries[i].translate(deltaX, deltaY);
-  }
-  this.changed();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.GeometryCollection.prototype.disposeInternal = function() {
-  this.unlistenGeometriesChange_();
-  ol.geom.Geometry.prototype.disposeInternal.call(this);
-};
-
-// TODO: serialize dataProjection as crs member when writing
-// see https://github.com/openlayers/ol3/issues/2078
-
-goog.provide('ol.format.GeoJSON');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.JSONFeature');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.obj');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the GeoJSON format.
- *
- * @constructor
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.GeoJSONOptions=} opt_options Options.
- * @api stable
- */
-ol.format.GeoJSON = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.JSONFeature.call(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get(
-      options.defaultDataProjection ?
-          options.defaultDataProjection : 'EPSG:4326');
-
-
-  if (options.featureProjection) {
-    this.defaultFeatureProjection = ol.proj.get(options.featureProjection);
-  }
-
-  /**
-   * Name of the geometry attribute for features.
-   * @type {string|undefined}
-   * @private
-   */
-  this.geometryName_ = options.geometryName;
-
-};
-ol.inherits(ol.format.GeoJSON, ol.format.JSONFeature);
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.GeoJSON.EXTENSIONS_ = ['.geojson'];
-
-
-/**
- * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @private
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.GeoJSON.readGeometry_ = function(object, opt_options) {
-  if (!object) {
-    return null;
-  }
-  var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type];
-  return /** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(
-          geometryReader(object), false, opt_options));
-};
-
-
-/**
- * @param {GeoJSONGeometryCollection} object Object.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @private
- * @return {ol.geom.GeometryCollection} Geometry collection.
- */
-ol.format.GeoJSON.readGeometryCollectionGeometry_ = function(
-    object, opt_options) {
-  ol.DEBUG && console.assert(object.type == 'GeometryCollection',
-      'object.type should be GeometryCollection');
-  var geometries = object.geometries.map(
-      /**
-       * @param {GeoJSONGeometry} geometry Geometry.
-       * @return {ol.geom.Geometry} geometry Geometry.
-       */
-      function(geometry) {
-        return ol.format.GeoJSON.readGeometry_(geometry, opt_options);
-      });
-  return new ol.geom.GeometryCollection(geometries);
-};
-
-
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Point} Point.
- */
-ol.format.GeoJSON.readPointGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.type == 'Point',
-      'object.type should be Point');
-  return new ol.geom.Point(object.coordinates);
-};
-
-
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.LineString} LineString.
- */
-ol.format.GeoJSON.readLineStringGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.type == 'LineString',
-      'object.type should be LineString');
-  return new ol.geom.LineString(object.coordinates);
-};
-
-
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiLineString} MultiLineString.
- */
-ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.type == 'MultiLineString',
-      'object.type should be MultiLineString');
-  return new ol.geom.MultiLineString(object.coordinates);
-};
-
-
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiPoint} MultiPoint.
- */
-ol.format.GeoJSON.readMultiPointGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.type == 'MultiPoint',
-      'object.type should be MultiPoint');
-  return new ol.geom.MultiPoint(object.coordinates);
-};
-
-
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.MultiPolygon} MultiPolygon.
- */
-ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.type == 'MultiPolygon',
-      'object.type should be MultiPolygon');
-  return new ol.geom.MultiPolygon(object.coordinates);
-};
-
-
-/**
- * @param {GeoJSONGeometry} object Object.
- * @private
- * @return {ol.geom.Polygon} Polygon.
- */
-ol.format.GeoJSON.readPolygonGeometry_ = function(object) {
-  ol.DEBUG && console.assert(object.type == 'Polygon',
-      'object.type should be Polygon');
-  return new ol.geom.Polygon(object.coordinates);
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry.
- */
-ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) {
-  var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()];
-  return geometryWriter(/** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
-      opt_options);
-};
-
-
-/**
- * @param {ol.geom.Geometry} geometry Geometry.
- * @private
- * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection.
- */
-ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) {
-  return /** @type {GeoJSONGeometryCollection} */ ({
-    type: 'GeometryCollection',
-    geometries: []
-  });
-};
-
-
-/**
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometryCollection} GeoJSON geometry collection.
- */
-ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function(
-    geometry, opt_options) {
-  var geometries = geometry.getGeometriesArray().map(function(geometry) {
-    var options = ol.obj.assign({}, opt_options);
-    delete options.featureProjection;
-    return ol.format.GeoJSON.writeGeometry_(geometry, options);
-  });
-  return /** @type {GeoJSONGeometryCollection} */ ({
-    type: 'GeometryCollection',
-    geometries: geometries
-  });
-};
-
-
-/**
- * @param {ol.geom.LineString} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
- */
-ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
-  return /** @type {GeoJSONGeometry} */ ({
-    type: 'LineString',
-    coordinates: geometry.getCoordinates()
-  });
-};
-
-
-/**
- * @param {ol.geom.MultiLineString} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
- */
-ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) {
-  return /** @type {GeoJSONGeometry} */ ({
-    type: 'MultiLineString',
-    coordinates: geometry.getCoordinates()
-  });
-};
-
-
-/**
- * @param {ol.geom.MultiPoint} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
- */
-ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
-  return /** @type {GeoJSONGeometry} */ ({
-    type: 'MultiPoint',
-    coordinates: geometry.getCoordinates()
-  });
-};
-
-
-/**
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
- */
-ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry, opt_options) {
-  var right;
-  if (opt_options) {
-    right = opt_options.rightHanded;
-  }
-  return /** @type {GeoJSONGeometry} */ ({
-    type: 'MultiPolygon',
-    coordinates: geometry.getCoordinates(right)
-  });
-};
-
-
-/**
- * @param {ol.geom.Point} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
- */
-ol.format.GeoJSON.writePointGeometry_ = function(geometry, opt_options) {
-  return /** @type {GeoJSONGeometry} */ ({
-    type: 'Point',
-    coordinates: geometry.getCoordinates()
-  });
-};
-
-
-/**
- * @param {ol.geom.Polygon} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @private
- * @return {GeoJSONGeometry} GeoJSON geometry.
- */
-ol.format.GeoJSON.writePolygonGeometry_ = function(geometry, opt_options) {
-  var right;
-  if (opt_options) {
-    right = opt_options.rightHanded;
-  }
-  return /** @type {GeoJSONGeometry} */ ({
-    type: 'Polygon',
-    coordinates: geometry.getCoordinates(right)
-  });
-};
-
-
-/**
- * @const
- * @private
- * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>}
- */
-ol.format.GeoJSON.GEOMETRY_READERS_ = {
-  'Point': ol.format.GeoJSON.readPointGeometry_,
-  'LineString': ol.format.GeoJSON.readLineStringGeometry_,
-  'Polygon': ol.format.GeoJSON.readPolygonGeometry_,
-  'MultiPoint': ol.format.GeoJSON.readMultiPointGeometry_,
-  'MultiLineString': ol.format.GeoJSON.readMultiLineStringGeometry_,
-  'MultiPolygon': ol.format.GeoJSON.readMultiPolygonGeometry_,
-  'GeometryCollection': ol.format.GeoJSON.readGeometryCollectionGeometry_
-};
-
-
-/**
- * @const
- * @private
- * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (GeoJSONGeometry|GeoJSONGeometryCollection)>}
- */
-ol.format.GeoJSON.GEOMETRY_WRITERS_ = {
-  'Point': ol.format.GeoJSON.writePointGeometry_,
-  'LineString': ol.format.GeoJSON.writeLineStringGeometry_,
-  'Polygon': ol.format.GeoJSON.writePolygonGeometry_,
-  'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_,
-  'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_,
-  'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_,
-  'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_,
-  'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.GeoJSON.prototype.getExtensions = function() {
-  return ol.format.GeoJSON.EXTENSIONS_;
-};
-
-
-/**
- * Read a feature from a GeoJSON Feature source.  Only works for Feature or
- * geometry types.  Use {@link ol.format.GeoJSON#readFeatures} to read
- * FeatureCollection source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
- */
-ol.format.GeoJSON.prototype.readFeature;
-
-
-/**
- * Read all features from a GeoJSON source.  Works for all GeoJSON types.
- * If the source includes only geometries, features will be created with those
- * geometries.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.GeoJSON.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.GeoJSON.prototype.readFeatureFromObject = function(
-    object, opt_options) {
-
-  ol.DEBUG && console.assert(object.type !== 'FeatureCollection', 'Expected a Feature or geometry');
-
-  /**
-   * @type {GeoJSONFeature}
-   */
-  var geoJSONFeature = null;
-  if (object.type === 'Feature') {
-    geoJSONFeature = /** @type {GeoJSONFeature} */ (object);
-  } else {
-    geoJSONFeature = /** @type {GeoJSONFeature} */ ({
-      type: 'Feature',
-      geometry: /** @type {GeoJSONGeometry|GeoJSONGeometryCollection} */ (object)
-    });
-  }
-
-  var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry, opt_options);
-  var feature = new ol.Feature();
-  if (this.geometryName_) {
-    feature.setGeometryName(this.geometryName_);
-  }
-  feature.setGeometry(geometry);
-  if (geoJSONFeature.id !== undefined) {
-    feature.setId(geoJSONFeature.id);
-  }
-  if (geoJSONFeature.properties) {
-    feature.setProperties(geoJSONFeature.properties);
-  }
-  return feature;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.GeoJSON.prototype.readFeaturesFromObject = function(
-    object, opt_options) {
-  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
-  /** @type {Array.<ol.Feature>} */
-  var features = null;
-  if (geoJSONObject.type === 'FeatureCollection') {
-    var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */
-        (object);
-    features = [];
-    var geoJSONFeatures = geoJSONFeatureCollection.features;
-    var i, ii;
-    for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) {
-      features.push(this.readFeatureFromObject(geoJSONFeatures[i],
-          opt_options));
-    }
-  } else {
-    features = [this.readFeatureFromObject(object, opt_options)];
-  }
-  return features;
-};
-
-
-/**
- * Read a geometry from a GeoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
- * @api stable
- */
-ol.format.GeoJSON.prototype.readGeometry;
-
-
-/**
- * @inheritDoc
- */
-ol.format.GeoJSON.prototype.readGeometryFromObject = function(
-    object, opt_options) {
-  return ol.format.GeoJSON.readGeometry_(
-      /** @type {GeoJSONGeometry} */ (object), opt_options);
-};
-
-
-/**
- * Read the projection from a GeoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.GeoJSON.prototype.readProjection;
-
-
-/**
- * @inheritDoc
- */
-ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) {
-  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
-  var crs = geoJSONObject.crs;
-  var projection;
-  if (crs) {
-    if (crs.type == 'name') {
-      projection = ol.proj.get(crs.properties.name);
-    } else if (crs.type == 'EPSG') {
-      // 'EPSG' is not part of the GeoJSON specification, but is generated by
-      // GeoServer.
-      // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996
-      // is fixed and widely deployed.
-      projection = ol.proj.get('EPSG:' + crs.properties.code);
-    } else {
-      ol.asserts.assert(false, 36); // Unknown SRS type
-    }
-  } else {
-    projection = this.defaultDataProjection;
-  }
-  return /** @type {ol.proj.Projection} */ (projection);
-};
-
-
-/**
- * Encode a feature as a GeoJSON Feature string.
- *
- * @function
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} GeoJSON.
- * @api stable
- */
-ol.format.GeoJSON.prototype.writeFeature;
-
-
-/**
- * Encode a feature as a GeoJSON Feature object.
- *
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {GeoJSONFeature} Object.
- * @api stable
- */
-ol.format.GeoJSON.prototype.writeFeatureObject = function(feature, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-
-  var object = /** @type {GeoJSONFeature} */ ({
-    'type': 'Feature'
-  });
-  var id = feature.getId();
-  if (id !== undefined) {
-    object.id = id;
-  }
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    object.geometry =
-        ol.format.GeoJSON.writeGeometry_(geometry, opt_options);
-  } else {
-    object.geometry = null;
-  }
-  var properties = feature.getProperties();
-  delete properties[feature.getGeometryName()];
-  if (!ol.obj.isEmpty(properties)) {
-    object.properties = properties;
-  } else {
-    object.properties = null;
-  }
-  return object;
-};
-
-
-/**
- * Encode an array of features as GeoJSON.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} GeoJSON.
- * @api stable
- */
-ol.format.GeoJSON.prototype.writeFeatures;
-
-
-/**
- * Encode an array of features as a GeoJSON object.
- *
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {GeoJSONFeatureCollection} GeoJSON Object.
- * @api stable
- */
-ol.format.GeoJSON.prototype.writeFeaturesObject = function(features, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  var objects = [];
-  var i, ii;
-  for (i = 0, ii = features.length; i < ii; ++i) {
-    objects.push(this.writeFeatureObject(features[i], opt_options));
-  }
-  return /** @type {GeoJSONFeatureCollection} */ ({
-    type: 'FeatureCollection',
-    features: objects
-  });
-};
-
-
-/**
- * Encode a geometry as a GeoJSON string.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} GeoJSON.
- * @api stable
- */
-ol.format.GeoJSON.prototype.writeGeometry;
-
-
-/**
- * Encode a geometry as a GeoJSON object.
- *
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {GeoJSONGeometry|GeoJSONGeometryCollection} Object.
- * @api stable
- */
-ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry,
-    opt_options) {
-  return ol.format.GeoJSON.writeGeometry_(geometry,
-      this.adaptOptions(opt_options));
-};
-
-goog.provide('ol.format.XMLFeature');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for XML feature formats.
- *
- * @constructor
- * @extends {ol.format.Feature}
- */
-ol.format.XMLFeature = function() {
-
-  /**
-   * @type {XMLSerializer}
-   * @private
-   */
-  this.xmlSerializer_ = new XMLSerializer();
-
-  ol.format.Feature.call(this);
-};
-ol.inherits(ol.format.XMLFeature, ol.format.Feature);
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.getType = function() {
-  return ol.format.FormatType.XML;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFeatureFromDocument(
-        /** @type {Document} */ (source), opt_options);
-  } else if (ol.xml.isNode(source)) {
-    return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options);
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readFeatureFromDocument(doc, opt_options);
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {ol.Feature} Feature.
- */
-ol.format.XMLFeature.prototype.readFeatureFromDocument = function(
-    doc, opt_options) {
-  var features = this.readFeaturesFromDocument(doc, opt_options);
-  if (features.length > 0) {
-    return features[0];
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @abstract
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {ol.Feature} Feature.
- */
-ol.format.XMLFeature.prototype.readFeatureFromNode = function(node, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFeaturesFromDocument(
-        /** @type {Document} */ (source), opt_options);
-  } else if (ol.xml.isNode(source)) {
-    return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options);
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readFeaturesFromDocument(doc, opt_options);
-  } else {
-    return [];
-  }
-};
-
-
-/**
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.XMLFeature.prototype.readFeaturesFromDocument = function(
-    doc, opt_options) {
-  /** @type {Array.<ol.Feature>} */
-  var features = [];
-  var n;
-  for (n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      ol.array.extend(features, this.readFeaturesFromNode(n, opt_options));
-    }
-  }
-  return features;
-};
-
-
-/**
- * @abstract
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.XMLFeature.prototype.readFeaturesFromNode = function(node, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) {
-  if (ol.xml.isDocument(source)) {
-    return this.readGeometryFromDocument(
-        /** @type {Document} */ (source), opt_options);
-  } else if (ol.xml.isNode(source)) {
-    return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options);
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readGeometryFromDocument(doc, opt_options);
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @abstract
- * @param {Document} doc Document.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.XMLFeature.prototype.readGeometryFromDocument = function(doc, opt_options) {};
-
-
-/**
- * @abstract
- * @param {Node} node Node.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.XMLFeature.prototype.readGeometryFromNode = function(node, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.readProjection = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readProjectionFromDocument(/** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readProjectionFromNode(/** @type {Node} */ (source));
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readProjectionFromDocument(doc);
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @param {Document} doc Document.
- * @protected
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * @param {Node} node Node.
- * @protected
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) {
-  var node = this.writeFeatureNode(feature, opt_options);
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  return this.xmlSerializer_.serializeToString(node);
-};
-
-
-/**
- * @abstract
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @protected
- * @return {Node} Node.
- */
-ol.format.XMLFeature.prototype.writeFeatureNode = function(feature, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) {
-  var node = this.writeFeaturesNode(features, opt_options);
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  return this.xmlSerializer_.serializeToString(node);
-};
-
-
-/**
- * @abstract
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- */
-ol.format.XMLFeature.prototype.writeFeaturesNode = function(features, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) {
-  var node = this.writeGeometryNode(geometry, opt_options);
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  return this.xmlSerializer_.serializeToString(node);
-};
-
-
-/**
- * @abstract
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- */
-ol.format.XMLFeature.prototype.writeGeometryNode = function(geometry, opt_options) {};
-
-// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part
-// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect
-// envelopes/extents, only geometries!
-goog.provide('ol.format.GMLBase');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.obj');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Feature base format for reading and writing data in the GML format.
- * This class cannot be instantiated, it contains only base content that
- * is shared with versioned format classes ol.format.GML2 and
- * ol.format.GML3.
- *
- * @constructor
- * @param {olx.format.GMLOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.XMLFeature}
- */
-ol.format.GMLBase = function(opt_options) {
-  var options = /** @type {olx.format.GMLOptions} */
-      (opt_options ? opt_options : {});
-
-  /**
-   * @protected
-   * @type {Array.<string>|string|undefined}
-   */
-  this.featureType = options.featureType;
-
-  /**
-   * @protected
-   * @type {Object.<string, string>|string|undefined}
-   */
-  this.featureNS = options.featureNS;
-
-  /**
-   * @protected
-   * @type {string}
-   */
-  this.srsName = options.srsName;
-
-  /**
-   * @protected
-   * @type {string}
-   */
-  this.schemaLocation = '';
-
-  /**
-   * @type {Object.<string, Object.<string, Object>>}
-   */
-  this.FEATURE_COLLECTION_PARSERS = {};
-  this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS] = {
-    'featureMember': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readFeaturesInternal),
-    'featureMembers': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readFeaturesInternal)
-  };
-
-  ol.format.XMLFeature.call(this);
-};
-ol.inherits(ol.format.GMLBase, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml';
-
-
-/**
- * A regular expression that matches if a string only contains whitespace
- * characters. It will e.g. match `''`, `' '`, `'\n'` etc. The non-breaking
- * space (0xa0) is explicitly included as IE doesn't include it in its
- * definition of `\s`.
- *
- * Information from `goog.string.isEmptyOrWhitespace`: https://github.com/google/closure-library/blob/e877b1e/closure/goog/string/string.js#L156-L160
- *
- * @const
- * @type {RegExp}
- * @private
- */
-ol.format.GMLBase.ONLY_WHITESPACE_RE_ = /^[\s\xa0]*$/;
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<ol.Feature> | undefined} Features.
- */
-ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  var localName = node.localName;
-  var features = null;
-  if (localName == 'FeatureCollection') {
-    if (node.namespaceURI === 'http://www.opengis.net/wfs') {
-      features = ol.xml.pushParseAndPop([],
-          this.FEATURE_COLLECTION_PARSERS, node,
-          objectStack, this);
-    } else {
-      features = ol.xml.pushParseAndPop(null,
-          this.FEATURE_COLLECTION_PARSERS, node,
-          objectStack, this);
-    }
-  } else if (localName == 'featureMembers' || localName == 'featureMember') {
-    var context = objectStack[0];
-    var featureType = context['featureType'];
-    var featureNS = context['featureNS'];
-    var i, ii, prefix = 'p', defaultPrefix = 'p0';
-    if (!featureType && node.childNodes) {
-      featureType = [], featureNS = {};
-      for (i = 0, ii = node.childNodes.length; i < ii; ++i) {
-        var child = node.childNodes[i];
-        if (child.nodeType === 1) {
-          var ft = child.nodeName.split(':').pop();
-          if (featureType.indexOf(ft) === -1) {
-            var key = '';
-            var count = 0;
-            var uri = child.namespaceURI;
-            for (var candidate in featureNS) {
-              if (featureNS[candidate] === uri) {
-                key = candidate;
-                break;
-              }
-              ++count;
-            }
-            if (!key) {
-              key = prefix + count;
-              featureNS[key] = uri;
-            }
-            featureType.push(key + ':' + ft);
-          }
-        }
-      }
-      if (localName != 'featureMember') {
-        // recheck featureType for each featureMember
-        context['featureType'] = featureType;
-        context['featureNS'] = featureNS;
-      }
-    }
-    if (typeof featureNS === 'string') {
-      var ns = featureNS;
-      featureNS = {};
-      featureNS[defaultPrefix] = ns;
-    }
-    var parsersNS = {};
-    var featureTypes = Array.isArray(featureType) ? featureType : [featureType];
-    for (var p in featureNS) {
-      var parsers = {};
-      for (i = 0, ii = featureTypes.length; i < ii; ++i) {
-        var featurePrefix = featureTypes[i].indexOf(':') === -1 ?
-            defaultPrefix : featureTypes[i].split(':')[0];
-        if (featurePrefix === p) {
-          parsers[featureTypes[i].split(':').pop()] =
-              (localName == 'featureMembers') ?
-              ol.xml.makeArrayPusher(this.readFeatureElement, this) :
-              ol.xml.makeReplacer(this.readFeatureElement, this);
-        }
-      }
-      parsersNS[featureNS[p]] = parsers;
-    }
-    if (localName == 'featureMember') {
-      features = ol.xml.pushParseAndPop(undefined, parsersNS, node, objectStack);
-    } else {
-      features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack);
-    }
-  }
-  if (features === null) {
-    features = [];
-  }
-  return features;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.Geometry|undefined} Geometry.
- */
-ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) {
-  var context = /** @type {Object} */ (objectStack[0]);
-  context['srsName'] = node.firstElementChild.getAttribute('srsName');
-  /** @type {ol.geom.Geometry} */
-  var geometry = ol.xml.pushParseAndPop(null,
-      this.GEOMETRY_PARSERS_, node, objectStack, this);
-  if (geometry) {
-    return /** @type {ol.geom.Geometry} */ (
-        ol.format.Feature.transformWithOptions(geometry, false, context));
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.Feature} Feature.
- */
-ol.format.GMLBase.prototype.readFeatureElement = function(node, objectStack) {
-  var n;
-  var fid = node.getAttribute('fid') ||
-      ol.xml.getAttributeNS(node, ol.format.GMLBase.GMLNS, 'id');
-  var values = {}, geometryName;
-  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
-    var localName = n.localName;
-    // Assume attribute elements have one child node and that the child
-    // is a text or CDATA node (to be treated as text).
-    // Otherwise assume it is a geometry node.
-    if (n.childNodes.length === 0 ||
-        (n.childNodes.length === 1 &&
-        (n.firstChild.nodeType === 3 || n.firstChild.nodeType === 4))) {
-      var value = ol.xml.getAllTextContent(n, false);
-      if (ol.format.GMLBase.ONLY_WHITESPACE_RE_.test(value)) {
-        value = undefined;
-      }
-      values[localName] = value;
-    } else {
-      // boundedBy is an extent and must not be considered as a geometry
-      if (localName !== 'boundedBy') {
-        geometryName = localName;
-      }
-      values[localName] = this.readGeometryElement(n, objectStack);
-    }
-  }
-  var feature = new ol.Feature(values);
-  if (geometryName) {
-    feature.setGeometryName(geometryName);
-  }
-  if (fid) {
-    feature.setId(fid);
-  }
-  return feature;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.Point|undefined} Point.
- */
-ol.format.GMLBase.prototype.readPoint = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point');
-  var flatCoordinates =
-      this.readFlatCoordinatesFromNode_(node, objectStack);
-  if (flatCoordinates) {
-    var point = new ol.geom.Point(null);
-    ol.DEBUG && console.assert(flatCoordinates.length == 3,
-        'flatCoordinates should have a length of 3');
-    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return point;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.MultiPoint|undefined} MultiPoint.
- */
-ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'MultiPoint',
-      'localName should be MultiPoint');
-  /** @type {Array.<Array.<number>>} */
-  var coordinates = ol.xml.pushParseAndPop([],
-      this.MULTIPOINT_PARSERS_, node, objectStack, this);
-  if (coordinates) {
-    return new ol.geom.MultiPoint(coordinates);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.MultiLineString|undefined} MultiLineString.
- */
-ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'MultiLineString',
-      'localName should be MultiLineString');
-  /** @type {Array.<ol.geom.LineString>} */
-  var lineStrings = ol.xml.pushParseAndPop([],
-      this.MULTILINESTRING_PARSERS_, node, objectStack, this);
-  if (lineStrings) {
-    var multiLineString = new ol.geom.MultiLineString(null);
-    multiLineString.setLineStrings(lineStrings);
-    return multiLineString;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
- */
-ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'MultiPolygon',
-      'localName should be MultiPolygon');
-  /** @type {Array.<ol.geom.Polygon>} */
-  var polygons = ol.xml.pushParseAndPop([],
-      this.MULTIPOLYGON_PARSERS_, node, objectStack, this);
-  if (polygons) {
-    var multiPolygon = new ol.geom.MultiPolygon(null);
-    multiPolygon.setPolygons(polygons);
-    return multiPolygon;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'pointMember' ||
-      node.localName == 'pointMembers',
-      'localName should be pointMember or pointMembers');
-  ol.xml.parseNode(this.POINTMEMBER_PARSERS_,
-      node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GMLBase.prototype.lineStringMemberParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'lineStringMember' ||
-      node.localName == 'lineStringMembers',
-      'localName should be LineStringMember or LineStringMembers');
-  ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_,
-      node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GMLBase.prototype.polygonMemberParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'polygonMember' ||
-      node.localName == 'polygonMembers',
-      'localName should be polygonMember or polygonMembers');
-  ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node,
-      objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.LineString|undefined} LineString.
- */
-ol.format.GMLBase.prototype.readLineString = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LineString',
-      'localName should be LineString');
-  var flatCoordinates =
-      this.readFlatCoordinatesFromNode_(node, objectStack);
-  if (flatCoordinates) {
-    var lineString = new ol.geom.LineString(null);
-    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return lineString;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} LinearRing flat coordinates.
- */
-ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LinearRing',
-      'localName should be LinearRing');
-  var ring = ol.xml.pushParseAndPop(null,
-      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
-      objectStack, this);
-  if (ring) {
-    return ring;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.LinearRing|undefined} LinearRing.
- */
-ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LinearRing',
-      'localName should be LinearRing');
-  var flatCoordinates =
-      this.readFlatCoordinatesFromNode_(node, objectStack);
-  if (flatCoordinates) {
-    var ring = new ol.geom.LinearRing(null);
-    ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return ring;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.geom.Polygon|undefined} Polygon.
- */
-ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Polygon',
-      'localName should be Polygon');
-  /** @type {Array.<Array.<number>>} */
-  var flatLinearRings = ol.xml.pushParseAndPop([null],
-      this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
-  if (flatLinearRings && flatLinearRings[0]) {
-    var polygon = new ol.geom.Polygon(null);
-    var flatCoordinates = flatLinearRings[0];
-    var ends = [flatCoordinates.length];
-    var i, ii;
-    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
-      ol.array.extend(flatCoordinates, flatLinearRings[i]);
-      ends.push(flatCoordinates.length);
-    }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    return polygon;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>} Flat coordinates.
- */
-ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  return ol.xml.pushParseAndPop(null,
-      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
-      objectStack, this);
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'pointMember': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.pointMemberParser_),
-    'pointMembers': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.pointMemberParser_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'lineStringMember': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.lineStringMemberParser_),
-    'lineStringMembers': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.lineStringMemberParser_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'polygonMember': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.polygonMemberParser_),
-    'polygonMembers': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.polygonMemberParser_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'LineString': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readLineString)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Polygon': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readPolygon)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @protected
- */
-ol.format.GMLBase.prototype.RING_PARSERS = {
-  'http://www.opengis.net/gml' : {
-    'LinearRing': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readFlatLinearRing_)
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.GMLBase.prototype.readGeometryFromNode = function(node, opt_options) {
-  var geometry = this.readGeometryElement(node,
-      [this.getReadOptions(node, opt_options ? opt_options : {})]);
-  return geometry ? geometry : null;
-};
-
-
-/**
- * Read all features from a GML FeatureCollection.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.GMLBase.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.GMLBase.prototype.readFeaturesFromNode = function(node, opt_options) {
-  var options = {
-    featureType: this.featureType,
-    featureNS: this.featureNS
-  };
-  if (opt_options) {
-    ol.obj.assign(options, this.getReadOptions(node, opt_options));
-  }
-  var features = this.readFeaturesInternal(node, [options]);
-  return features || [];
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.GMLBase.prototype.readProjectionFromNode = function(node) {
-  return ol.proj.get(this.srsName ? this.srsName :
-      node.firstElementChild.getAttribute('srsName'));
-};
-
-goog.provide('ol.format.XSD');
-
-goog.require('ol');
-goog.require('ol.xml');
-goog.require('ol.string');
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema';
-
-
-/**
- * @param {Node} node Node.
- * @return {boolean|undefined} Boolean.
- */
-ol.format.XSD.readBoolean = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readBooleanString(s);
-};
-
-
-/**
- * @param {string} string String.
- * @return {boolean|undefined} Boolean.
- */
-ol.format.XSD.readBooleanString = function(string) {
-  var m = /^\s*(true|1)|(false|0)\s*$/.exec(string);
-  if (m) {
-    return m[1] !== undefined || false;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {number|undefined} DateTime in seconds.
- */
-ol.format.XSD.readDateTime = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  var dateTime = Date.parse(s);
-  return isNaN(dateTime) ? undefined : dateTime / 1000;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {number|undefined} Decimal.
- */
-ol.format.XSD.readDecimal = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readDecimalString(s);
-};
-
-
-/**
- * @param {string} string String.
- * @return {number|undefined} Decimal.
- */
-ol.format.XSD.readDecimalString = function(string) {
-  // FIXME check spec
-  var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string);
-  if (m) {
-    return parseFloat(m[1]);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {number|undefined} Non negative integer.
- */
-ol.format.XSD.readNonNegativeInteger = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  return ol.format.XSD.readNonNegativeIntegerString(s);
-};
-
-
-/**
- * @param {string} string String.
- * @return {number|undefined} Non negative integer.
- */
-ol.format.XSD.readNonNegativeIntegerString = function(string) {
-  var m = /^\s*(\d+)\s*$/.exec(string);
-  if (m) {
-    return parseInt(m[1], 10);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {string|undefined} String.
- */
-ol.format.XSD.readString = function(node) {
-  return ol.xml.getAllTextContent(node, false).trim();
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the boolean to.
- * @param {boolean} bool Boolean.
- */
-ol.format.XSD.writeBooleanTextNode = function(node, bool) {
-  ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0');
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the dateTime to.
- * @param {number} dateTime DateTime in seconds.
- */
-ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) {
-  var date = new Date(dateTime * 1000);
-  var string = date.getUTCFullYear() + '-' +
-      ol.string.padNumber(date.getUTCMonth() + 1, 2) + '-' +
-      ol.string.padNumber(date.getUTCDate(), 2) + 'T' +
-      ol.string.padNumber(date.getUTCHours(), 2) + ':' +
-      ol.string.padNumber(date.getUTCMinutes(), 2) + ':' +
-      ol.string.padNumber(date.getUTCSeconds(), 2) + 'Z';
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the decimal to.
- * @param {number} decimal Decimal.
- */
-ol.format.XSD.writeDecimalTextNode = function(node, decimal) {
-  var string = decimal.toPrecision();
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the decimal to.
- * @param {number} nonNegativeInteger Non negative integer.
- */
-ol.format.XSD.writeNonNegativeIntegerTextNode = function(node, nonNegativeInteger) {
-  ol.DEBUG && console.assert(nonNegativeInteger >= 0, 'value should be more than 0');
-  ol.DEBUG && console.assert(nonNegativeInteger == (nonNegativeInteger | 0),
-      'value should be an integer value');
-  var string = nonNegativeInteger.toString();
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the string to.
- * @param {string} string String.
- */
-ol.format.XSD.writeStringTextNode = function(node, string) {
-  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
-};
-
-goog.provide('ol.format.GML3');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.format.Feature');
-goog.require('ol.format.GMLBase');
-goog.require('ol.format.XSD');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Polygon');
-goog.require('ol.obj');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the GML format
- * version 3.1.1.
- * Currently only supports GML 3.1.1 Simple Features profile.
- *
- * @constructor
- * @param {olx.format.GMLOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.GMLBase}
- * @api
- */
-ol.format.GML3 = function(opt_options) {
-  var options = /** @type {olx.format.GMLOptions} */
-      (opt_options ? opt_options : {});
-
-  ol.format.GMLBase.call(this, options);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.surface_ = options.surface !== undefined ? options.surface : false;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.curve_ = options.curve !== undefined ? options.curve : false;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.multiCurve_ = options.multiCurve !== undefined ?
-      options.multiCurve : true;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.multiSurface_ = options.multiSurface !== undefined ?
-      options.multiSurface : true;
-
-  /**
-   * @inheritDoc
-   */
-  this.schemaLocation = options.schemaLocation ?
-      options.schemaLocation : ol.format.GML3.schemaLocation_;
-
-};
-ol.inherits(ol.format.GML3, ol.format.GMLBase);
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS +
-    ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
-    '1.0.0/gmlsf.xsd';
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiLineString|undefined} MultiLineString.
- */
-ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'MultiCurve',
-      'localName should be MultiCurve');
-  /** @type {Array.<ol.geom.LineString>} */
-  var lineStrings = ol.xml.pushParseAndPop([],
-      this.MULTICURVE_PARSERS_, node, objectStack, this);
-  if (lineStrings) {
-    var multiLineString = new ol.geom.MultiLineString(null);
-    multiLineString.setLineStrings(lineStrings);
-    return multiLineString;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
- */
-ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'MultiSurface',
-      'localName should be MultiSurface');
-  /** @type {Array.<ol.geom.Polygon>} */
-  var polygons = ol.xml.pushParseAndPop([],
-      this.MULTISURFACE_PARSERS_, node, objectStack, this);
-  if (polygons) {
-    var multiPolygon = new ol.geom.MultiPolygon(null);
-    multiPolygon.setPolygons(polygons);
-    return multiPolygon;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'curveMember' ||
-      node.localName == 'curveMembers',
-      'localName should be curveMember or curveMembers');
-  ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'surfaceMember' ||
-      node.localName == 'surfaceMembers',
-      'localName should be surfaceMember or surfaceMembers');
-  ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_,
-      node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
- */
-ol.format.GML3.prototype.readPatch_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'patches',
-      'localName should be patches');
-  return ol.xml.pushParseAndPop([null],
-      this.PATCHES_PARSERS_, node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} flat coordinates.
- */
-ol.format.GML3.prototype.readSegment_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'segments',
-      'localName should be segments');
-  return ol.xml.pushParseAndPop([null],
-      this.SEGMENTS_PARSERS_, node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
- */
-ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'npde.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'PolygonPatch',
-      'localName should be PolygonPatch');
-  return ol.xml.pushParseAndPop([null],
-      this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} flat coordinates.
- */
-ol.format.GML3.prototype.readLineStringSegment_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LineStringSegment',
-      'localName should be LineStringSegment');
-  return ol.xml.pushParseAndPop([null],
-      this.GEOMETRY_FLAT_COORDINATES_PARSERS_,
-      node, objectStack, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'interior',
-      'localName should be interior');
-  /** @type {Array.<number>|undefined} */
-  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
-      this.RING_PARSERS, node, objectStack, this);
-  if (flatLinearRing) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    ol.DEBUG && console.assert(Array.isArray(flatLinearRings),
-        'flatLinearRings should be an array');
-    ol.DEBUG && console.assert(flatLinearRings.length > 0,
-        'flatLinearRings should have an array length of 1 or more');
-    flatLinearRings.push(flatLinearRing);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'exterior',
-      'localName should be exterior');
-   /** @type {Array.<number>|undefined} */
-  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
-      this.RING_PARSERS, node, objectStack, this);
-  if (flatLinearRing) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    ol.DEBUG && console.assert(Array.isArray(flatLinearRings),
-        'flatLinearRings should be an array');
-    ol.DEBUG && console.assert(flatLinearRings.length > 0,
-        'flatLinearRings should have an array length of 1 or more');
-    flatLinearRings[0] = flatLinearRing;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Polygon|undefined} Polygon.
- */
-ol.format.GML3.prototype.readSurface_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Surface',
-      'localName should be Surface');
-  /** @type {Array.<Array.<number>>} */
-  var flatLinearRings = ol.xml.pushParseAndPop([null],
-      this.SURFACE_PARSERS_, node, objectStack, this);
-  if (flatLinearRings && flatLinearRings[0]) {
-    var polygon = new ol.geom.Polygon(null);
-    var flatCoordinates = flatLinearRings[0];
-    var ends = [flatCoordinates.length];
-    var i, ii;
-    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
-      ol.array.extend(flatCoordinates, flatLinearRings[i]);
-      ends.push(flatCoordinates.length);
-    }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    return polygon;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.LineString|undefined} LineString.
- */
-ol.format.GML3.prototype.readCurve_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Curve', 'localName should be Curve');
-  /** @type {Array.<number>} */
-  var flatCoordinates = ol.xml.pushParseAndPop([null],
-      this.CURVE_PARSERS_, node, objectStack, this);
-  if (flatCoordinates) {
-    var lineString = new ol.geom.LineString(null);
-    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    return lineString;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Extent|undefined} Envelope.
- */
-ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Envelope',
-      'localName should be Envelope');
-  /** @type {Array.<number>} */
-  var flatCoordinates = ol.xml.pushParseAndPop([null],
-      this.ENVELOPE_PARSERS_, node, objectStack, this);
-  return ol.extent.createOrUpdate(flatCoordinates[1][0],
-      flatCoordinates[1][1], flatCoordinates[2][0],
-      flatCoordinates[2][1]);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
- */
-ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) {
-  var s = ol.xml.getAllTextContent(node, false);
-  var re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/;
-  /** @type {Array.<number>} */
-  var flatCoordinates = [];
-  var m;
-  while ((m = re.exec(s))) {
-    flatCoordinates.push(parseFloat(m[1]));
-    s = s.substr(m[0].length);
-  }
-  if (s !== '') {
-    return undefined;
-  }
-  var context = objectStack[0];
-  var containerSrs = context['srsName'];
-  var axisOrientation = 'enu';
-  if (containerSrs) {
-    var proj = ol.proj.get(containerSrs);
-    axisOrientation = proj.getAxisOrientation();
-  }
-  if (axisOrientation === 'neu') {
-    var i, ii;
-    for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) {
-      var y = flatCoordinates[i];
-      var x = flatCoordinates[i + 1];
-      flatCoordinates[i] = x;
-      flatCoordinates[i + 1] = y;
-    }
-  }
-  var len = flatCoordinates.length;
-  if (len == 2) {
-    flatCoordinates.push(0);
-  }
-  if (len === 0) {
-    return undefined;
-  }
-  return flatCoordinates;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
- */
-ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) {
-  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
-  var context = objectStack[0];
-  var containerSrs = context['srsName'];
-  var containerDimension = node.parentNode.getAttribute('srsDimension');
-  var axisOrientation = 'enu';
-  if (containerSrs) {
-    var proj = ol.proj.get(containerSrs);
-    axisOrientation = proj.getAxisOrientation();
-  }
-  var coords = s.split(/\s+/);
-  // The "dimension" attribute is from the GML 3.0.1 spec.
-  var dim = 2;
-  if (node.getAttribute('srsDimension')) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(
-        node.getAttribute('srsDimension'));
-  } else if (node.getAttribute('dimension')) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(
-        node.getAttribute('dimension'));
-  } else if (containerDimension) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
-  }
-  var x, y, z;
-  var flatCoordinates = [];
-  for (var i = 0, ii = coords.length; i < ii; i += dim) {
-    x = parseFloat(coords[i]);
-    y = parseFloat(coords[i + 1]);
-    z = (dim === 3) ? parseFloat(coords[i + 2]) : 0;
-    if (axisOrientation.substr(0, 2) === 'en') {
-      flatCoordinates.push(x, y, z);
-    } else {
-      flatCoordinates.push(y, x, z);
-    }
-  }
-  return flatCoordinates;
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_),
-    'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'interior': ol.format.GML3.prototype.interiorParser_,
-    'exterior': ol.format.GML3.prototype.exteriorParser_
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.GEOMETRY_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
-    'MultiPoint': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPoint),
-    'LineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLineString),
-    'MultiLineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiLineString),
-    'LinearRing' : ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLinearRing),
-    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
-    'MultiPolygon': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPolygon),
-    'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_),
-    'MultiSurface': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readMultiSurface_),
-    'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_),
-    'MultiCurve': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readMultiCurve_),
-    'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.MULTICURVE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'curveMember': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.curveMemberParser_),
-    'curveMembers': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.curveMemberParser_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'surfaceMember': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.surfaceMemberParser_),
-    'surfaceMembers': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.surfaceMemberParser_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'LineString': ol.xml.makeArrayPusher(
-        ol.format.GMLBase.prototype.readLineString),
-    'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon),
-    'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.SURFACE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.CURVE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.ENVELOPE_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'lowerCorner': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.readFlatPosList_),
-    'upperCorner': ol.xml.makeArrayPusher(
-        ol.format.GML3.prototype.readFlatPosList_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.PATCHES_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'PolygonPatch': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readPolygonPatch_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML3.prototype.SEGMENTS_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'LineStringSegment': ol.xml.makeReplacer(
-        ol.format.GML3.prototype.readLineStringSegment_)
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Point} value Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  var axisOrientation = 'enu';
-  if (srsName) {
-    axisOrientation = ol.proj.get(srsName).getAxisOrientation();
-  }
-  var point = value.getCoordinates();
-  var coords;
-  // only 2d for simple features profile
-  if (axisOrientation.substr(0, 2) === 'en') {
-    coords = (point[0] + ' ' + point[1]);
-  } else {
-    coords = (point[1] + ' ' + point[0]);
-  }
-  ol.format.XSD.writeStringTextNode(node, coords);
-};
-
-
-/**
- * @param {Array.<number>} point Point geometry.
- * @param {string=} opt_srsName Optional srsName
- * @return {string} The coords string.
- * @private
- */
-ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) {
-  var axisOrientation = 'enu';
-  if (opt_srsName) {
-    axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
-  }
-  return ((axisOrientation.substr(0, 2) === 'en') ?
-      point[0] + ' ' + point[1] :
-      point[1] + ' ' + point[0]);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  // only 2d for simple features profile
-  var points = value.getCoordinates();
-  var len = points.length;
-  var parts = new Array(len);
-  var point;
-  for (var i = 0; i < len; ++i) {
-    point = points[i];
-    parts[i] = this.getCoords_(point, srsName);
-  }
-  ol.format.XSD.writeStringTextNode(node, parts.join(' '));
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Point} geometry Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  if (srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  var pos = ol.xml.createElementNS(node.namespaceURI, 'pos');
-  node.appendChild(pos);
-  this.writePos_(pos, geometry, objectStack);
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GML3.ENVELOPE_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-    'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Extent} extent Extent.
- * @param {Array.<*>} objectStack Node stack.
- */
-ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) {
-  ol.DEBUG && console.assert(extent.length == 4, 'extent should have 4 items');
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  if (srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  var keys = ['lowerCorner', 'upperCorner'];
-  var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]];
-  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
-      ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values,
-      objectStack, keys, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} geometry LinearRing geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeLinearRing_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  if (srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
-  node.appendChild(posList);
-  this.writePosList_(posList, geometry, objectStack);
-};
-
-
-/**
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node} Node.
- * @private
- */
-ol.format.GML3.prototype.RING_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
-  var context = objectStack[objectStack.length - 1];
-  var parentNode = context.node;
-  var exteriorWritten = context['exteriorWritten'];
-  if (exteriorWritten === undefined) {
-    context['exteriorWritten'] = true;
-  }
-  return ol.xml.createElementNS(parentNode.namespaceURI,
-      exteriorWritten !== undefined ? 'interior' : 'exterior');
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} geometry Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeSurfaceOrPolygon_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  if (node.nodeName !== 'PolygonPatch' && srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') {
-    var rings = geometry.getLinearRings();
-    ol.xml.pushSerializeAndPop(
-        {node: node, srsName: srsName},
-        ol.format.GML3.RING_SERIALIZERS_,
-        this.RING_NODE_FACTORY_,
-        rings, objectStack, undefined, this);
-  } else if (node.nodeName === 'Surface') {
-    var patches = ol.xml.createElementNS(node.namespaceURI, 'patches');
-    node.appendChild(patches);
-    this.writeSurfacePatches_(
-        patches, geometry, objectStack);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} geometry LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeCurveOrLineString_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  if (node.nodeName !== 'LineStringSegment' && srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  if (node.nodeName === 'LineString' ||
-      node.nodeName === 'LineStringSegment') {
-    var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
-    node.appendChild(posList);
-    this.writePosList_(posList, geometry, objectStack);
-  } else if (node.nodeName === 'Curve') {
-    var segments = ol.xml.createElementNS(node.namespaceURI, 'segments');
-    node.appendChild(segments);
-    this.writeCurveSegments_(segments,
-        geometry, objectStack);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  var surface = context['surface'];
-  if (srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  var polygons = geometry.getPolygons();
-  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface},
-      ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_,
-      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons,
-      objectStack, undefined, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.MultiPoint} geometry MultiPoint geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry,
-    objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  if (srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  var points = geometry.getPoints();
-  ol.xml.pushSerializeAndPop({node: node, srsName: srsName},
-      ol.format.GML3.POINTMEMBER_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory('pointMember'), points,
-      objectStack, undefined, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.MultiLineString} geometry MultiLineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeMultiCurveOrLineString_ = function(node, geometry, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var srsName = context['srsName'];
-  var curve = context['curve'];
-  if (srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  var lines = geometry.getLineStrings();
-  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve},
-      ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_,
-      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines,
-      objectStack, undefined, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} ring LinearRing geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) {
-  var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing');
-  node.appendChild(linearRing);
-  this.writeLinearRing_(linearRing, ring, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ = function(node, polygon, objectStack) {
-  var child = this.GEOMETRY_NODE_FACTORY_(
-      polygon, objectStack);
-  if (child) {
-    node.appendChild(child);
-    this.writeSurfaceOrPolygon_(child, polygon, objectStack);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Point} point Point geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writePointMember_ = function(node, point, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI, 'Point');
-  node.appendChild(child);
-  this.writePoint_(child, point, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} line LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeLineStringOrCurveMember_ = function(node, line, objectStack) {
-  var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack);
-  if (child) {
-    node.appendChild(child);
-    this.writeCurveOrLineString_(child, line, objectStack);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeSurfacePatches_ = function(node, polygon, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch');
-  node.appendChild(child);
-  this.writeSurfaceOrPolygon_(child, polygon, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} line LineString geometry.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeCurveSegments_ = function(node, line, objectStack) {
-  var child = ol.xml.createElementNS(node.namespaceURI,
-      'LineStringSegment');
-  node.appendChild(child);
-  this.writeCurveOrLineString_(child, line, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
- * @param {Array.<*>} objectStack Node stack.
- */
-ol.format.GML3.prototype.writeGeometryElement = function(node, geometry, objectStack) {
-  var context = /** @type {olx.format.WriteOptions} */ (objectStack[objectStack.length - 1]);
-  var item = ol.obj.assign({}, context);
-  item.node = node;
-  var value;
-  if (Array.isArray(geometry)) {
-    if (context.dataProjection) {
-      value = ol.proj.transformExtent(
-          geometry, context.featureProjection, context.dataProjection);
-    } else {
-      value = geometry;
-    }
-  } else {
-    value =
-        ol.format.Feature.transformWithOptions(/** @type {ol.geom.Geometry} */ (geometry), true, context);
-  }
-  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
-      (item), ol.format.GML3.GEOMETRY_SERIALIZERS_,
-      this.GEOMETRY_NODE_FACTORY_, [value],
-      objectStack, undefined, this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- */
-ol.format.GML3.prototype.writeFeatureElement = function(node, feature, objectStack) {
-  var fid = feature.getId();
-  if (fid) {
-    node.setAttribute('fid', fid);
-  }
-  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var featureNS = context['featureNS'];
-  var geometryName = feature.getGeometryName();
-  if (!context.serializers) {
-    context.serializers = {};
-    context.serializers[featureNS] = {};
-  }
-  var properties = feature.getProperties();
-  var keys = [], values = [];
-  for (var key in properties) {
-    var value = properties[key];
-    if (value !== null) {
-      keys.push(key);
-      values.push(value);
-      if (key == geometryName || value instanceof ol.geom.Geometry) {
-        if (!(key in context.serializers[featureNS])) {
-          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
-              this.writeGeometryElement, this);
-        }
-      } else {
-        if (!(key in context.serializers[featureNS])) {
-          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
-              ol.format.XSD.writeStringTextNode);
-        }
-      }
-    }
-  }
-  var item = ol.obj.assign({}, context);
-  item.node = node;
-  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
-      (item), context.serializers,
-      ol.xml.makeSimpleNodeFactory(undefined, featureNS),
-      values,
-      objectStack, keys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<ol.Feature>} features Features.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GML3.prototype.writeFeatureMembers_ = function(node, features, objectStack) {
-  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var featureType = context['featureType'];
-  var featureNS = context['featureNS'];
-  var serializers = {};
-  serializers[featureNS] = {};
-  serializers[featureNS][featureType] = ol.xml.makeChildAppender(
-      this.writeFeatureElement, this);
-  var item = ol.obj.assign({}, context);
-  item.node = node;
-  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
-      (item),
-      serializers,
-      ol.xml.makeSimpleNodeFactory(featureType, featureNS), features,
-      objectStack);
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'surfaceMember': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeSurfaceOrPolygonMember_),
-    'polygonMember': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeSurfaceOrPolygonMember_)
-  }
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GML3.POINTMEMBER_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'pointMember': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writePointMember_)
-  }
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'lineStringMember': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeLineStringOrCurveMember_),
-    'curveMember': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeLineStringOrCurveMember_)
-  }
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GML3.RING_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'exterior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_),
-    'interior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_)
-  }
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GML3.GEOMETRY_SERIALIZERS_ = {
-  'http://www.opengis.net/gml': {
-    'Curve': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeCurveOrLineString_),
-    'MultiCurve': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeMultiCurveOrLineString_),
-    'Point': ol.xml.makeChildAppender(ol.format.GML3.prototype.writePoint_),
-    'MultiPoint': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeMultiPoint_),
-    'LineString': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeCurveOrLineString_),
-    'MultiLineString': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeMultiCurveOrLineString_),
-    'LinearRing': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeLinearRing_),
-    'Polygon': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeSurfaceOrPolygon_),
-    'MultiPolygon': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_),
-    'Surface': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeSurfaceOrPolygon_),
-    'MultiSurface': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_),
-    'Envelope': ol.xml.makeChildAppender(
-        ol.format.GML3.prototype.writeEnvelope)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, string>}
- * @private
- */
-ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = {
-  'MultiLineString': 'lineStringMember',
-  'MultiCurve': 'curveMember',
-  'MultiPolygon': 'polygonMember',
-  'MultiSurface': 'surfaceMember'
-};
-
-
-/**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
- */
-ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
-  var parentNode = objectStack[objectStack.length - 1].node;
-  ol.DEBUG && console.assert(ol.xml.isNode(parentNode),
-      'parentNode should be a node');
-  return ol.xml.createElementNS('http://www.opengis.net/gml',
-      ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]);
-};
-
-
-/**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
- */
-ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
-  var context = objectStack[objectStack.length - 1];
-  var multiSurface = context['multiSurface'];
-  var surface = context['surface'];
-  var curve = context['curve'];
-  var multiCurve = context['multiCurve'];
-  var parentNode = objectStack[objectStack.length - 1].node;
-  ol.DEBUG && console.assert(ol.xml.isNode(parentNode),
-      'parentNode should be a node');
-  var nodeName;
-  if (!Array.isArray(value)) {
-    nodeName = /** @type {ol.geom.Geometry} */ (value).getType();
-    if (nodeName === 'MultiPolygon' && multiSurface === true) {
-      nodeName = 'MultiSurface';
-    } else if (nodeName === 'Polygon' && surface === true) {
-      nodeName = 'Surface';
-    } else if (nodeName === 'LineString' && curve === true) {
-      nodeName = 'Curve';
-    } else if (nodeName === 'MultiLineString' && multiCurve === true) {
-      nodeName = 'MultiCurve';
-    }
-  } else {
-    nodeName = 'Envelope';
-  }
-  return ol.xml.createElementNS('http://www.opengis.net/gml',
-      nodeName);
-};
-
-
-/**
- * Encode a geometry in GML 3.1.1 Simple Features.
- *
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- * @api
- */
-ol.format.GML3.prototype.writeGeometryNode = function(geometry, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  var geom = ol.xml.createElementNS('http://www.opengis.net/gml', 'geom');
-  var context = {node: geom, srsName: this.srsName,
-    curve: this.curve_, surface: this.surface_,
-    multiSurface: this.multiSurface_, multiCurve: this.multiCurve_};
-  if (opt_options) {
-    ol.obj.assign(context, opt_options);
-  }
-  this.writeGeometryElement(geom, geometry, [context]);
-  return geom;
-};
-
-
-/**
- * Encode an array of features in GML 3.1.1 Simple Features.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {string} Result.
- * @api stable
- */
-ol.format.GML3.prototype.writeFeatures;
-
-
-/**
- * Encode an array of features in the GML 3.1.1 format as an XML node.
- *
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- * @api
- */
-ol.format.GML3.prototype.writeFeaturesNode = function(features, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  var node = ol.xml.createElementNS('http://www.opengis.net/gml',
-      'featureMembers');
-  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
-      'xsi:schemaLocation', this.schemaLocation);
-  var context = {
-    srsName: this.srsName,
-    curve: this.curve_,
-    surface: this.surface_,
-    multiSurface: this.multiSurface_,
-    multiCurve: this.multiCurve_,
-    featureNS: this.featureNS,
-    featureType: this.featureType
-  };
-  if (opt_options) {
-    ol.obj.assign(context, opt_options);
-  }
-  this.writeFeatureMembers_(node, features, [context]);
-  return node;
-};
-
-goog.provide('ol.format.GML');
-
-goog.require('ol.format.GML3');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the GML format
- * version 3.1.1.
- * Currently only supports GML 3.1.1 Simple Features profile.
- *
- * @constructor
- * @param {olx.format.GMLOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.GMLBase}
- * @api stable
- */
-ol.format.GML = ol.format.GML3;
-
-
-/**
- * Encode an array of features in GML 3.1.1 Simple Features.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {string} Result.
- * @api stable
- */
-ol.format.GML.prototype.writeFeatures;
-
-
-/**
- * Encode an array of features in the GML 3.1.1 format as an XML node.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- * @api
- */
-ol.format.GML.prototype.writeFeaturesNode;
-
-goog.provide('ol.format.GML2');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.format.GMLBase');
-goog.require('ol.format.XSD');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the GML format,
- * version 2.1.2.
- *
- * @constructor
- * @param {olx.format.GMLOptions=} opt_options Optional configuration object.
- * @extends {ol.format.GMLBase}
- * @api
- */
-ol.format.GML2 = function(opt_options) {
-  var options = /** @type {olx.format.GMLOptions} */
-      (opt_options ? opt_options : {});
-
-  ol.format.GMLBase.call(this, options);
-
-  this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][
-      'featureMember'] =
-      ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal);
-
-  /**
-   * @inheritDoc
-   */
-  this.schemaLocation = options.schemaLocation ?
-      options.schemaLocation : ol.format.GML2.schemaLocation_;
-
-};
-ol.inherits(ol.format.GML2, ol.format.GMLBase);
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS +
-    ' http://schemas.opengis.net/gml/2.1.2/feature.xsd';
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
- */
-ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) {
-  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
-  var context = /** @type {ol.XmlNodeStackItem} */ (objectStack[0]);
-  var containerSrs = context['srsName'];
-  var containerDimension = node.parentNode.getAttribute('srsDimension');
-  var axisOrientation = 'enu';
-  if (containerSrs) {
-    var proj = ol.proj.get(containerSrs);
-    if (proj) {
-      axisOrientation = proj.getAxisOrientation();
-    }
-  }
-  var coords = s.split(/[\s,]+/);
-  // The "dimension" attribute is from the GML 3.0.1 spec.
-  var dim = 2;
-  if (node.getAttribute('srsDimension')) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(
-        node.getAttribute('srsDimension'));
-  } else if (node.getAttribute('dimension')) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(
-        node.getAttribute('dimension'));
-  } else if (containerDimension) {
-    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
-  }
-  var x, y, z;
-  var flatCoordinates = [];
-  for (var i = 0, ii = coords.length; i < ii; i += dim) {
-    x = parseFloat(coords[i]);
-    y = parseFloat(coords[i + 1]);
-    z = (dim === 3) ? parseFloat(coords[i + 2]) : 0;
-    if (axisOrientation.substr(0, 2) === 'en') {
-      flatCoordinates.push(x, y, z);
-    } else {
-      flatCoordinates.push(y, x, z);
-    }
-  }
-  return flatCoordinates;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Extent|undefined} Envelope.
- */
-ol.format.GML2.prototype.readBox_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Box', 'localName should be Box');
-  /** @type {Array.<number>} */
-  var flatCoordinates = ol.xml.pushParseAndPop([null],
-      this.BOX_PARSERS_, node, objectStack, this);
-  return ol.extent.createOrUpdate(flatCoordinates[1][0],
-      flatCoordinates[1][1], flatCoordinates[1][3],
-      flatCoordinates[1][4]);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GML2.prototype.innerBoundaryIsParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'innerBoundaryIs',
-      'localName should be innerBoundaryIs');
-  /** @type {Array.<number>|undefined} */
-  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
-      this.RING_PARSERS, node, objectStack, this);
-  if (flatLinearRing) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    ol.DEBUG && console.assert(Array.isArray(flatLinearRings),
-        'flatLinearRings should be an array');
-    ol.DEBUG && console.assert(flatLinearRings.length > 0,
-        'flatLinearRings should have an array length larger than 0');
-    flatLinearRings.push(flatLinearRing);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GML2.prototype.outerBoundaryIsParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'outerBoundaryIs',
-      'localName should be outerBoundaryIs');
-  /** @type {Array.<number>|undefined} */
-  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
-      this.RING_PARSERS, node, objectStack, this);
-  if (flatLinearRing) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    ol.DEBUG && console.assert(Array.isArray(flatLinearRings),
-        'flatLinearRings should be an array');
-    ol.DEBUG && console.assert(flatLinearRings.length > 0,
-        'flatLinearRings should have an array length larger than 0');
-    flatLinearRings[0] = flatLinearRing;
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'coordinates': ol.xml.makeReplacer(
-        ol.format.GML2.prototype.readFlatCoordinates_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_,
-    'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML2.prototype.BOX_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'coordinates': ol.xml.makeArrayPusher(
-        ol.format.GML2.prototype.readFlatCoordinates_)
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GML2.prototype.GEOMETRY_PARSERS_ = {
-  'http://www.opengis.net/gml' : {
-    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
-    'MultiPoint': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPoint),
-    'LineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLineString),
-    'MultiLineString': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiLineString),
-    'LinearRing' : ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readLinearRing),
-    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
-    'MultiPolygon': ol.xml.makeReplacer(
-        ol.format.GMLBase.prototype.readMultiPolygon),
-    'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_)
-  }
-};
-
-goog.provide('ol.format.GPX');
-
-goog.require('ol');
-goog.require('ol.Feature');
-goog.require('ol.array');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.format.XSD');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.Point');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the GPX format.
- *
- * @constructor
- * @extends {ol.format.XMLFeature}
- * @param {olx.format.GPXOptions=} opt_options Options.
- * @api stable
- */
-ol.format.GPX = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.XMLFeature.call(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
-
-  /**
-   * @type {function(ol.Feature, Node)|undefined}
-   * @private
-   */
-  this.readExtensions_ = options.readExtensions;
-};
-ol.inherits(ol.format.GPX, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @private
- * @type {Array.<string>}
- */
-ol.format.GPX.NAMESPACE_URIS_ = [
-  null,
-  'http://www.topografix.com/GPX/1/0',
-  'http://www.topografix.com/GPX/1/1'
-];
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.GPX.SCHEMA_LOCATION_ = 'http://www.topografix.com/GPX/1/1 ' +
-    'http://www.topografix.com/GPX/1/1/gpx.xsd';
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {Node} node Node.
- * @param {Object} values Values.
- * @private
- * @return {Array.<number>} Flat coordinates.
- */
-ol.format.GPX.appendCoordinate_ = function(flatCoordinates, node, values) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  flatCoordinates.push(
-      parseFloat(node.getAttribute('lon')),
-      parseFloat(node.getAttribute('lat')));
-  if ('ele' in values) {
-    flatCoordinates.push(/** @type {number} */ (values['ele']));
-    delete values['ele'];
-  } else {
-    flatCoordinates.push(0);
-  }
-  if ('time' in values) {
-    flatCoordinates.push(/** @type {number} */ (values['time']));
-    delete values['time'];
-  } else {
-    flatCoordinates.push(0);
-  }
-  return flatCoordinates;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.parseLink_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'link', 'localName should be link');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var href = node.getAttribute('href');
-  if (href !== null) {
-    values['link'] = href;
-  }
-  ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.parseExtensions_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'extensions',
-      'localName should be extensions');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  values['extensionsNode_'] = node;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.parseRtePt_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'rtept', 'localName should be rtept');
-  var values = ol.xml.pushParseAndPop(
-      {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack);
-  if (values) {
-    var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-    var flatCoordinates = /** @type {Array.<number>} */
-        (rteValues['flatCoordinates']);
-    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.parseTrkPt_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'trkpt', 'localName should be trkpt');
-  var values = ol.xml.pushParseAndPop(
-      {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack);
-  if (values) {
-    var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-    var flatCoordinates = /** @type {Array.<number>} */
-        (trkValues['flatCoordinates']);
-    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.parseTrkSeg_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'trkseg',
-      'localName should be trkseg');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack);
-  var flatCoordinates = /** @type {Array.<number>} */
-      (values['flatCoordinates']);
-  var ends = /** @type {Array.<number>} */ (values['ends']);
-  ends.push(flatCoordinates.length);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
- */
-ol.format.GPX.readRte_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'rte', 'localName should be rte');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var values = ol.xml.pushParseAndPop({
-    'flatCoordinates': []
-  }, ol.format.GPX.RTE_PARSERS_, node, objectStack);
-  if (!values) {
-    return undefined;
-  }
-  var flatCoordinates = /** @type {Array.<number>} */
-      (values['flatCoordinates']);
-  delete values['flatCoordinates'];
-  var geometry = new ol.geom.LineString(null);
-  geometry.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setProperties(values);
-  return feature;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Track.
- */
-ol.format.GPX.readTrk_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'trk', 'localName should be trk');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var values = ol.xml.pushParseAndPop({
-    'flatCoordinates': [],
-    'ends': []
-  }, ol.format.GPX.TRK_PARSERS_, node, objectStack);
-  if (!values) {
-    return undefined;
-  }
-  var flatCoordinates = /** @type {Array.<number>} */
-      (values['flatCoordinates']);
-  delete values['flatCoordinates'];
-  var ends = /** @type {Array.<number>} */ (values['ends']);
-  delete values['ends'];
-  var geometry = new ol.geom.MultiLineString(null);
-  geometry.setFlatCoordinates(
-      ol.geom.GeometryLayout.XYZM, flatCoordinates, ends);
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setProperties(values);
-  return feature;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Waypoint.
- */
-ol.format.GPX.readWpt_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'wpt', 'localName should be wpt');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var values = ol.xml.pushParseAndPop(
-      {}, ol.format.GPX.WPT_PARSERS_, node, objectStack);
-  if (!values) {
-    return undefined;
-  }
-  var coordinates = ol.format.GPX.appendCoordinate_([], node, values);
-  var geometry = new ol.geom.Point(
-      coordinates, ol.geom.GeometryLayout.XYZM);
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setProperties(values);
-  return feature;
-};
-
-
-/**
- * @const
- * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>}
- * @private
- */
-ol.format.GPX.FEATURE_READER_ = {
-  'rte': ol.format.GPX.readRte_,
-  'trk': ol.format.GPX.readTrk_,
-  'wpt': ol.format.GPX.readWpt_
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.GPX_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_),
-      'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_),
-      'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.LINK_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'text':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'),
-      'type':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType')
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.RTE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'link': ol.format.GPX.parseLink_,
-      'number':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
-      'extensions': ol.format.GPX.parseExtensions_,
-      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'rtept': ol.format.GPX.parseRtePt_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.TRK_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'link': ol.format.GPX.parseLink_,
-      'number':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
-      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'extensions': ol.format.GPX.parseExtensions_,
-      'trkseg': ol.format.GPX.parseTrkSeg_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'trkpt': ol.format.GPX.parseTrkPt_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.GPX.WPT_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime),
-      'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'link': ol.format.GPX.parseLink_,
-      'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'sat': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger),
-      'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'ageofdgpsdata':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'dgpsid':
-          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
-      'extensions': ol.format.GPX.parseExtensions_
-    });
-
-
-/**
- * @param {Array.<ol.Feature>} features List of features.
- * @private
- */
-ol.format.GPX.prototype.handleReadExtensions_ = function(features) {
-  if (!features) {
-    features = [];
-  }
-  for (var i = 0, ii = features.length; i < ii; ++i) {
-    var feature = features[i];
-    if (this.readExtensions_) {
-      var extensionsNode = feature.get('extensionsNode_') || null;
-      this.readExtensions_(feature, extensionsNode);
-    }
-    feature.set('extensionsNode_', undefined);
-  }
-};
-
-
-/**
- * Read the first feature from a GPX source.
- * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`)
- * into MultiLineString. Any properties on route and track waypoints are ignored.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
- */
-ol.format.GPX.prototype.readFeature;
-
-
-/**
- * @inheritDoc
- */
-ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
-    return null;
-  }
-  var featureReader = ol.format.GPX.FEATURE_READER_[node.localName];
-  if (!featureReader) {
-    return null;
-  }
-  var feature = featureReader(node, [this.getReadOptions(node, opt_options)]);
-  if (!feature) {
-    return null;
-  }
-  this.handleReadExtensions_([feature]);
-  return feature;
-};
-
-
-/**
- * Read all features from a GPX source.
- * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`)
- * into MultiLineString. Any properties on route and track waypoints are ignored.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.GPX.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
-    return [];
-  }
-  if (node.localName == 'gpx') {
-    /** @type {Array.<ol.Feature>} */
-    var features = ol.xml.pushParseAndPop([], ol.format.GPX.GPX_PARSERS_,
-        node, [this.getReadOptions(node, opt_options)]);
-    if (features) {
-      this.handleReadExtensions_(features);
-      return features;
-    } else {
-      return [];
-    }
-  }
-  return [];
-};
-
-
-/**
- * Read the projection from a GPX source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.GPX.prototype.readProjection;
-
-
-/**
- * @param {Node} node Node.
- * @param {string} value Value for the link's `href` attribute.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.GPX.writeLink_ = function(node, value, objectStack) {
-  node.setAttribute('href', value);
-  var context = objectStack[objectStack.length - 1];
-  var properties = context['properties'];
-  var link = [
-    properties['linkText'],
-    properties['linkType']
-  ];
-  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ ({node: node}),
-      ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      link, objectStack, ol.format.GPX.LINK_SEQUENCE_);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var parentNode = context.node;
-  ol.DEBUG && console.assert(ol.xml.isNode(parentNode),
-      'parentNode should be an XML node');
-  var namespaceURI = parentNode.namespaceURI;
-  var properties = context['properties'];
-  //FIXME Projection handling
-  ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]);
-  ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]);
-  var geometryLayout = context['geometryLayout'];
-  switch (geometryLayout) {
-    case ol.geom.GeometryLayout.XYZM:
-      if (coordinate[3] !== 0) {
-        properties['time'] = coordinate[3];
-      }
-      // fall through
-    case ol.geom.GeometryLayout.XYZ:
-      if (coordinate[2] !== 0) {
-        properties['ele'] = coordinate[2];
-      }
-      break;
-    case ol.geom.GeometryLayout.XYM:
-      if (coordinate[2] !== 0) {
-        properties['time'] = coordinate[2];
-      }
-      break;
-    default:
-      // pass
-  }
-  var orderedKeys = (node.nodeName == 'rtept') ?
-      ol.format.GPX.RTEPT_TYPE_SEQUENCE_[namespaceURI] :
-      ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
-      ({node: node, 'properties': properties}),
-      ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.writeRte_ = function(node, feature, objectStack) {
-  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
-  var properties = feature.getProperties();
-  var context = {node: node, 'properties': properties};
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    geometry = /** @type {ol.geom.LineString} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
-    context['geometryLayout'] = geometry.getLayout();
-    properties['rtept'] = geometry.getCoordinates();
-  }
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.writeTrk_ = function(node, feature, objectStack) {
-  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
-  var properties = feature.getProperties();
-  /** @type {ol.XmlNodeStackItem} */
-  var context = {node: node, 'properties': properties};
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    geometry = /** @type {ol.geom.MultiLineString} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
-    properties['trkseg'] = geometry.getLineStrings();
-  }
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LineString} lineString LineString.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) {
-  /** @type {ol.XmlNodeStackItem} */
-  var context = {node: node, 'geometryLayout': lineString.getLayout(),
-    'properties': {}};
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_,
-      lineString.getCoordinates(), objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.GPX.writeWpt_ = function(node, feature, objectStack) {
-  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
-  var context = objectStack[objectStack.length - 1];
-  context['properties'] = feature.getProperties();
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    geometry = /** @type {ol.geom.Point} */
-        (ol.format.Feature.transformWithOptions(geometry, true, options));
-    context['geometryLayout'] = geometry.getLayout();
-    ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack);
-  }
-};
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type'];
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
-      'number': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
-          ol.format.GPX.writeWptType_))
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.GPX.RTEPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'ele', 'time'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
-      'number': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
-          ol.format.GPX.writeTrkSeg_))
-    });
-
-
-/**
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, [
-      'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src',
-      'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop',
-      'ageofdgpsdata', 'dgpsid'
-    ]);
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode),
-      'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'geoidheight': ol.xml.makeChildAppender(
-          ol.format.XSD.writeDecimalTextNode),
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
-      'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'sat': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode),
-      'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'ageofdgpsdata': ol.xml.makeChildAppender(
-          ol.format.XSD.writeDecimalTextNode),
-      'dgpsid': ol.xml.makeChildAppender(
-          ol.format.XSD.writeNonNegativeIntegerTextNode)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, string>}
- * @private
- */
-ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = {
-  'Point': 'wpt',
-  'LineString': 'rte',
-  'MultiLineString': 'trk'
-};
-
-
-/**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
- */
-ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
-  var geometry = /** @type {ol.Feature} */ (value).getGeometry();
-  if (geometry) {
-    var nodeName = ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()];
-    if (nodeName) {
-      var parentNode = objectStack[objectStack.length - 1].node;
-      ol.DEBUG && console.assert(ol.xml.isNode(parentNode),
-          'parentNode should be an XML node');
-      return ol.xml.createElementNS(parentNode.namespaceURI, nodeName);
-    }
-  }
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.GPX.NAMESPACE_URIS_, {
-      'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_),
-      'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_),
-      'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_)
-    });
-
-
-/**
- * Encode an array of features in the GPX format.
- * LineString geometries are output as routes (`<rte>`), and MultiLineString
- * as tracks (`<trk>`).
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Result.
- * @api stable
- */
-ol.format.GPX.prototype.writeFeatures;
-
-
-/**
- * Encode an array of features in the GPX format as an XML node.
- * LineString geometries are output as routes (`<rte>`), and MultiLineString
- * as tracks (`<trk>`).
- *
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- * @api
- */
-ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  //FIXME Serialize metadata
-  var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx');
-  var xmlnsUri = 'http://www.w3.org/2000/xmlns/';
-  var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance';
-  ol.xml.setAttributeNS(gpx, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri);
-  ol.xml.setAttributeNS(gpx, xmlSchemaInstanceUri, 'xsi:schemaLocation',
-      ol.format.GPX.SCHEMA_LOCATION_);
-  gpx.setAttribute('version', '1.1');
-  gpx.setAttribute('creator', 'OpenLayers 3');
-
-  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
-      ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_,
-      ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]);
-  return gpx;
-};
-
-goog.provide('ol.format.TextFeature');
-
-goog.require('ol');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for text feature formats.
- *
- * @constructor
- * @extends {ol.format.Feature}
- */
-ol.format.TextFeature = function() {
-  ol.format.Feature.call(this);
-};
-ol.inherits(ol.format.TextFeature, ol.format.Feature);
-
-
-/**
- * @param {Document|Node|Object|string} source Source.
- * @private
- * @return {string} Text.
- */
-ol.format.TextFeature.prototype.getText_ = function(source) {
-  if (typeof source === 'string') {
-    return source;
-  } else {
-    return '';
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.getType = function() {
-  return ol.format.FormatType.TEXT;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.readFeature = function(source, opt_options) {
-  return this.readFeatureFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
-};
-
-
-/**
- * @abstract
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.Feature} Feature.
- */
-ol.format.TextFeature.prototype.readFeatureFromText = function(text, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) {
-  return this.readFeaturesFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
-};
-
-
-/**
- * @abstract
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {Array.<ol.Feature>} Features.
- */
-ol.format.TextFeature.prototype.readFeaturesFromText = function(text, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) {
-  return this.readGeometryFromText(
-      this.getText_(source), this.adaptOptions(opt_options));
-};
-
-
-/**
- * @abstract
- * @param {string} text Text.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @protected
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.TextFeature.prototype.readGeometryFromText = function(text, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.readProjection = function(source) {
-  return this.readProjectionFromText(this.getText_(source));
-};
-
-
-/**
- * @param {string} text Text.
- * @protected
- * @return {ol.proj.Projection} Projection.
- */
-ol.format.TextFeature.prototype.readProjectionFromText = function(text) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) {
-  return this.writeFeatureText(feature, this.adaptOptions(opt_options));
-};
-
-
-/**
- * @abstract
- * @param {ol.Feature} feature Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {string} Text.
- */
-ol.format.TextFeature.prototype.writeFeatureText = function(feature, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.writeFeatures = function(
-    features, opt_options) {
-  return this.writeFeaturesText(features, this.adaptOptions(opt_options));
-};
-
-
-/**
- * @abstract
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {string} Text.
- */
-ol.format.TextFeature.prototype.writeFeaturesText = function(features, opt_options) {};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TextFeature.prototype.writeGeometry = function(
-    geometry, opt_options) {
-  return this.writeGeometryText(geometry, this.adaptOptions(opt_options));
-};
-
-
-/**
- * @abstract
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @protected
- * @return {string} Text.
- */
-ol.format.TextFeature.prototype.writeGeometryText = function(geometry, opt_options) {};
-
-goog.provide('ol.format.IGC');
-
-goog.require('ol');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.TextFeature');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.LineString');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Feature format for `*.igc` flight recording files.
- *
- * @constructor
- * @extends {ol.format.TextFeature}
- * @param {olx.format.IGCOptions=} opt_options Options.
- * @api
- */
-ol.format.IGC = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.TextFeature.call(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
-
-  /**
-   * @private
-   * @type {ol.format.IGC.Z}
-   */
-  this.altitudeMode_ = options.altitudeMode ?
-      options.altitudeMode : ol.format.IGC.Z.NONE;
-
-};
-ol.inherits(ol.format.IGC, ol.format.TextFeature);
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.IGC.EXTENSIONS_ = ['.igc'];
-
-
-/**
- * @const
- * @type {RegExp}
- * @private
- */
-ol.format.IGC.B_RECORD_RE_ =
-    /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/;
-
-
-/**
- * @const
- * @type {RegExp}
- * @private
- */
-ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;
-
-
-/**
- * @const
- * @type {RegExp}
- * @private
- */
-ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/;
-
-
-/**
- * A regular expression matching the newline characters `\r\n`, `\r` and `\n`.
- *
- * @const
- * @type {RegExp}
- * @private
- */
-ol.format.IGC.NEWLINE_RE_ = /\r\n|\r|\n/;
-
-
-/**
- * @inheritDoc
- */
-ol.format.IGC.prototype.getExtensions = function() {
-  return ol.format.IGC.EXTENSIONS_;
-};
-
-
-/**
- * Read the feature from the IGC source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api
- */
-ol.format.IGC.prototype.readFeature;
-
-
-/**
- * @inheritDoc
- */
-ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) {
-  var altitudeMode = this.altitudeMode_;
-  var lines = text.split(ol.format.IGC.NEWLINE_RE_);
-  /** @type {Object.<string, string>} */
-  var properties = {};
-  var flatCoordinates = [];
-  var year = 2000;
-  var month = 0;
-  var day = 1;
-  var lastDateTime = -1;
-  var i, ii;
-  for (i = 0, ii = lines.length; i < ii; ++i) {
-    var line = lines[i];
-    var m;
-    if (line.charAt(0) == 'B') {
-      m = ol.format.IGC.B_RECORD_RE_.exec(line);
-      if (m) {
-        var hour = parseInt(m[1], 10);
-        var minute = parseInt(m[2], 10);
-        var second = parseInt(m[3], 10);
-        var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000;
-        if (m[6] == 'S') {
-          y = -y;
-        }
-        var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000;
-        if (m[9] == 'W') {
-          x = -x;
-        }
-        flatCoordinates.push(x, y);
-        if (altitudeMode != ol.format.IGC.Z.NONE) {
-          var z;
-          if (altitudeMode == ol.format.IGC.Z.GPS) {
-            z = parseInt(m[11], 10);
-          } else if (altitudeMode == ol.format.IGC.Z.BAROMETRIC) {
-            z = parseInt(m[12], 10);
-          } else {
-            ol.DEBUG && console.assert(false, 'Unknown altitude mode.');
-            z = 0;
-          }
-          flatCoordinates.push(z);
-        }
-        var dateTime = Date.UTC(year, month, day, hour, minute, second);
-        // Detect UTC midnight wrap around.
-        if (dateTime < lastDateTime) {
-          dateTime = Date.UTC(year, month, day + 1, hour, minute, second);
-        }
-        flatCoordinates.push(dateTime / 1000);
-        lastDateTime = dateTime;
-      }
-    } else if (line.charAt(0) == 'H') {
-      m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line);
-      if (m) {
-        day = parseInt(m[1], 10);
-        month = parseInt(m[2], 10) - 1;
-        year = 2000 + parseInt(m[3], 10);
-      } else {
-        m = ol.format.IGC.H_RECORD_RE_.exec(line);
-        if (m) {
-          properties[m[1]] = m[2].trim();
-        }
-      }
-    }
-  }
-  if (flatCoordinates.length === 0) {
-    return null;
-  }
-  var lineString = new ol.geom.LineString(null);
-  var layout = altitudeMode == ol.format.IGC.Z.NONE ?
-      ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM;
-  lineString.setFlatCoordinates(layout, flatCoordinates);
-  var feature = new ol.Feature(ol.format.Feature.transformWithOptions(
-      lineString, false, opt_options));
-  feature.setProperties(properties);
-  return feature;
-};
-
-
-/**
- * Read the feature from the source. As IGC sources contain a single
- * feature, this will return the feature in an array.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api
- */
-ol.format.IGC.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) {
-  var feature = this.readFeatureFromText(text, opt_options);
-  if (feature) {
-    return [feature];
-  } else {
-    return [];
-  }
-};
-
-
-/**
- * Read the projection from the IGC source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api
- */
-ol.format.IGC.prototype.readProjection;
-
-
-/**
- * IGC altitude/z. One of 'barometric', 'gps', 'none'.
- * @enum {string}
- */
-ol.format.IGC.Z = {
-  BAROMETRIC: 'barometric',
-  GPS: 'gps',
-  NONE: 'none'
-};
-
-goog.provide('ol.style.IconImage');
-
-goog.require('ol');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.EventTarget');
-goog.require('ol.events.EventType');
-goog.require('ol.Image');
-goog.require('ol.style');
-
-
-/**
- * @constructor
- * @param {Image|HTMLCanvasElement} image Image.
- * @param {string|undefined} src Src.
- * @param {ol.Size} size Size.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Image.State} imageState Image state.
- * @param {ol.Color} color Color.
- * @extends {ol.events.EventTarget}
- */
-ol.style.IconImage = function(image, src, size, crossOrigin, imageState,
-                               color) {
-
-  ol.events.EventTarget.call(this);
-
-  /**
-   * @private
-   * @type {Image|HTMLCanvasElement}
-   */
-  this.hitDetectionImage_ = null;
-
-  /**
-   * @private
-   * @type {Image|HTMLCanvasElement}
-   */
-  this.image_ = !image ? new Image() : image;
-
-  if (crossOrigin !== null) {
-    this.image_.crossOrigin = crossOrigin;
-  }
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = color ?
-      /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) :
-      null;
-
-  /**
-   * @private
-   * @type {ol.Color}
-   */
-  this.color_ = color;
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.imageListenerKeys_ = null;
-
-  /**
-   * @private
-   * @type {ol.Image.State}
-   */
-  this.imageState_ = imageState;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.size_ = size;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.src_ = src;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.tainting_ = false;
-  if (this.imageState_ == ol.Image.State.LOADED) {
-    this.determineTainting_();
-  }
-
-};
-ol.inherits(ol.style.IconImage, ol.events.EventTarget);
-
-
-/**
- * @param {Image|HTMLCanvasElement} image Image.
- * @param {string} src Src.
- * @param {ol.Size} size Size.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.Image.State} imageState Image state.
- * @param {ol.Color} color Color.
- * @return {ol.style.IconImage} Icon image.
- */
-ol.style.IconImage.get = function(image, src, size, crossOrigin, imageState,
-                                   color) {
-  var iconImageCache = ol.style.iconImageCache;
-  var iconImage = iconImageCache.get(src, crossOrigin, color);
-  if (!iconImage) {
-    iconImage = new ol.style.IconImage(
-        image, src, size, crossOrigin, imageState, color);
-    iconImageCache.set(src, crossOrigin, color, iconImage);
-  }
-  return iconImage;
-};
-
-
-/**
- * @private
- */
-ol.style.IconImage.prototype.determineTainting_ = function() {
-  var context = ol.dom.createCanvasContext2D(1, 1);
-  try {
-    context.drawImage(this.image_, 0, 0);
-    context.getImageData(0, 0, 1, 1);
-  } catch (e) {
-    this.tainting_ = true;
-  }
-};
-
-
-/**
- * @private
- */
-ol.style.IconImage.prototype.dispatchChangeEvent_ = function() {
-  this.dispatchEvent(ol.events.EventType.CHANGE);
-};
-
-
-/**
- * @private
- */
-ol.style.IconImage.prototype.handleImageError_ = function() {
-  this.imageState_ = ol.Image.State.ERROR;
-  this.unlistenImage_();
-  this.dispatchChangeEvent_();
-};
-
-
-/**
- * @private
- */
-ol.style.IconImage.prototype.handleImageLoad_ = function() {
-  this.imageState_ = ol.Image.State.LOADED;
-  if (this.size_) {
-    this.image_.width = this.size_[0];
-    this.image_.height = this.size_[1];
-  }
-  this.size_ = [this.image_.width, this.image_.height];
-  this.unlistenImage_();
-  this.determineTainting_();
-  this.replaceColor_();
-  this.dispatchChangeEvent_();
-};
-
-
-/**
- * @param {number} pixelRatio Pixel ratio.
- * @return {Image|HTMLCanvasElement} Image or Canvas element.
- */
-ol.style.IconImage.prototype.getImage = function(pixelRatio) {
-  return this.canvas_ ? this.canvas_ : this.image_;
-};
-
-
-/**
- * @return {ol.Image.State} Image state.
- */
-ol.style.IconImage.prototype.getImageState = function() {
-  return this.imageState_;
-};
-
-
-/**
- * @param {number} pixelRatio Pixel ratio.
- * @return {Image|HTMLCanvasElement} Image element.
- */
-ol.style.IconImage.prototype.getHitDetectionImage = function(pixelRatio) {
-  if (!this.hitDetectionImage_) {
-    if (this.tainting_) {
-      var width = this.size_[0];
-      var height = this.size_[1];
-      var context = ol.dom.createCanvasContext2D(width, height);
-      context.fillRect(0, 0, width, height);
-      this.hitDetectionImage_ = context.canvas;
-    } else {
-      this.hitDetectionImage_ = this.image_;
-    }
-  }
-  return this.hitDetectionImage_;
-};
-
-
-/**
- * @return {ol.Size} Image size.
- */
-ol.style.IconImage.prototype.getSize = function() {
-  return this.size_;
-};
-
-
-/**
- * @return {string|undefined} Image src.
- */
-ol.style.IconImage.prototype.getSrc = function() {
-  return this.src_;
-};
-
-
-/**
- * Load not yet loaded URI.
- */
-ol.style.IconImage.prototype.load = function() {
-  if (this.imageState_ == ol.Image.State.IDLE) {
-    ol.DEBUG && console.assert(this.src_ !== undefined,
-        'this.src_ must not be undefined');
-    ol.DEBUG && console.assert(!this.imageListenerKeys_,
-        'no listener keys existing');
-    this.imageState_ = ol.Image.State.LOADING;
-    this.imageListenerKeys_ = [
-      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
-          this.handleImageError_, this),
-      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
-          this.handleImageLoad_, this)
-    ];
-    try {
-      this.image_.src = this.src_;
-    } catch (e) {
-      this.handleImageError_();
-    }
-  }
-};
-
-
-/**
- * @private
- */
-ol.style.IconImage.prototype.replaceColor_ = function() {
-  if (this.tainting_ || this.color_ === null) {
-    return;
-  }
-
-  this.canvas_.width = this.image_.width;
-  this.canvas_.height = this.image_.height;
-
-  var ctx = this.canvas_.getContext('2d');
-  ctx.drawImage(this.image_, 0, 0);
-
-  var imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
-  var data = imgData.data;
-  var r = this.color_[0] / 255.0;
-  var g = this.color_[1] / 255.0;
-  var b = this.color_[2] / 255.0;
-
-  for (var i = 0, ii = data.length; i < ii; i += 4) {
-    data[i] *= r;
-    data[i + 1] *= g;
-    data[i + 2] *= b;
-  }
-  ctx.putImageData(imgData, 0, 0);
-};
-
-
-/**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
- */
-ol.style.IconImage.prototype.unlistenImage_ = function() {
-  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
-};
-
-goog.provide('ol.style.Icon');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.color');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.Image');
-goog.require('ol.style.IconImage');
-goog.require('ol.style.Image');
-
-
-/**
- * @classdesc
- * Set icon style for vector features.
- *
- * @constructor
- * @param {olx.style.IconOptions=} opt_options Options.
- * @extends {ol.style.Image}
- * @api
- */
-ol.style.Icon = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5];
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.normalizedAnchor_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.Icon.Origin}
-   */
-  this.anchorOrigin_ = options.anchorOrigin !== undefined ?
-      options.anchorOrigin : ol.style.Icon.Origin.TOP_LEFT;
-
-  /**
-   * @private
-   * @type {ol.style.Icon.AnchorUnits}
-   */
-  this.anchorXUnits_ = options.anchorXUnits !== undefined ?
-      options.anchorXUnits : ol.style.Icon.AnchorUnits.FRACTION;
-
-  /**
-   * @private
-   * @type {ol.style.Icon.AnchorUnits}
-   */
-  this.anchorYUnits_ = options.anchorYUnits !== undefined ?
-      options.anchorYUnits : ol.style.Icon.AnchorUnits.FRACTION;
-
-  /**
-   * @private
-   * @type {?string}
-   */
-  this.crossOrigin_ =
-      options.crossOrigin !== undefined ? options.crossOrigin : null;
-
-  /**
-   * @type {Image|HTMLCanvasElement}
-   */
-  var image = options.img !== undefined ? options.img : null;
-
-  /**
-   * @type {ol.Size}
-   */
-  var imgSize = options.imgSize !== undefined ? options.imgSize : null;
-
-  /**
-   * @type {string|undefined}
-   */
-  var src = options.src;
-
-  ol.asserts.assert(!(src !== undefined && image),
-      4); // `image` and `src` cannot be provided at the same time
-  ol.asserts.assert(!image || (image && imgSize),
-      5); // `imgSize` must be set when `image` is provided
-
-  if ((src === undefined || src.length === 0) && image) {
-    src = image.src || ol.getUid(image).toString();
-  }
-  ol.asserts.assert(src !== undefined && src.length > 0,
-      6); // A defined and non-empty `src` or `image` must be provided
-
-  /**
-   * @type {ol.Image.State}
-   */
-  var imageState = options.src !== undefined ?
-      ol.Image.State.IDLE : ol.Image.State.LOADED;
-
-  /**
-   * @private
-   * @type {ol.Color}
-   */
-  this.color_ = options.color !== undefined ? ol.color.asArray(options.color) :
-      null;
-
-  /**
-   * @private
-   * @type {ol.style.IconImage}
-   */
-  this.iconImage_ = ol.style.IconImage.get(
-      image, /** @type {string} */ (src), imgSize, this.crossOrigin_, imageState, this.color_);
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.offset_ = options.offset !== undefined ? options.offset : [0, 0];
-
-  /**
-   * @private
-   * @type {ol.style.Icon.Origin}
-   */
-  this.offsetOrigin_ = options.offsetOrigin !== undefined ?
-      options.offsetOrigin : ol.style.Icon.Origin.TOP_LEFT;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.origin_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.size_ = options.size !== undefined ? options.size : null;
-
-  /**
-   * @type {number}
-   */
-  var opacity = options.opacity !== undefined ? options.opacity : 1;
-
-  /**
-   * @type {boolean}
-   */
-  var rotateWithView = options.rotateWithView !== undefined ?
-      options.rotateWithView : false;
-
-  /**
-   * @type {number}
-   */
-  var rotation = options.rotation !== undefined ? options.rotation : 0;
-
-  /**
-   * @type {number}
-   */
-  var scale = options.scale !== undefined ? options.scale : 1;
-
-  /**
-   * @type {boolean}
-   */
-  var snapToPixel = options.snapToPixel !== undefined ?
-      options.snapToPixel : true;
-
-  ol.style.Image.call(this, {
-    opacity: opacity,
-    rotation: rotation,
-    scale: scale,
-    snapToPixel: snapToPixel,
-    rotateWithView: rotateWithView
-  });
-
-};
-ol.inherits(ol.style.Icon, ol.style.Image);
-
-
-/**
- * Clones the style.
- * @return {ol.style.Icon} The cloned style.
- * @api
- */
-ol.style.Icon.prototype.clone = function() {
-  var oldImage = this.getImage(1);
-  var newImage;
-  if (this.iconImage_.getImageState() === ol.Image.State.LOADED) {
-    if (oldImage.tagName.toUpperCase() === 'IMG') {
-      newImage = /** @type {Image} */ (oldImage.cloneNode(true));
-    } else {
-      newImage = /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
-      var context = newImage.getContext('2d');
-      newImage.width = oldImage.width;
-      newImage.height = oldImage.height;
-      context.drawImage(oldImage, 0, 0);
-    }
-  }
-  return new ol.style.Icon({
-    anchor: this.anchor_.slice(),
-    anchorOrigin: this.anchorOrigin_,
-    anchorXUnits: this.anchorXUnits_,
-    anchorYUnits: this.anchorYUnits_,
-    crossOrigin: this.crossOrigin_,
-    color: (this.color_ && this.color_.slice) ? this.color_.slice() : this.color_ || undefined,
-    img: newImage ? newImage : undefined,
-    imgSize: newImage ? this.iconImage_.getSize().slice() : undefined,
-    src: newImage ? undefined : this.getSrc(),
-    offset: this.offset_.slice(),
-    offsetOrigin: this.offsetOrigin_,
-    size: this.size_ !== null ? this.size_.slice() : undefined,
-    opacity: this.getOpacity(),
-    scale: this.getScale(),
-    snapToPixel: this.getSnapToPixel(),
-    rotation: this.getRotation(),
-    rotateWithView: this.getRotateWithView()
-  });
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.style.Icon.prototype.getAnchor = function() {
-  if (this.normalizedAnchor_) {
-    return this.normalizedAnchor_;
-  }
-  var anchor = this.anchor_;
-  var size = this.getSize();
-  if (this.anchorXUnits_ == ol.style.Icon.AnchorUnits.FRACTION ||
-      this.anchorYUnits_ == ol.style.Icon.AnchorUnits.FRACTION) {
-    if (!size) {
-      return null;
-    }
-    anchor = this.anchor_.slice();
-    if (this.anchorXUnits_ == ol.style.Icon.AnchorUnits.FRACTION) {
-      anchor[0] *= size[0];
-    }
-    if (this.anchorYUnits_ == ol.style.Icon.AnchorUnits.FRACTION) {
-      anchor[1] *= size[1];
-    }
-  }
-
-  if (this.anchorOrigin_ != ol.style.Icon.Origin.TOP_LEFT) {
-    if (!size) {
-      return null;
-    }
-    if (anchor === this.anchor_) {
-      anchor = this.anchor_.slice();
-    }
-    if (this.anchorOrigin_ == ol.style.Icon.Origin.TOP_RIGHT ||
-        this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) {
-      anchor[0] = -anchor[0] + size[0];
-    }
-    if (this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_LEFT ||
-        this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) {
-      anchor[1] = -anchor[1] + size[1];
-    }
-  }
-  this.normalizedAnchor_ = anchor;
-  return this.normalizedAnchor_;
-};
-
-
-/**
- * Get the image icon.
- * @param {number} pixelRatio Pixel ratio.
- * @return {Image|HTMLCanvasElement} Image or Canvas element.
- * @api
- */
-ol.style.Icon.prototype.getImage = function(pixelRatio) {
-  return this.iconImage_.getImage(pixelRatio);
-};
-
-
-/**
- * Real Image size used.
- * @return {ol.Size} Size.
- */
-ol.style.Icon.prototype.getImageSize = function() {
-  return this.iconImage_.getSize();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Icon.prototype.getHitDetectionImageSize = function() {
-  return this.getImageSize();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Icon.prototype.getImageState = function() {
-  return this.iconImage_.getImageState();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) {
-  return this.iconImage_.getHitDetectionImage(pixelRatio);
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.style.Icon.prototype.getOrigin = function() {
-  if (this.origin_) {
-    return this.origin_;
-  }
-  var offset = this.offset_;
-
-  if (this.offsetOrigin_ != ol.style.Icon.Origin.TOP_LEFT) {
-    var size = this.getSize();
-    var iconImageSize = this.iconImage_.getSize();
-    if (!size || !iconImageSize) {
-      return null;
-    }
-    offset = offset.slice();
-    if (this.offsetOrigin_ == ol.style.Icon.Origin.TOP_RIGHT ||
-        this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) {
-      offset[0] = iconImageSize[0] - size[0] - offset[0];
-    }
-    if (this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_LEFT ||
-        this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) {
-      offset[1] = iconImageSize[1] - size[1] - offset[1];
-    }
-  }
-  this.origin_ = offset;
-  return this.origin_;
-};
-
-
-/**
- * Get the image URL.
- * @return {string|undefined} Image src.
- * @api
- */
-ol.style.Icon.prototype.getSrc = function() {
-  return this.iconImage_.getSrc();
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.style.Icon.prototype.getSize = function() {
-  return !this.size_ ? this.iconImage_.getSize() : this.size_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) {
-  return ol.events.listen(this.iconImage_, ol.events.EventType.CHANGE,
-      listener, thisArg);
-};
-
-
-/**
- * Load not yet loaded URI.
- * When rendering a feature with an icon style, the vector renderer will
- * automatically call this method. However, you might want to call this
- * method yourself for preloading or other purposes.
- * @api
- */
-ol.style.Icon.prototype.load = function() {
-  this.iconImage_.load();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
-  ol.events.unlisten(this.iconImage_, ol.events.EventType.CHANGE,
-      listener, thisArg);
-};
-
-
-/**
- * Icon anchor units. One of 'fraction', 'pixels'.
- * @enum {string}
- */
-ol.style.Icon.AnchorUnits = {
-  FRACTION: 'fraction',
-  PIXELS: 'pixels'
-};
-
-
-/**
- * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'.
- * @enum {string}
- */
-ol.style.Icon.Origin = {
-  BOTTOM_LEFT: 'bottom-left',
-  BOTTOM_RIGHT: 'bottom-right',
-  TOP_LEFT: 'top-left',
-  TOP_RIGHT: 'top-right'
-};
-
-goog.provide('ol.style.Text');
-
-
-goog.require('ol.style.Fill');
-
-
-/**
- * @classdesc
- * Set text style for vector features.
- *
- * @constructor
- * @param {olx.style.TextOptions=} opt_options Options.
- * @api
- */
-ol.style.Text = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.font_ = options.font;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.rotation_ = options.rotation;
-
-  /**
-   * @private
-   * @type {boolean|undefined}
-   */
-  this.rotateWithView_ = options.rotateWithView;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.scale_ = options.scale;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.text_ = options.text;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.textAlign_ = options.textAlign;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.textBaseline_ = options.textBaseline;
-
-  /**
-   * @private
-   * @type {ol.style.Fill}
-   */
-  this.fill_ = options.fill !== undefined ? options.fill :
-      new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});
-
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0;
-};
-
-
-/**
- * The default fill color to use if no fill was set at construction time; a
- * blackish `#333`.
- *
- * @const {string}
- * @private
- */
-ol.style.Text.DEFAULT_FILL_COLOR_ = '#333';
-
-
-/**
- * Clones the style.
- * @return {ol.style.Text} The cloned style.
- * @api
- */
-ol.style.Text.prototype.clone = function() {
-  return new ol.style.Text({
-    font: this.getFont(),
-    rotation: this.getRotation(),
-    rotateWithView: this.getRotateWithView(),
-    scale: this.getScale(),
-    text: this.getText(),
-    textAlign: this.getTextAlign(),
-    textBaseline: this.getTextBaseline(),
-    fill: this.getFill() ? this.getFill().clone() : undefined,
-    stroke: this.getStroke() ? this.getStroke().clone() : undefined,
-    offsetX: this.getOffsetX(),
-    offsetY: this.getOffsetY()
-  });
-};
-
-
-/**
- * Get the font name.
- * @return {string|undefined} Font.
- * @api
- */
-ol.style.Text.prototype.getFont = function() {
-  return this.font_;
-};
-
-
-/**
- * Get the x-offset for the text.
- * @return {number} Horizontal text offset.
- * @api
- */
-ol.style.Text.prototype.getOffsetX = function() {
-  return this.offsetX_;
-};
-
-
-/**
- * Get the y-offset for the text.
- * @return {number} Vertical text offset.
- * @api
- */
-ol.style.Text.prototype.getOffsetY = function() {
-  return this.offsetY_;
-};
-
-
-/**
- * Get the fill style for the text.
- * @return {ol.style.Fill} Fill style.
- * @api
- */
-ol.style.Text.prototype.getFill = function() {
-  return this.fill_;
-};
-
-
-/**
- * Determine whether the text rotates with the map.
- * @return {boolean|undefined} Rotate with map.
- * @api
- */
-ol.style.Text.prototype.getRotateWithView = function() {
-  return this.rotateWithView_;
-};
-
-
-/**
- * Get the text rotation.
- * @return {number|undefined} Rotation.
- * @api
- */
-ol.style.Text.prototype.getRotation = function() {
-  return this.rotation_;
-};
-
-
-/**
- * Get the text scale.
- * @return {number|undefined} Scale.
- * @api
- */
-ol.style.Text.prototype.getScale = function() {
-  return this.scale_;
-};
-
-
-/**
- * Get the stroke style for the text.
- * @return {ol.style.Stroke} Stroke style.
- * @api
- */
-ol.style.Text.prototype.getStroke = function() {
-  return this.stroke_;
-};
-
-
-/**
- * Get the text to be rendered.
- * @return {string|undefined} Text.
- * @api
- */
-ol.style.Text.prototype.getText = function() {
-  return this.text_;
-};
-
-
-/**
- * Get the text alignment.
- * @return {string|undefined} Text align.
- * @api
- */
-ol.style.Text.prototype.getTextAlign = function() {
-  return this.textAlign_;
-};
-
-
-/**
- * Get the text baseline.
- * @return {string|undefined} Text baseline.
- * @api
- */
-ol.style.Text.prototype.getTextBaseline = function() {
-  return this.textBaseline_;
-};
-
-
-/**
- * Set the font.
- *
- * @param {string|undefined} font Font.
- * @api
- */
-ol.style.Text.prototype.setFont = function(font) {
-  this.font_ = font;
-};
-
-
-/**
- * Set the x offset.
- *
- * @param {number} offsetX Horizontal text offset.
- * @api
- */
-ol.style.Text.prototype.setOffsetX = function(offsetX) {
-  this.offsetX_ = offsetX;
-};
-
-
-/**
- * Set the y offset.
- *
- * @param {number} offsetY Vertical text offset.
- * @api
- */
-ol.style.Text.prototype.setOffsetY = function(offsetY) {
-  this.offsetY_ = offsetY;
-};
-
-
-/**
- * Set the fill.
- *
- * @param {ol.style.Fill} fill Fill style.
- * @api
- */
-ol.style.Text.prototype.setFill = function(fill) {
-  this.fill_ = fill;
-};
-
-
-/**
- * Set the rotation.
- *
- * @param {number|undefined} rotation Rotation.
- * @api
- */
-ol.style.Text.prototype.setRotation = function(rotation) {
-  this.rotation_ = rotation;
-};
-
-
-/**
- * Set the scale.
- *
- * @param {number|undefined} scale Scale.
- * @api
- */
-ol.style.Text.prototype.setScale = function(scale) {
-  this.scale_ = scale;
-};
-
-
-/**
- * Set the stroke.
- *
- * @param {ol.style.Stroke} stroke Stroke style.
- * @api
- */
-ol.style.Text.prototype.setStroke = function(stroke) {
-  this.stroke_ = stroke;
-};
-
-
-/**
- * Set the text.
- *
- * @param {string|undefined} text Text.
- * @api
- */
-ol.style.Text.prototype.setText = function(text) {
-  this.text_ = text;
-};
-
-
-/**
- * Set the text alignment.
- *
- * @param {string|undefined} textAlign Text align.
- * @api
- */
-ol.style.Text.prototype.setTextAlign = function(textAlign) {
-  this.textAlign_ = textAlign;
-};
-
-
-/**
- * Set the text baseline.
- *
- * @param {string|undefined} textBaseline Text baseline.
- * @api
- */
-ol.style.Text.prototype.setTextBaseline = function(textBaseline) {
-  this.textBaseline_ = textBaseline;
-};
-
-// FIXME http://earth.google.com/kml/1.0 namespace?
-// FIXME why does node.getAttribute return an unknown type?
-// FIXME serialize arbitrary feature properties
-// FIXME don't parse style if extractStyles is false
-
-goog.provide('ol.format.KML');
-
-goog.require('ol');
-goog.require('ol.Feature');
-goog.require('ol.array');
-goog.require('ol.asserts');
-goog.require('ol.color');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.format.XSD');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.math');
-goog.require('ol.proj');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Icon');
-goog.require('ol.style.Stroke');
-goog.require('ol.style.Style');
-goog.require('ol.style.Text');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the KML format.
- *
- * Note that the KML format uses the URL() constructor. Older browsers such as IE
- * which do not support this will need a URL polyfill to be loaded before use.
- *
- * @constructor
- * @extends {ol.format.XMLFeature}
- * @param {olx.format.KMLOptions=} opt_options Options.
- * @api stable
- */
-ol.format.KML = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.XMLFeature.call(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
-
-  /**
-   * @private
-   * @type {Array.<ol.style.Style>}
-   */
-  this.defaultStyle_ = options.defaultStyle ?
-      options.defaultStyle :
-      (ol.format.KML.DEFAULT_STYLE_ARRAY_ || ol.format.KML.createStyleDefaults_());
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.extractStyles_ = options.extractStyles !== undefined ?
-      options.extractStyles : true;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.writeStyles_ = options.writeStyles !== undefined ?
-      options.writeStyles : true;
-
-  /**
-   * @private
-   * @type {Object.<string, (Array.<ol.style.Style>|string)>}
-   */
-  this.sharedStyles_ = {};
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.showPointNames_ = options.showPointNames !== undefined ?
-      options.showPointNames : true;
-
-};
-ol.inherits(ol.format.KML, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.KML.EXTENSIONS_ = ['.kml'];
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.KML.GX_NAMESPACE_URIS_ = [
-  'http://www.google.com/kml/ext/2.2'
-];
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.KML.NAMESPACE_URIS_ = [
-  null,
-  'http://earth.google.com/kml/2.0',
-  'http://earth.google.com/kml/2.1',
-  'http://earth.google.com/kml/2.2',
-  'http://www.opengis.net/kml/2.2'
-];
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' +
-    'https://developers.google.com/kml/schema/kml22gx.xsd';
-
-
-/**
- * @return {Array.<ol.style.Style>} Default style.
- * @private
- */
-ol.format.KML.createStyleDefaults_ = function() {
-  /**
-   * @const
-   * @type {ol.Color}
-   * @private
-   */
-  ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1];
-
-  /**
-   * @const
-   * @type {ol.style.Fill}
-   * @private
-   */
-  ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({
-    color: ol.format.KML.DEFAULT_COLOR_
-  });
-
-  /**
-   * @const
-   * @type {ol.Size}
-   * @private
-   */
-  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ?
-
-  /**
-   * @const
-   * @type {ol.style.Icon.AnchorUnits}
-   * @private
-   */
-  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ =
-      ol.style.Icon.AnchorUnits.PIXELS;
-
-  /**
-   * @const
-   * @type {ol.style.Icon.AnchorUnits}
-   * @private
-   */
-  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ =
-      ol.style.Icon.AnchorUnits.PIXELS;
-
-  /**
-   * @const
-   * @type {ol.Size}
-   * @private
-   */
-  ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64];
-
-  /**
-   * @const
-   * @type {string}
-   * @private
-   */
-  ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
-      'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
-
-  /**
-   * @const
-   * @type {number}
-   * @private
-   */
-  ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_ = 0.5;
-
-  /**
-   * @const
-   * @type {ol.style.Image}
-   * @private
-   */
-  ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({
-    anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_,
-    anchorOrigin: ol.style.Icon.Origin.BOTTOM_LEFT,
-    anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_,
-    anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_,
-    crossOrigin: 'anonymous',
-    rotation: 0,
-    scale: ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_,
-    size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_,
-    src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_
-  });
-
-  /**
-   * @const
-   * @type {string}
-   * @private
-   */
-  ol.format.KML.DEFAULT_NO_IMAGE_STYLE_ = 'NO_IMAGE';
-
-  /**
-   * @const
-   * @type {ol.style.Stroke}
-   * @private
-   */
-  ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
-    color: ol.format.KML.DEFAULT_COLOR_,
-    width: 1
-  });
-
-  /**
-   * @const
-   * @type {ol.style.Stroke}
-   * @private
-   */
-  ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_ = new ol.style.Stroke({
-    color: [51, 51, 51, 1],
-    width: 2
-  });
-
-  /**
-   * @const
-   * @type {ol.style.Text}
-   * @private
-   */
-  ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({
-    font: 'bold 16px Helvetica',
-    fill: ol.format.KML.DEFAULT_FILL_STYLE_,
-    stroke: ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_,
-    scale: 0.8
-  });
-
-  /**
-   * @const
-   * @type {ol.style.Style}
-   * @private
-   */
-  ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({
-    fill: ol.format.KML.DEFAULT_FILL_STYLE_,
-    image: ol.format.KML.DEFAULT_IMAGE_STYLE_,
-    text: ol.format.KML.DEFAULT_TEXT_STYLE_,
-    stroke: ol.format.KML.DEFAULT_STROKE_STYLE_,
-    zIndex: 0
-  });
-
-  /**
-   * @const
-   * @type {Array.<ol.style.Style>}
-   * @private
-   */
-  ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_];
-
-  return ol.format.KML.DEFAULT_STYLE_ARRAY_;
-};
-
-
-/**
- * @const
- * @type {Object.<string, ol.style.Icon.AnchorUnits>}
- * @private
- */
-ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = {
-  'fraction': ol.style.Icon.AnchorUnits.FRACTION,
-  'pixels': ol.style.Icon.AnchorUnits.PIXELS
-};
-
-
-/**
- * @param {ol.style.Style|undefined} foundStyle Style.
- * @param {string} name Name.
- * @return {ol.style.Style} style Style.
- * @private
- */
-ol.format.KML.createNameStyleFunction_ = function(foundStyle, name) {
-  var textStyle = null;
-  var textOffset = [0, 0];
-  var textAlign = 'start';
-  if (foundStyle.getImage()) {
-    var imageSize = foundStyle.getImage().getImageSize();
-    if (imageSize === null) {
-      imageSize = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
-    }
-    var imageScale = foundStyle.getImage().getScale();
-    if (isNaN(imageScale)) {
-      imageScale = ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_;
-    }
-    if (imageSize.length == 2) {
-      // Offset the label to be centered to the right of the icon, if there is
-      // one.
-      textOffset[0] = imageScale * imageSize[0] / 2;
-      textOffset[1] = -imageScale * imageSize[1] / 2;
-      textAlign = 'left';
-    }
-  }
-  if (foundStyle.getText() !== null) {
-    // clone the text style, customizing it with name, alignments and offset.
-    // Note that kml does not support many text options that OpenLayers does (rotation, textBaseline).
-    var foundText = foundStyle.getText();
-    textStyle = foundText.clone();
-    textStyle.setFont(foundText.getFont() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFont());
-    textStyle.setScale(foundText.getScale() || ol.format.KML.DEFAULT_TEXT_STYLE_.getScale());
-    textStyle.setFill(foundText.getFill() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFill());
-    textStyle.setStroke(foundText.getStroke() || ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_);
-  } else {
-    textStyle = ol.format.KML.DEFAULT_TEXT_STYLE_.clone();
-  }
-  textStyle.setText(name);
-  textStyle.setOffsetX(textOffset[0]);
-  textStyle.setOffsetY(textOffset[1]);
-  textStyle.setTextAlign(textAlign);
-
-  var nameStyle = new ol.style.Style({
-    text: textStyle
-  });
-  return nameStyle;
-};
-
-
-/**
- * @param {Array.<ol.style.Style>|undefined} style Style.
- * @param {string} styleUrl Style URL.
- * @param {Array.<ol.style.Style>} defaultStyle Default style.
- * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles Shared
- *          styles.
- * @param {boolean|undefined} showPointNames true to show names for point
- *          placemarks.
- * @return {ol.FeatureStyleFunction} Feature style function.
- * @private
- */
-ol.format.KML.createFeatureStyleFunction_ = function(style, styleUrl,
-    defaultStyle, sharedStyles, showPointNames) {
-
-  return (
-      /**
-       * @param {number} resolution Resolution.
-       * @return {Array.<ol.style.Style>} Style.
-       * @this {ol.Feature}
-       */
-      function(resolution) {
-        var drawName = showPointNames;
-        /** @type {ol.style.Style|undefined} */
-        var nameStyle;
-        var name = '';
-        if (drawName) {
-          if (this.getGeometry()) {
-            drawName = (this.getGeometry().getType() ===
-                        ol.geom.GeometryType.POINT);
-          }
-        }
-
-        if (drawName) {
-          name = /** @type {string} */ (this.get('name'));
-          drawName = drawName && name;
-        }
-
-        if (style) {
-          if (drawName) {
-            nameStyle = ol.format.KML.createNameStyleFunction_(style[0],
-                name);
-            return style.concat(nameStyle);
-          }
-          return style;
-        }
-        if (styleUrl) {
-          var foundStyle = ol.format.KML.findStyle_(styleUrl, defaultStyle,
-              sharedStyles);
-          if (drawName) {
-            nameStyle = ol.format.KML.createNameStyleFunction_(foundStyle[0],
-                name);
-            return foundStyle.concat(nameStyle);
-          }
-          return foundStyle;
-        }
-        if (drawName) {
-          nameStyle = ol.format.KML.createNameStyleFunction_(defaultStyle[0],
-              name);
-          return defaultStyle.concat(nameStyle);
-        }
-        return defaultStyle;
-      });
-};
-
-
-/**
- * @param {Array.<ol.style.Style>|string|undefined} styleValue Style value.
- * @param {Array.<ol.style.Style>} defaultStyle Default style.
- * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles
- * Shared styles.
- * @return {Array.<ol.style.Style>} Style.
- * @private
- */
-ol.format.KML.findStyle_ = function(styleValue, defaultStyle, sharedStyles) {
-  if (Array.isArray(styleValue)) {
-    return styleValue;
-  } else if (typeof styleValue === 'string') {
-    // KML files in the wild occasionally forget the leading `#` on styleUrls
-    // defined in the same document.  Add a leading `#` if it enables to find
-    // a style.
-    if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) {
-      styleValue = '#' + styleValue;
-    }
-    return ol.format.KML.findStyle_(
-        sharedStyles[styleValue], defaultStyle, sharedStyles);
-  } else {
-    return defaultStyle;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @private
- * @return {ol.Color|undefined} Color.
- */
-ol.format.KML.readColor_ = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  // The KML specification states that colors should not include a leading `#`
-  // but we tolerate them.
-  var m = /^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(s);
-  if (m) {
-    var hexColor = m[1];
-    return [
-      parseInt(hexColor.substr(6, 2), 16),
-      parseInt(hexColor.substr(4, 2), 16),
-      parseInt(hexColor.substr(2, 2), 16),
-      parseInt(hexColor.substr(0, 2), 16) / 255
-    ];
-
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @private
- * @return {Array.<number>|undefined} Flat coordinates.
- */
-ol.format.KML.readFlatCoordinates_ = function(node) {
-  var s = ol.xml.getAllTextContent(node, false);
-  var flatCoordinates = [];
-  // The KML specification states that coordinate tuples should not include
-  // spaces, but we tolerate them.
-  var re =
-      /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i;
-  var m;
-  while ((m = re.exec(s))) {
-    var x = parseFloat(m[1]);
-    var y = parseFloat(m[2]);
-    var z = m[3] ? parseFloat(m[3]) : 0;
-    flatCoordinates.push(x, y, z);
-    s = s.substr(m[0].length);
-  }
-  if (s !== '') {
-    return undefined;
-  }
-  return flatCoordinates;
-};
-
-
-/**
- * @param {Node} node Node.
- * @private
- * @return {string} URI.
- */
-ol.format.KML.readURI_ = function(node) {
-  var s = ol.xml.getAllTextContent(node, false).trim();
-  if (node.baseURI) {
-    var url = new URL(s, node.baseURI);
-    return url.href;
-  } else {
-    return s;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @private
- * @return {ol.KMLVec2_} Vec2.
- */
-ol.format.KML.readVec2_ = function(node) {
-  var xunits = node.getAttribute('xunits');
-  var yunits = node.getAttribute('yunits');
-  return {
-    x: parseFloat(node.getAttribute('x')),
-    xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits],
-    y: parseFloat(node.getAttribute('y')),
-    yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits]
-  };
-};
-
-
-/**
- * @param {Node} node Node.
- * @private
- * @return {number|undefined} Scale.
- */
-ol.format.KML.readScale_ = function(node) {
-  return ol.format.XSD.readDecimal(node);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<ol.style.Style>|string|undefined} StyleMap.
- */
-ol.format.KML.readStyleMapValue_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(undefined,
-      ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.IconStyleParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be an ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'IconStyle',
-      'localName should be IconStyle');
-  // FIXME refreshMode
-  // FIXME refreshInterval
-  // FIXME viewRefreshTime
-  // FIXME viewBoundScale
-  // FIXME viewFormat
-  // FIXME httpQuery
-  var object = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.ICON_STYLE_PARSERS_, node, objectStack);
-  if (!object) {
-    return;
-  }
-  var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var IconObject = 'Icon' in object ? object['Icon'] : {};
-  var drawIcon = (!('Icon' in object) || Object.keys(IconObject).length > 0);
-  var src;
-  var href = /** @type {string|undefined} */
-      (IconObject['href']);
-  if (href) {
-    src = href;
-  } else if (drawIcon) {
-    src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_;
-  }
-  var anchor, anchorXUnits, anchorYUnits;
-  var hotSpot = /** @type {ol.KMLVec2_|undefined} */
-      (object['hotSpot']);
-  if (hotSpot) {
-    anchor = [hotSpot.x, hotSpot.y];
-    anchorXUnits = hotSpot.xunits;
-    anchorYUnits = hotSpot.yunits;
-  } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
-    anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_;
-    anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_;
-    anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_;
-  } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) {
-    anchor = [0.5, 0];
-    anchorXUnits = ol.style.Icon.AnchorUnits.FRACTION;
-    anchorYUnits = ol.style.Icon.AnchorUnits.FRACTION;
-  }
-
-  var offset;
-  var x = /** @type {number|undefined} */
-      (IconObject['x']);
-  var y = /** @type {number|undefined} */
-      (IconObject['y']);
-  if (x !== undefined && y !== undefined) {
-    offset = [x, y];
-  }
-
-  var size;
-  var w = /** @type {number|undefined} */
-      (IconObject['w']);
-  var h = /** @type {number|undefined} */
-      (IconObject['h']);
-  if (w !== undefined && h !== undefined) {
-    size = [w, h];
-  }
-
-  var rotation;
-  var heading = /** @type {number} */
-      (object['heading']);
-  if (heading !== undefined) {
-    rotation = ol.math.toRadians(heading);
-  }
-
-  var scale = /** @type {number|undefined} */
-      (object['scale']);
-  if (isNaN(scale) || scale === undefined) {
-    scale = ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_;
-  } else {
-    scale = scale * ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_;
-  }
-
-  if (drawIcon) {
-    if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
-      size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
-      if (scale === undefined) {
-        scale = ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_;
-      }
-    }
-
-    var imageStyle = new ol.style.Icon({
-      anchor: anchor,
-      anchorOrigin: ol.style.Icon.Origin.BOTTOM_LEFT,
-      anchorXUnits: anchorXUnits,
-      anchorYUnits: anchorYUnits,
-      crossOrigin: 'anonymous', // FIXME should this be configurable?
-      offset: offset,
-      offsetOrigin: ol.style.Icon.Origin.BOTTOM_LEFT,
-      rotation: rotation,
-      scale: scale,
-      size: size,
-      src: src
-    });
-    styleObject['imageStyle'] = imageStyle;
-  } else {
-    // handle the case when we explicitly want to draw no icon.
-    styleObject['imageStyle'] = ol.format.KML.DEFAULT_NO_IMAGE_STYLE_;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.LabelStyleParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LabelStyle',
-      'localName should be LabelStyle');
-  // FIXME colorMode
-  var object = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack);
-  if (!object) {
-    return;
-  }
-  var styleObject = objectStack[objectStack.length - 1];
-  var textStyle = new ol.style.Text({
-    fill: new ol.style.Fill({
-      color: /** @type {ol.Color} */
-          ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_)
-    }),
-    scale: /** @type {number|undefined} */
-        (object['scale'])
-  });
-  styleObject['textStyle'] = textStyle;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.LineStyleParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LineStyle',
-      'localName should be LineStyle');
-  // FIXME colorMode
-  // FIXME gx:outerColor
-  // FIXME gx:outerWidth
-  // FIXME gx:physicalWidth
-  // FIXME gx:labelVisibility
-  var object = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.LINE_STYLE_PARSERS_, node, objectStack);
-  if (!object) {
-    return;
-  }
-  var styleObject = objectStack[objectStack.length - 1];
-  var strokeStyle = new ol.style.Stroke({
-    color: /** @type {ol.Color} */
-        ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_),
-    width: /** @type {number} */ ('width' in object ? object['width'] : 1)
-  });
-  styleObject['strokeStyle'] = strokeStyle;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.PolyStyleParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'PolyStyle',
-      'localName should be PolyStyle');
-  // FIXME colorMode
-  var object = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack);
-  if (!object) {
-    return;
-  }
-  var styleObject = objectStack[objectStack.length - 1];
-  var fillStyle = new ol.style.Fill({
-    color: /** @type {ol.Color} */
-        ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_)
-  });
-  styleObject['fillStyle'] = fillStyle;
-  var fill = /** @type {boolean|undefined} */ (object['fill']);
-  if (fill !== undefined) {
-    styleObject['fill'] = fill;
-  }
-  var outline =
-      /** @type {boolean|undefined} */ (object['outline']);
-  if (outline !== undefined) {
-    styleObject['outline'] = outline;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>} LinearRing flat coordinates.
- */
-ol.format.KML.readFlatLinearRing_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LinearRing',
-      'localName should be LinearRing');
-  return ol.xml.pushParseAndPop(null,
-      ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.gxCoordParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(ol.array.includes(
-      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI),
-      'namespaceURI of the node should be known to the KML parser');
-  ol.DEBUG && console.assert(node.localName == 'coord', 'localName should be coord');
-  var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */
-      (objectStack[objectStack.length - 1]);
-  var flatCoordinates = gxTrackObject.flatCoordinates;
-  var s = ol.xml.getAllTextContent(node, false);
-  var re =
-      /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i;
-  var m = re.exec(s);
-  if (m) {
-    var x = parseFloat(m[1]);
-    var y = parseFloat(m[2]);
-    var z = parseFloat(m[3]);
-    flatCoordinates.push(x, y, z, 0);
-  } else {
-    flatCoordinates.push(0, 0, 0, 0);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.MultiLineString|undefined} MultiLineString.
- */
-ol.format.KML.readGxMultiTrack_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(ol.array.includes(
-      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI),
-      'namespaceURI of the node should be known to the KML parser');
-  ol.DEBUG && console.assert(node.localName == 'MultiTrack',
-      'localName should be MultiTrack');
-  var lineStrings = ol.xml.pushParseAndPop([],
-      ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack);
-  if (!lineStrings) {
-    return undefined;
-  }
-  var multiLineString = new ol.geom.MultiLineString(null);
-  multiLineString.setLineStrings(lineStrings);
-  return multiLineString;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.LineString|undefined} LineString.
- */
-ol.format.KML.readGxTrack_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(ol.array.includes(
-      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI),
-      'namespaceURI of the node should be known to the KML parser');
-  ol.DEBUG && console.assert(node.localName == 'Track', 'localName should be Track');
-  var gxTrackObject = ol.xml.pushParseAndPop(
-      /** @type {ol.KMLGxTrackObject_} */ ({
-        flatCoordinates: [],
-        whens: []
-      }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack);
-  if (!gxTrackObject) {
-    return undefined;
-  }
-  var flatCoordinates = gxTrackObject.flatCoordinates;
-  var whens = gxTrackObject.whens;
-  ol.DEBUG && console.assert(flatCoordinates.length / 4 == whens.length,
-      'the length of the flatCoordinates array divided by 4 should be the ' +
-      'length of the whens array');
-  var i, ii;
-  for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii;
-       ++i) {
-    flatCoordinates[4 * i + 3] = whens[i];
-  }
-  var lineString = new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
-  return lineString;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object} Icon object.
- */
-ol.format.KML.readIcon_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Icon', 'localName should be Icon');
-  var iconObject = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.ICON_PARSERS_, node, objectStack);
-  if (iconObject) {
-    return iconObject;
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<number>} Flat coordinates.
- */
-ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  return ol.xml.pushParseAndPop(null,
-      ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.LineString|undefined} LineString.
- */
-ol.format.KML.readLineString_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LineString',
-      'localName should be LineString');
-  var properties = ol.xml.pushParseAndPop({},
-      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
-      objectStack);
-  var flatCoordinates =
-      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (flatCoordinates) {
-    var lineString = new ol.geom.LineString(null);
-    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    lineString.setProperties(properties);
-    return lineString;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Polygon|undefined} Polygon.
- */
-ol.format.KML.readLinearRing_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'LinearRing',
-      'localName should be LinearRing');
-  var properties = ol.xml.pushParseAndPop({},
-      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
-      objectStack);
-  var flatCoordinates =
-      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (flatCoordinates) {
-    var polygon = new ol.geom.Polygon(null);
-    polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates,
-        [flatCoordinates.length]);
-    polygon.setProperties(properties);
-    return polygon;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.KML.readMultiGeometry_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'MultiGeometry',
-      'localName should be MultiGeometry');
-  var geometries = ol.xml.pushParseAndPop([],
-      ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack);
-  if (!geometries) {
-    return null;
-  }
-  if (geometries.length === 0) {
-    return new ol.geom.GeometryCollection(geometries);
-  }
-  /** @type {ol.geom.Geometry} */
-  var multiGeometry;
-  var homogeneous = true;
-  var type = geometries[0].getType();
-  var geometry, i, ii;
-  for (i = 1, ii = geometries.length; i < ii; ++i) {
-    geometry = geometries[i];
-    if (geometry.getType() != type) {
-      homogeneous = false;
-      break;
-    }
-  }
-  if (homogeneous) {
-    var layout;
-    var flatCoordinates;
-    if (type == ol.geom.GeometryType.POINT) {
-      var point = geometries[0];
-      layout = point.getLayout();
-      flatCoordinates = point.getFlatCoordinates();
-      for (i = 1, ii = geometries.length; i < ii; ++i) {
-        geometry = geometries[i];
-        ol.DEBUG && console.assert(geometry.getLayout() == layout,
-            'geometry layout should be consistent');
-        ol.array.extend(flatCoordinates, geometry.getFlatCoordinates());
-      }
-      multiGeometry = new ol.geom.MultiPoint(null);
-      multiGeometry.setFlatCoordinates(layout, flatCoordinates);
-      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
-    } else if (type == ol.geom.GeometryType.LINE_STRING) {
-      multiGeometry = new ol.geom.MultiLineString(null);
-      multiGeometry.setLineStrings(geometries);
-      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
-    } else if (type == ol.geom.GeometryType.POLYGON) {
-      multiGeometry = new ol.geom.MultiPolygon(null);
-      multiGeometry.setPolygons(geometries);
-      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
-    } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
-      multiGeometry = new ol.geom.GeometryCollection(geometries);
-    } else {
-      ol.asserts.assert(false, 37); // Unknown geometry type found
-    }
-  } else {
-    multiGeometry = new ol.geom.GeometryCollection(geometries);
-  }
-  return /** @type {ol.geom.Geometry} */ (multiGeometry);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Point|undefined} Point.
- */
-ol.format.KML.readPoint_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point');
-  var properties = ol.xml.pushParseAndPop({},
-      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
-      objectStack);
-  var flatCoordinates =
-      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
-  if (flatCoordinates) {
-    var point = new ol.geom.Point(null);
-    ol.DEBUG && console.assert(flatCoordinates.length == 3,
-        'flatCoordinates should have a length of 3');
-    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
-    point.setProperties(properties);
-    return point;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.geom.Polygon|undefined} Polygon.
- */
-ol.format.KML.readPolygon_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Polygon',
-      'localName should be Polygon');
-  var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}),
-      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
-      objectStack);
-  var flatLinearRings = ol.xml.pushParseAndPop([null],
-      ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack);
-  if (flatLinearRings && flatLinearRings[0]) {
-    var polygon = new ol.geom.Polygon(null);
-    var flatCoordinates = flatLinearRings[0];
-    var ends = [flatCoordinates.length];
-    var i, ii;
-    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
-      ol.array.extend(flatCoordinates, flatLinearRings[i]);
-      ends.push(flatCoordinates.length);
-    }
-    polygon.setFlatCoordinates(
-        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
-    polygon.setProperties(properties);
-    return polygon;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<ol.style.Style>} Style.
- */
-ol.format.KML.readStyle_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style');
-  var styleObject = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.STYLE_PARSERS_, node, objectStack);
-  if (!styleObject) {
-    return null;
-  }
-  var fillStyle = /** @type {ol.style.Fill} */
-      ('fillStyle' in styleObject ?
-          styleObject['fillStyle'] : ol.format.KML.DEFAULT_FILL_STYLE_);
-  var fill = /** @type {boolean|undefined} */ (styleObject['fill']);
-  if (fill !== undefined && !fill) {
-    fillStyle = null;
-  }
-  var imageStyle = /** @type {ol.style.Image} */
-      ('imageStyle' in styleObject ?
-          styleObject['imageStyle'] : ol.format.KML.DEFAULT_IMAGE_STYLE_);
-  if (imageStyle == ol.format.KML.DEFAULT_NO_IMAGE_STYLE_) {
-    imageStyle = undefined;
-  }
-  var textStyle = /** @type {ol.style.Text} */
-      ('textStyle' in styleObject ?
-          styleObject['textStyle'] : ol.format.KML.DEFAULT_TEXT_STYLE_);
-  var strokeStyle = /** @type {ol.style.Stroke} */
-      ('strokeStyle' in styleObject ?
-          styleObject['strokeStyle'] : ol.format.KML.DEFAULT_STROKE_STYLE_);
-  var outline = /** @type {boolean|undefined} */
-      (styleObject['outline']);
-  if (outline !== undefined && !outline) {
-    strokeStyle = null;
-  }
-  return [new ol.style.Style({
-    fill: fillStyle,
-    image: imageStyle,
-    stroke: strokeStyle,
-    text: textStyle,
-    zIndex: undefined // FIXME
-  })];
-};
-
-
-/**
- * Reads an array of geometries and creates arrays for common geometry
- * properties. Then sets them to the multi geometry.
- * @param {ol.geom.MultiPoint|ol.geom.MultiLineString|ol.geom.MultiPolygon}
- *     multiGeometry A multi-geometry.
- * @param {Array.<ol.geom.Geometry>} geometries List of geometries.
- * @private
- */
-ol.format.KML.setCommonGeometryProperties_ = function(multiGeometry,
-    geometries) {
-  var ii = geometries.length;
-  var extrudes = new Array(geometries.length);
-  var altitudeModes = new Array(geometries.length);
-  var geometry, i, hasExtrude, hasAltitudeMode;
-  hasExtrude = hasAltitudeMode = false;
-  for (i = 0; i < ii; ++i) {
-    geometry = geometries[i];
-    extrudes[i] = geometry.get('extrude');
-    altitudeModes[i] = geometry.get('altitudeMode');
-    hasExtrude = hasExtrude || extrudes[i] !== undefined;
-    hasAltitudeMode = hasAltitudeMode || altitudeModes[i];
-  }
-  if (hasExtrude) {
-    multiGeometry.set('extrude', extrudes);
-  }
-  if (hasAltitudeMode) {
-    multiGeometry.set('altitudeMode', altitudeModes);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.DataParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Data', 'localName should be Data');
-  var name = node.getAttribute('name');
-  if (name !== null) {
-    var data = ol.xml.pushParseAndPop(
-        undefined, ol.format.KML.DATA_PARSERS_, node, objectStack);
-    if (data) {
-      var featureObject =
-          /** @type {Object} */ (objectStack[objectStack.length - 1]);
-      featureObject[name] = data;
-    }
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.ExtendedDataParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ExtendedData',
-      'localName should be ExtendedData');
-  ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.PairDataParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Pair', 'localName should be Pair');
-  var pairObject = ol.xml.pushParseAndPop(
-      {}, ol.format.KML.PAIR_PARSERS_, node, objectStack);
-  if (!pairObject) {
-    return;
-  }
-  var key = /** @type {string|undefined} */
-      (pairObject['key']);
-  if (key && key == 'normal') {
-    var styleUrl = /** @type {string|undefined} */
-        (pairObject['styleUrl']);
-    if (styleUrl) {
-      objectStack[objectStack.length - 1] = styleUrl;
-    }
-    var Style = /** @type {ol.style.Style} */
-        (pairObject['Style']);
-    if (Style) {
-      objectStack[objectStack.length - 1] = Style;
-    }
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'StyleMap',
-      'localName should be StyleMap');
-  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
-  if (!styleMapValue) {
-    return;
-  }
-  var placemarkObject = objectStack[objectStack.length - 1];
-  if (Array.isArray(styleMapValue)) {
-    placemarkObject['Style'] = styleMapValue;
-  } else if (typeof styleMapValue === 'string') {
-    placemarkObject['styleUrl'] = styleMapValue;
-  } else {
-    ol.asserts.assert(false, 38); // `styleMapValue` has an unknown type
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.SchemaDataParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'SchemaData',
-      'localName should be SchemaData');
-  ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.SimpleDataParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'SimpleData',
-      'localName should be SimpleData');
-  var name = node.getAttribute('name');
-  if (name !== null) {
-    var data = ol.format.XSD.readString(node);
-    var featureObject =
-        /** @type {Object} */ (objectStack[objectStack.length - 1]);
-    featureObject[name] = data;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'innerBoundaryIs',
-      'localName should be innerBoundaryIs');
-  /** @type {Array.<number>|undefined} */
-  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
-      ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack);
-  if (flatLinearRing) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    ol.DEBUG && console.assert(Array.isArray(flatLinearRings),
-        'flatLinearRings should be an array');
-    ol.DEBUG && console.assert(flatLinearRings.length > 0,
-        'flatLinearRings array should not be empty');
-    flatLinearRings.push(flatLinearRing);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'outerBoundaryIs',
-      'localName should be outerBoundaryIs');
-  /** @type {Array.<number>|undefined} */
-  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
-      ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack);
-  if (flatLinearRing) {
-    var flatLinearRings = /** @type {Array.<Array.<number>>} */
-        (objectStack[objectStack.length - 1]);
-    ol.DEBUG && console.assert(Array.isArray(flatLinearRings),
-        'flatLinearRings should be an array');
-    ol.DEBUG && console.assert(flatLinearRings.length > 0,
-        'flatLinearRings array should not be empty');
-    flatLinearRings[0] = flatLinearRing;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.LinkParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Link', 'localName should be Link');
-  ol.xml.parseNode(ol.format.KML.LINK_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.whenParser_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'when', 'localName should be when');
-  var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */
-      (objectStack[objectStack.length - 1]);
-  var whens = gxTrackObject.whens;
-  var s = ol.xml.getAllTextContent(node, false);
-  var when = Date.parse(s);
-  whens.push(isNaN(when) ? 0 : when);
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'value': ol.xml.makeReplacer(ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Data': ol.format.KML.DataParser_,
-      'SchemaData': ol.format.KML.SchemaDataParser_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'extrude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
-      'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_,
-      'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'when': ol.format.KML.whenParser_
-    }, ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'coord': ol.format.KML.gxCoordParser_
-        }));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.ICON_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
-    }, ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-          'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-          'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-          'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
-        }));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_),
-      'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
-      'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_),
-      'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
-      'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
-      'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LineString': ol.xml.makeArrayPusher(ol.format.KML.readLineString_),
-      'LinearRing': ol.xml.makeArrayPusher(ol.format.KML.readLinearRing_),
-      'MultiGeometry': ol.xml.makeArrayPusher(ol.format.KML.readMultiGeometry_),
-      'Point': ol.xml.makeArrayPusher(ol.format.KML.readPoint_),
-      'Polygon': ol.xml.makeArrayPusher(ol.format.KML.readPolygon_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.GX_NAMESPACE_URIS_, {
-      'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'ExtendedData': ol.format.KML.ExtendedDataParser_,
-      'Link': ol.format.KML.LinkParser_,
-      'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
-      'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.LINK_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.PAIR_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_),
-      'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'ExtendedData': ol.format.KML.ExtendedDataParser_,
-      'MultiGeometry': ol.xml.makeObjectPropertySetter(
-          ol.format.KML.readMultiGeometry_, 'geometry'),
-      'LineString': ol.xml.makeObjectPropertySetter(
-          ol.format.KML.readLineString_, 'geometry'),
-      'LinearRing': ol.xml.makeObjectPropertySetter(
-          ol.format.KML.readLinearRing_, 'geometry'),
-      'Point': ol.xml.makeObjectPropertySetter(
-          ol.format.KML.readPoint_, 'geometry'),
-      'Polygon': ol.xml.makeObjectPropertySetter(
-          ol.format.KML.readPolygon_, 'geometry'),
-      'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_),
-      'StyleMap': ol.format.KML.PlacemarkStyleMapParser_,
-      'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
-      'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_),
-      'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
-    }, ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'MultiTrack': ol.xml.makeObjectPropertySetter(
-              ol.format.KML.readGxMultiTrack_, 'geometry'),
-          'Track': ol.xml.makeObjectPropertySetter(
-              ol.format.KML.readGxTrack_, 'geometry')
-        }
-    ));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
-      'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
-      'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'SimpleData': ol.format.KML.SimpleDataParser_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.STYLE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'IconStyle': ol.format.KML.IconStyleParser_,
-      'LabelStyle': ol.format.KML.LabelStyleParser_,
-      'LineStyle': ol.format.KML.LineStyleParser_,
-      'PolyStyle': ol.format.KML.PolyStyleParser_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Pair': ol.format.KML.PairDataParser_
-    });
-
-
-/**
- * @inheritDoc
- */
-ol.format.KML.prototype.getExtensions = function() {
-  return ol.format.KML.EXTENSIONS_;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<ol.Feature>|undefined} Features.
- */
-ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  var localName = node.localName;
-  ol.DEBUG && console.assert(localName == 'Document' || localName == 'Folder',
-      'localName should be Document or Folder');
-  // FIXME use scope somehow
-  var parsersNS = ol.xml.makeStructureNS(
-      ol.format.KML.NAMESPACE_URIS_, {
-        'Document': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this),
-        'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this),
-        'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this),
-        'Style': this.readSharedStyle_.bind(this),
-        'StyleMap': this.readSharedStyleMap_.bind(this)
-      });
-  /** @type {Array.<ol.Feature>} */
-  var features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack, this);
-  if (features) {
-    return features;
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {ol.Feature|undefined} Feature.
- */
-ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Placemark',
-      'localName should be Placemark');
-  var object = ol.xml.pushParseAndPop({'geometry': null},
-      ol.format.KML.PLACEMARK_PARSERS_, node, objectStack);
-  if (!object) {
-    return undefined;
-  }
-  var feature = new ol.Feature();
-  var id = node.getAttribute('id');
-  if (id !== null) {
-    feature.setId(id);
-  }
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-
-  var geometry = object['geometry'];
-  if (geometry) {
-    ol.format.Feature.transformWithOptions(geometry, false, options);
-  }
-  feature.setGeometry(geometry);
-  delete object['geometry'];
-
-  if (this.extractStyles_) {
-    var style = object['Style'];
-    var styleUrl = object['styleUrl'];
-    var styleFunction = ol.format.KML.createFeatureStyleFunction_(
-        style, styleUrl, this.defaultStyle_, this.sharedStyles_,
-        this.showPointNames_);
-    feature.setStyle(styleFunction);
-  }
-  delete object['Style'];
-  // we do not remove the styleUrl property from the object, so it
-  // gets stored on feature when setProperties is called
-
-  feature.setProperties(object);
-
-  return feature;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style');
-  var id = node.getAttribute('id');
-  if (id !== null) {
-    var style = ol.format.KML.readStyle_(node, objectStack);
-    if (style) {
-      var styleUri;
-      if (node.baseURI) {
-        var url = new URL('#' + id, node.baseURI);
-        styleUri = url.href;
-      } else {
-        styleUri = '#' + id;
-      }
-      this.sharedStyles_[styleUri] = style;
-    }
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'StyleMap',
-      'localName should be StyleMap');
-  var id = node.getAttribute('id');
-  if (id === null) {
-    return;
-  }
-  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
-  if (!styleMapValue) {
-    return;
-  }
-  var styleUri;
-  if (node.baseURI) {
-    var url = new URL('#' + id, node.baseURI);
-    styleUri = url.href;
-  } else {
-    styleUri = '#' + id;
-  }
-  this.sharedStyles_[styleUri] = styleMapValue;
-};
-
-
-/**
- * Read the first feature from a KML source. MultiGeometries are converted into
- * GeometryCollections if they are a mix of geometry types, and into MultiPoint/
- * MultiLineString/MultiPolygon if they are all of the same type.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
- */
-ol.format.KML.prototype.readFeature;
-
-
-/**
- * @inheritDoc
- */
-ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
-    return null;
-  }
-  ol.DEBUG && console.assert(node.localName == 'Placemark',
-      'localName should be Placemark');
-  var feature = this.readPlacemark_(
-      node, [this.getReadOptions(node, opt_options)]);
-  if (feature) {
-    return feature;
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * Read all features from a KML source. MultiGeometries are converted into
- * GeometryCollections if they are a mix of geometry types, and into MultiPoint/
- * MultiLineString/MultiPolygon if they are all of the same type.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.KML.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
-    return [];
-  }
-  var features;
-  var localName = node.localName;
-  if (localName == 'Document' || localName == 'Folder') {
-    features = this.readDocumentOrFolder_(
-        node, [this.getReadOptions(node, opt_options)]);
-    if (features) {
-      return features;
-    } else {
-      return [];
-    }
-  } else if (localName == 'Placemark') {
-    var feature = this.readPlacemark_(
-        node, [this.getReadOptions(node, opt_options)]);
-    if (feature) {
-      return [feature];
-    } else {
-      return [];
-    }
-  } else if (localName == 'kml') {
-    features = [];
-    var n;
-    for (n = node.firstElementChild; n; n = n.nextElementSibling) {
-      var fs = this.readFeaturesFromNode(n, opt_options);
-      if (fs) {
-        ol.array.extend(features, fs);
-      }
-    }
-    return features;
-  } else {
-    return [];
-  }
-};
-
-
-/**
- * Read the name of the KML.
- *
- * @param {Document|Node|string} source Souce.
- * @return {string|undefined} Name.
- * @api stable
- */
-ol.format.KML.prototype.readName = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readNameFromDocument(/** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readNameFromNode(/** @type {Node} */ (source));
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readNameFromDocument(doc);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Document} doc Document.
- * @return {string|undefined} Name.
- */
-ol.format.KML.prototype.readNameFromDocument = function(doc) {
-  var n;
-  for (n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      var name = this.readNameFromNode(n);
-      if (name) {
-        return name;
-      }
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {string|undefined} Name.
- */
-ol.format.KML.prototype.readNameFromNode = function(node) {
-  var n;
-  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
-    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
-        n.localName == 'name') {
-      return ol.format.XSD.readString(n);
-    }
-  }
-  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
-    var localName = n.localName;
-    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
-        (localName == 'Document' ||
-         localName == 'Folder' ||
-         localName == 'Placemark' ||
-         localName == 'kml')) {
-      var name = this.readNameFromNode(n);
-      if (name) {
-        return name;
-      }
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * Read the network links of the KML.
- *
- * @param {Document|Node|string} source Source.
- * @return {Array.<Object>} Network links.
- * @api
- */
-ol.format.KML.prototype.readNetworkLinks = function(source) {
-  var networkLinks = [];
-  if (ol.xml.isDocument(source)) {
-    ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(
-        /** @type {Document} */ (source)));
-  } else if (ol.xml.isNode(source)) {
-    ol.array.extend(networkLinks, this.readNetworkLinksFromNode(
-        /** @type {Node} */ (source)));
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(doc));
-  }
-  return networkLinks;
-};
-
-
-/**
- * @param {Document} doc Document.
- * @return {Array.<Object>} Network links.
- */
-ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) {
-  var n, networkLinks = [];
-  for (n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
-    }
-  }
-  return networkLinks;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {Array.<Object>} Network links.
- */
-ol.format.KML.prototype.readNetworkLinksFromNode = function(node) {
-  var n, networkLinks = [];
-  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
-    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
-        n.localName == 'NetworkLink') {
-      var obj = ol.xml.pushParseAndPop({}, ol.format.KML.NETWORK_LINK_PARSERS_,
-          n, []);
-      networkLinks.push(obj);
-    }
-  }
-  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
-    var localName = n.localName;
-    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
-        (localName == 'Document' ||
-         localName == 'Folder' ||
-         localName == 'kml')) {
-      ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
-    }
-  }
-  return networkLinks;
-};
-
-
-/**
- * Read the projection from a KML source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.KML.prototype.readProjection;
-
-
-/**
- * @param {Node} node Node to append a TextNode with the color to.
- * @param {ol.Color|string} color Color.
- * @private
- */
-ol.format.KML.writeColorTextNode_ = function(node, color) {
-  var rgba = ol.color.asArray(color);
-  var opacity = (rgba.length == 4) ? rgba[3] : 1;
-  var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]];
-  var i;
-  for (i = 0; i < 4; ++i) {
-    var hex = parseInt(abgr[i], 10).toString(16);
-    abgr[i] = (hex.length == 1) ? '0' + hex : hex;
-  }
-  ol.format.XSD.writeStringTextNode(node, abgr.join(''));
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the coordinates to.
- * @param {Array.<number>} coordinates Coordinates.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeCoordinatesTextNode_ = function(node, coordinates, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-
-  var layout = context['layout'];
-  var stride = context['stride'];
-
-  var dimension;
-  if (layout == ol.geom.GeometryLayout.XY ||
-      layout == ol.geom.GeometryLayout.XYM) {
-    dimension = 2;
-  } else if (layout == ol.geom.GeometryLayout.XYZ ||
-      layout == ol.geom.GeometryLayout.XYZM) {
-    dimension = 3;
-  } else {
-    ol.asserts.assert(false, 34); // Invalid geometry layout
-  }
-
-  var d, i;
-  var ii = coordinates.length;
-  var text = '';
-  if (ii > 0) {
-    text += coordinates[0];
-    for (d = 1; d < dimension; ++d) {
-      text += ',' + coordinates[d];
-    }
-    for (i = stride; i < ii; i += stride) {
-      text += ' ' + coordinates[i];
-      for (d = 1; d < dimension; ++d) {
-        text += ',' + coordinates[i + d];
-      }
-    }
-  }
-  ol.format.XSD.writeStringTextNode(node, text);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<ol.Feature>} features Features.
- * @param {Array.<*>} objectStack Object stack.
- * @this {ol.format.KML}
- * @private
- */
-ol.format.KML.writeDocument_ = function(node, features, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_,
-      ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack, undefined,
-      this);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Object} icon Icon object.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeIcon_ = function(node, icon, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(icon, orderedKeys);
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
-      values, objectStack, orderedKeys);
-  orderedKeys =
-      ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]];
-  values = ol.xml.makeSequence(icon, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_,
-      ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.style.Icon} style Icon style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  var properties = {};
-  var src = style.getSrc();
-  var size = style.getSize();
-  var iconImageSize = style.getImageSize();
-  var iconProperties = {
-    'href': src
-  };
-
-  if (size) {
-    iconProperties['w'] = size[0];
-    iconProperties['h'] = size[1];
-    var anchor = style.getAnchor(); // top-left
-    var origin = style.getOrigin(); // top-left
-
-    if (origin && iconImageSize && origin[0] !== 0 && origin[1] !== size[1]) {
-      iconProperties['x'] = origin[0];
-      iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]);
-    }
-
-    if (anchor && anchor[0] !== 0 && anchor[1] !== size[1]) {
-      var /** @type {ol.KMLVec2_} */ hotSpot = {
-        x: anchor[0],
-        xunits: ol.style.Icon.AnchorUnits.PIXELS,
-        y: size[1] - anchor[1],
-        yunits: ol.style.Icon.AnchorUnits.PIXELS
-      };
-      properties['hotSpot'] = hotSpot;
-    }
-  }
-
-  properties['Icon'] = iconProperties;
-
-  var scale = style.getScale();
-  if (scale !== 1) {
-    properties['scale'] = scale;
-  }
-
-  var rotation = style.getRotation();
-  if (rotation !== 0) {
-    properties['heading'] = rotation; // 0-360
-  }
-
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.style.Text} style style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  var properties = {};
-  var fill = style.getFill();
-  if (fill) {
-    properties['color'] = fill.getColor();
-  }
-  var scale = style.getScale();
-  if (scale && scale !== 1) {
-    properties['scale'] = scale;
-  }
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys =
-      ol.format.KML.LABEL_STYLE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.LABEL_STYLE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.style.Stroke} style style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeLineStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  var properties = {
-    'color': style.getColor(),
-    'width': style.getWidth()
-  };
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeMultiGeometry_ = function(node, geometry, objectStack) {
-  /** @type {ol.XmlNodeStackItem} */
-  var context = {node: node};
-  var type = geometry.getType();
-  /** @type {Array.<ol.geom.Geometry>} */
-  var geometries;
-  /** @type {function(*, Array.<*>, string=): (Node|undefined)} */
-  var factory;
-  if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
-    geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
-    factory = ol.format.KML.GEOMETRY_NODE_FACTORY_;
-  } else if (type == ol.geom.GeometryType.MULTI_POINT) {
-    geometries = /** @type {ol.geom.MultiPoint} */ (geometry).getPoints();
-    factory = ol.format.KML.POINT_NODE_FACTORY_;
-  } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) {
-    geometries =
-        (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings();
-    factory = ol.format.KML.LINE_STRING_NODE_FACTORY_;
-  } else if (type == ol.geom.GeometryType.MULTI_POLYGON) {
-    geometries =
-        (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons();
-    factory = ol.format.KML.POLYGON_NODE_FACTORY_;
-  } else {
-    ol.asserts.assert(false, 39); // Unknown geometry type
-  }
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory,
-      geometries, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.LinearRing} linearRing Linear ring.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.BOUNDARY_IS_SERIALIZERS_,
-      ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack);
-};
-
-
-/**
- * FIXME currently we do serialize arbitrary/custom feature properties
- * (ExtendedData).
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Object stack.
- * @this {ol.format.KML}
- * @private
- */
-ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-
-  // set id
-  if (feature.getId()) {
-    node.setAttribute('id', feature.getId());
-  }
-
-  // serialize properties (properties unknown to KML are not serialized)
-  var properties = feature.getProperties();
-
-  var styleFunction = feature.getStyleFunction();
-  if (styleFunction) {
-    // FIXME the styles returned by the style function are supposed to be
-    // resolution-independent here
-    var styles = styleFunction.call(feature, 0);
-    if (styles) {
-      var style = Array.isArray(styles) ? styles[0] : styles;
-      if (this.writeStyles_) {
-        properties['Style'] = style;
-      }
-      var textStyle = style.getText();
-      if (textStyle) {
-        properties['name'] = textStyle.getText();
-      }
-    }
-  }
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.PLACEMARK_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
-
-  // serialize geometry
-  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    geometry =
-        ol.format.Feature.transformWithOptions(geometry, true, options);
-  }
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
-      ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) {
-  ol.DEBUG && console.assert(
-      (geometry instanceof ol.geom.Point) ||
-      (geometry instanceof ol.geom.LineString) ||
-      (geometry instanceof ol.geom.LinearRing),
-      'geometry should be one of ol.geom.Point, ol.geom.LineString ' +
-      'or ol.geom.LinearRing');
-  var flatCoordinates = geometry.getFlatCoordinates();
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  context['layout'] = geometry.getLayout();
-  context['stride'] = geometry.getStride();
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_,
-      ol.format.KML.COORDINATES_NODE_FACTORY_,
-      [flatCoordinates], objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.geom.Polygon} polygon Polygon.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writePolygon_ = function(node, polygon, objectStack) {
-  var linearRings = polygon.getLinearRings();
-  ol.DEBUG && console.assert(linearRings.length > 0,
-      'linearRings should not be empty');
-  var outerRing = linearRings.shift();
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  // inner rings
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.POLYGON_SERIALIZERS_,
-      ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_,
-      linearRings, objectStack);
-  // outer ring
-  ol.xml.pushSerializeAndPop(context,
-      ol.format.KML.POLYGON_SERIALIZERS_,
-      ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_,
-      [outerRing], objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.style.Fill} style Style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writePolyStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_,
-      ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack);
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the scale to.
- * @param {number|undefined} scale Scale.
- * @private
- */
-ol.format.KML.writeScaleTextNode_ = function(node, scale) {
-  // the Math is to remove any excess decimals created by float arithmetic
-  ol.format.XSD.writeDecimalTextNode(node,
-      Math.round(scale * scale * 1e6) / 1e6);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.style.Style} style Style.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.KML.writeStyle_ = function(node, style, objectStack) {
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
-  var properties = {};
-  var fillStyle = style.getFill();
-  var strokeStyle = style.getStroke();
-  var imageStyle = style.getImage();
-  var textStyle = style.getText();
-  if (imageStyle instanceof ol.style.Icon) {
-    properties['IconStyle'] = imageStyle;
-  }
-  if (textStyle) {
-    properties['LabelStyle'] = textStyle;
-  }
-  if (strokeStyle) {
-    properties['LineStyle'] = strokeStyle;
-  }
-  if (fillStyle) {
-    properties['PolyStyle'] = fillStyle;
-  }
-  var parentNode = objectStack[objectStack.length - 1].node;
-  var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.STYLE_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
-};
-
-
-/**
- * @param {Node} node Node to append a TextNode with the Vec2 to.
- * @param {ol.KMLVec2_} vec2 Vec2.
- * @private
- */
-ol.format.KML.writeVec2_ = function(node, vec2) {
-  node.setAttribute('x', vec2.x);
-  node.setAttribute('y', vec2.y);
-  node.setAttribute('xunits', vec2.xunits);
-  node.setAttribute('yunits', vec2.yunits);
-};
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'Document', 'Placemark'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.KML_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Document': ol.xml.makeChildAppender(ol.format.KML.writeDocument_),
-      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, string>}
- * @private
- */
-ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = {
-  'Point': 'Point',
-  'LineString': 'LineString',
-  'LinearRing': 'LinearRing',
-  'Polygon': 'Polygon',
-  'MultiPoint': 'MultiGeometry',
-  'MultiLineString': 'MultiGeometry',
-  'MultiPolygon': 'MultiGeometry',
-  'GeometryCollection': 'MultiGeometry'
-};
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'href'
-    ],
-    ol.xml.makeStructureNS(ol.format.KML.GX_NAMESPACE_URIS_, [
-      'x', 'y', 'w', 'h'
-    ]));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-    }, ol.xml.makeStructureNS(
-        ol.format.KML.GX_NAMESPACE_URIS_, {
-          'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-          'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
-        }));
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'scale', 'heading', 'Icon', 'hotSpot'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_),
-      'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
-      'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_),
-      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'color', 'scale'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_),
-      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'color', 'width'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.LINE_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_),
-      'width': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LinearRing': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'LineString': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Point': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_),
-      'GeometryCollection': ol.xml.makeChildAppender(
-          ol.format.KML.writeMultiGeometry_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'name', 'open', 'visibility', 'address', 'phoneNumber', 'description',
-      'styleUrl', 'Style'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'MultiGeometry': ol.xml.makeChildAppender(
-          ol.format.KML.writeMultiGeometry_),
-      'LineString': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'LinearRing': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Point': ol.xml.makeChildAppender(
-          ol.format.KML.writePrimitiveGeometry_),
-      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_),
-      'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_),
-      'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'description': ol.xml.makeChildAppender(
-          ol.format.XSD.writeStringTextNode),
-      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode),
-      'phoneNumber': ol.xml.makeChildAppender(
-          ol.format.XSD.writeStringTextNode),
-      'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
-      'visibility': ol.xml.makeChildAppender(
-          ol.format.XSD.writeBooleanTextNode)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'coordinates': ol.xml.makeChildAppender(
-          ol.format.KML.writeCoordinatesTextNode_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'outerBoundaryIs': ol.xml.makeChildAppender(
-          ol.format.KML.writeBoundaryIs_),
-      'innerBoundaryIs': ol.xml.makeChildAppender(
-          ol.format.KML.writeBoundaryIs_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Array.<string>>}
- * @private
- */
-ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, [
-      'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle'
-    ]);
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
-    ol.format.KML.NAMESPACE_URIS_, {
-      'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_),
-      'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_),
-      'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_),
-      'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_)
-    });
-
-
-/**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
- */
-ol.format.KML.GX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
-  return ol.xml.createElementNS(ol.format.KML.GX_NAMESPACE_URIS_[0],
-      'gx:' + opt_nodeName);
-};
-
-
-/**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
- */
-ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack,
-    opt_nodeName) {
-  var parentNode = objectStack[objectStack.length - 1].node;
-  ol.DEBUG && console.assert(ol.xml.isNode(parentNode),
-      'parentNode should be an XML node');
-  return ol.xml.createElementNS(parentNode.namespaceURI, 'Placemark');
-};
-
-
-/**
- * @const
- * @param {*} value Value.
- * @param {Array.<*>} objectStack Object stack.
- * @param {string=} opt_nodeName Node name.
- * @return {Node|undefined} Node.
- * @private
- */
-ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack,
-    opt_nodeName) {
-  if (value) {
-    var parentNode = objectStack[objectStack.length - 1].node;
-    ol.DEBUG && console.assert(ol.xml.isNode(parentNode),
-        'parentNode should be an XML node');
-    return ol.xml.createElementNS(parentNode.namespaceURI,
-        ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[/** @type {ol.geom.Geometry} */ (value).getType()]);
-  }
-};
-
-
-/**
- * A factory for creating coordinates nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color');
-
-
-/**
- * A factory for creating coordinates nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.COORDINATES_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('coordinates');
-
-
-/**
- * A factory for creating innerBoundaryIs nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('innerBoundaryIs');
-
-
-/**
- * A factory for creating Point nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.POINT_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('Point');
-
-
-/**
- * A factory for creating LineString nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.LINE_STRING_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('LineString');
-
-
-/**
- * A factory for creating LinearRing nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.LINEAR_RING_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('LinearRing');
-
-
-/**
- * A factory for creating Polygon nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.POLYGON_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('Polygon');
-
-
-/**
- * A factory for creating outerBoundaryIs nodes.
- * @const
- * @type {function(*, Array.<*>, string=): (Node|undefined)}
- * @private
- */
-ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ =
-    ol.xml.makeSimpleNodeFactory('outerBoundaryIs');
-
-
-/**
- * Encode an array of features in the KML format. GeometryCollections, MultiPoints,
- * MultiLineStrings, and MultiPolygons are output as MultiGeometries.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {string} Result.
- * @api stable
- */
-ol.format.KML.prototype.writeFeatures;
-
-
-/**
- * Encode an array of features in the KML format as an XML node. GeometryCollections,
- * MultiPoints, MultiLineStrings, and MultiPolygons are output as MultiGeometries.
- *
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Options.
- * @return {Node} Node.
- * @api
- */
-ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) {
-  opt_options = this.adaptOptions(opt_options);
-  var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml');
-  var xmlnsUri = 'http://www.w3.org/2000/xmlns/';
-  var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance';
-  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx',
-      ol.format.KML.GX_NAMESPACE_URIS_[0]);
-  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri);
-  ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation',
-      ol.format.KML.SCHEMA_LOCATION_);
-
-  var /** @type {ol.XmlNodeStackItem} */ context = {node: kml};
-  var properties = {};
-  if (features.length > 1) {
-    properties['Document'] = features;
-  } else if (features.length == 1) {
-    properties['Placemark'] = features[0];
-  }
-  var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI];
-  var values = ol.xml.makeSequence(properties, orderedKeys);
-  ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_,
-      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys,
-      this);
-  return kml;
-};
-
-goog.provide('ol.ext.pbf');
-/** @typedef {function(*)} */
-ol.ext.pbf;
-(function() {
-var exports = {};
-var module = {exports: exports};
-var define;
-/**
- * @fileoverview
- * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
- */
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pbf = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)ret [...]
-exports.read = function (buffer, offset, isLE, mLen, nBytes) {
-  var e, m
-  var eLen = nBytes * 8 - mLen - 1
-  var eMax = (1 << eLen) - 1
-  var eBias = eMax >> 1
-  var nBits = -7
-  var i = isLE ? (nBytes - 1) : 0
-  var d = isLE ? -1 : 1
-  var s = buffer[offset + i]
-
-  i += d
-
-  e = s & ((1 << (-nBits)) - 1)
-  s >>= (-nBits)
-  nBits += eLen
-  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
-
-  m = e & ((1 << (-nBits)) - 1)
-  e >>= (-nBits)
-  nBits += mLen
-  for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
-
-  if (e === 0) {
-    e = 1 - eBias
-  } else if (e === eMax) {
-    return m ? NaN : ((s ? -1 : 1) * Infinity)
-  } else {
-    m = m + Math.pow(2, mLen)
-    e = e - eBias
-  }
-  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
-}
-
-exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
-  var e, m, c
-  var eLen = nBytes * 8 - mLen - 1
-  var eMax = (1 << eLen) - 1
-  var eBias = eMax >> 1
-  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
-  var i = isLE ? 0 : (nBytes - 1)
-  var d = isLE ? 1 : -1
-  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
-
-  value = Math.abs(value)
-
-  if (isNaN(value) || value === Infinity) {
-    m = isNaN(value) ? 1 : 0
-    e = eMax
-  } else {
-    e = Math.floor(Math.log(value) / Math.LN2)
-    if (value * (c = Math.pow(2, -e)) < 1) {
-      e--
-      c *= 2
-    }
-    if (e + eBias >= 1) {
-      value += rt / c
-    } else {
-      value += rt * Math.pow(2, 1 - eBias)
-    }
-    if (value * c >= 2) {
-      e++
-      c /= 2
-    }
-
-    if (e + eBias >= eMax) {
-      m = 0
-      e = eMax
-    } else if (e + eBias >= 1) {
-      m = (value * c - 1) * Math.pow(2, mLen)
-      e = e + eBias
-    } else {
-      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
-      e = 0
-    }
-  }
-
-  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
-
-  e = (e << mLen) | m
-  eLen += mLen
-  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
-
-  buffer[offset + i - d] |= s * 128
-}
-
-},{}],2:[function(_dereq_,module,exports){
-'use strict';
-
-module.exports = Pbf;
-
-var ieee754 = _dereq_('ieee754');
-
-function Pbf(buf) {
-    this.buf = ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
-    this.pos = 0;
-    this.type = 0;
-    this.length = this.buf.length;
-}
-
-Pbf.Varint  = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
-Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
-Pbf.Bytes   = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
-Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
-
-var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
-    SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
-
-Pbf.prototype = {
-
-    destroy: function() {
-        this.buf = null;
-    },
-
-    // === READING =================================================================
-
-    readFields: function(readField, result, end) {
-        end = end || this.length;
-
-        while (this.pos < end) {
-            var val = this.readVarint(),
-                tag = val >> 3,
-                startPos = this.pos;
-
-            this.type = val & 0x7;
-            readField(tag, result, this);
-
-            if (this.pos === startPos) this.skip(val);
-        }
-        return result;
-    },
-
-    readMessage: function(readField, result) {
-        return this.readFields(readField, result, this.readVarint() + this.pos);
-    },
-
-    readFixed32: function() {
-        var val = readUInt32(this.buf, this.pos);
-        this.pos += 4;
-        return val;
-    },
-
-    readSFixed32: function() {
-        var val = readInt32(this.buf, this.pos);
-        this.pos += 4;
-        return val;
-    },
-
-    // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
-
-    readFixed64: function() {
-        var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
-        this.pos += 8;
-        return val;
-    },
-
-    readSFixed64: function() {
-        var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
-        this.pos += 8;
-        return val;
-    },
-
-    readFloat: function() {
-        var val = ieee754.read(this.buf, this.pos, true, 23, 4);
-        this.pos += 4;
-        return val;
-    },
-
-    readDouble: function() {
-        var val = ieee754.read(this.buf, this.pos, true, 52, 8);
-        this.pos += 8;
-        return val;
-    },
-
-    readVarint: function(isSigned) {
-        var buf = this.buf,
-            val, b;
-
-        b = buf[this.pos++]; val  =  b & 0x7f;        if (b < 0x80) return val;
-        b = buf[this.pos++]; val |= (b & 0x7f) << 7;  if (b < 0x80) return val;
-        b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val;
-        b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val;
-        b = buf[this.pos];   val |= (b & 0x0f) << 28;
-
-        return readVarintRemainder(val, isSigned, this);
-    },
-
-    readVarint64: function() { // for compatibility with v2.0.1
-        return this.readVarint(true);
-    },
-
-    readSVarint: function() {
-        var num = this.readVarint();
-        return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
-    },
-
-    readBoolean: function() {
-        return Boolean(this.readVarint());
-    },
-
-    readString: function() {
-        var end = this.readVarint() + this.pos,
-            str = readUtf8(this.buf, this.pos, end);
-        this.pos = end;
-        return str;
-    },
-
-    readBytes: function() {
-        var end = this.readVarint() + this.pos,
-            buffer = this.buf.subarray(this.pos, end);
-        this.pos = end;
-        return buffer;
-    },
-
-    // verbose for performance reasons; doesn't affect gzipped size
-
-    readPackedVarint: function(arr, isSigned) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readVarint(isSigned));
-        return arr;
-    },
-    readPackedSVarint: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readSVarint());
-        return arr;
-    },
-    readPackedBoolean: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readBoolean());
-        return arr;
-    },
-    readPackedFloat: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readFloat());
-        return arr;
-    },
-    readPackedDouble: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readDouble());
-        return arr;
-    },
-    readPackedFixed32: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readFixed32());
-        return arr;
-    },
-    readPackedSFixed32: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readSFixed32());
-        return arr;
-    },
-    readPackedFixed64: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readFixed64());
-        return arr;
-    },
-    readPackedSFixed64: function(arr) {
-        var end = readPackedEnd(this);
-        arr = arr || [];
-        while (this.pos < end) arr.push(this.readSFixed64());
-        return arr;
-    },
-
-    skip: function(val) {
-        var type = val & 0x7;
-        if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {}
-        else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;
-        else if (type === Pbf.Fixed32) this.pos += 4;
-        else if (type === Pbf.Fixed64) this.pos += 8;
-        else throw new Error('Unimplemented type: ' + type);
-    },
-
-    // === WRITING =================================================================
-
-    writeTag: function(tag, type) {
-        this.writeVarint((tag << 3) | type);
-    },
-
-    realloc: function(min) {
-        var length = this.length || 16;
-
-        while (length < this.pos + min) length *= 2;
-
-        if (length !== this.length) {
-            var buf = new Uint8Array(length);
-            buf.set(this.buf);
-            this.buf = buf;
-            this.length = length;
-        }
-    },
-
-    finish: function() {
-        this.length = this.pos;
-        this.pos = 0;
-        return this.buf.subarray(0, this.length);
-    },
-
-    writeFixed32: function(val) {
-        this.realloc(4);
-        writeInt32(this.buf, val, this.pos);
-        this.pos += 4;
-    },
-
-    writeSFixed32: function(val) {
-        this.realloc(4);
-        writeInt32(this.buf, val, this.pos);
-        this.pos += 4;
-    },
-
-    writeFixed64: function(val) {
-        this.realloc(8);
-        writeInt32(this.buf, val & -1, this.pos);
-        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
-        this.pos += 8;
-    },
-
-    writeSFixed64: function(val) {
-        this.realloc(8);
-        writeInt32(this.buf, val & -1, this.pos);
-        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
-        this.pos += 8;
-    },
-
-    writeVarint: function(val) {
-        val = +val || 0;
-
-        if (val > 0xfffffff || val < 0) {
-            writeBigVarint(val, this);
-            return;
-        }
-
-        this.realloc(4);
-
-        this.buf[this.pos++] =           val & 0x7f  | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
-        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
-        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
-        this.buf[this.pos++] =   (val >>> 7) & 0x7f;
-    },
-
-    writeSVarint: function(val) {
-        this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
-    },
-
-    writeBoolean: function(val) {
-        this.writeVarint(Boolean(val));
-    },
-
-    writeString: function(str) {
-        str = String(str);
-        this.realloc(str.length * 4);
-
-        this.pos++; // reserve 1 byte for short string length
-
-        var startPos = this.pos;
-        // write the string directly to the buffer and see how much was written
-        this.pos = writeUtf8(this.buf, str, this.pos);
-        var len = this.pos - startPos;
-
-        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
-
-        // finally, write the message length in the reserved place and restore the position
-        this.pos = startPos - 1;
-        this.writeVarint(len);
-        this.pos += len;
-    },
-
-    writeFloat: function(val) {
-        this.realloc(4);
-        ieee754.write(this.buf, val, this.pos, true, 23, 4);
-        this.pos += 4;
-    },
-
-    writeDouble: function(val) {
-        this.realloc(8);
-        ieee754.write(this.buf, val, this.pos, true, 52, 8);
-        this.pos += 8;
-    },
-
-    writeBytes: function(buffer) {
-        var len = buffer.length;
-        this.writeVarint(len);
-        this.realloc(len);
-        for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
-    },
-
-    writeRawMessage: function(fn, obj) {
-        this.pos++; // reserve 1 byte for short message length
-
-        // write the message directly to the buffer and see how much was written
-        var startPos = this.pos;
-        fn(obj, this);
-        var len = this.pos - startPos;
-
-        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
-
-        // finally, write the message length in the reserved place and restore the position
-        this.pos = startPos - 1;
-        this.writeVarint(len);
-        this.pos += len;
-    },
-
-    writeMessage: function(tag, fn, obj) {
-        this.writeTag(tag, Pbf.Bytes);
-        this.writeRawMessage(fn, obj);
-    },
-
-    writePackedVarint:   function(tag, arr) { this.writeMessage(tag, writePackedVarint, arr);   },
-    writePackedSVarint:  function(tag, arr) { this.writeMessage(tag, writePackedSVarint, arr);  },
-    writePackedBoolean:  function(tag, arr) { this.writeMessage(tag, writePackedBoolean, arr);  },
-    writePackedFloat:    function(tag, arr) { this.writeMessage(tag, writePackedFloat, arr);    },
-    writePackedDouble:   function(tag, arr) { this.writeMessage(tag, writePackedDouble, arr);   },
-    writePackedFixed32:  function(tag, arr) { this.writeMessage(tag, writePackedFixed32, arr);  },
-    writePackedSFixed32: function(tag, arr) { this.writeMessage(tag, writePackedSFixed32, arr); },
-    writePackedFixed64:  function(tag, arr) { this.writeMessage(tag, writePackedFixed64, arr);  },
-    writePackedSFixed64: function(tag, arr) { this.writeMessage(tag, writePackedSFixed64, arr); },
-
-    writeBytesField: function(tag, buffer) {
-        this.writeTag(tag, Pbf.Bytes);
-        this.writeBytes(buffer);
-    },
-    writeFixed32Field: function(tag, val) {
-        this.writeTag(tag, Pbf.Fixed32);
-        this.writeFixed32(val);
-    },
-    writeSFixed32Field: function(tag, val) {
-        this.writeTag(tag, Pbf.Fixed32);
-        this.writeSFixed32(val);
-    },
-    writeFixed64Field: function(tag, val) {
-        this.writeTag(tag, Pbf.Fixed64);
-        this.writeFixed64(val);
-    },
-    writeSFixed64Field: function(tag, val) {
-        this.writeTag(tag, Pbf.Fixed64);
-        this.writeSFixed64(val);
-    },
-    writeVarintField: function(tag, val) {
-        this.writeTag(tag, Pbf.Varint);
-        this.writeVarint(val);
-    },
-    writeSVarintField: function(tag, val) {
-        this.writeTag(tag, Pbf.Varint);
-        this.writeSVarint(val);
-    },
-    writeStringField: function(tag, str) {
-        this.writeTag(tag, Pbf.Bytes);
-        this.writeString(str);
-    },
-    writeFloatField: function(tag, val) {
-        this.writeTag(tag, Pbf.Fixed32);
-        this.writeFloat(val);
-    },
-    writeDoubleField: function(tag, val) {
-        this.writeTag(tag, Pbf.Fixed64);
-        this.writeDouble(val);
-    },
-    writeBooleanField: function(tag, val) {
-        this.writeVarintField(tag, Boolean(val));
-    }
-};
-
-function readVarintRemainder(l, s, p) {
-    var buf = p.buf,
-        h, b;
-
-    b = buf[p.pos++]; h  = (b & 0x70) >> 4;  if (b < 0x80) return toNum(l, h, s);
-    b = buf[p.pos++]; h |= (b & 0x7f) << 3;  if (b < 0x80) return toNum(l, h, s);
-    b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s);
-    b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s);
-    b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s);
-    b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s);
-
-    throw new Error('Expected varint not more than 10 bytes');
-}
-
-function readPackedEnd(pbf) {
-    return pbf.type === Pbf.Bytes ?
-        pbf.readVarint() + pbf.pos : pbf.pos + 1;
-}
-
-function toNum(low, high, isSigned) {
-    if (isSigned) {
-        return high * 0x100000000 + (low >>> 0);
-    }
-
-    return ((high >>> 0) * 0x100000000) + (low >>> 0);
-}
-
-function writeBigVarint(val, pbf) {
-    var low, high;
-
-    if (val >= 0) {
-        low  = (val % 0x100000000) | 0;
-        high = (val / 0x100000000) | 0;
-    } else {
-        low  = ~(-val % 0x100000000);
-        high = ~(-val / 0x100000000);
-
-        if (low ^ 0xffffffff) {
-            low = (low + 1) | 0;
-        } else {
-            low = 0;
-            high = (high + 1) | 0;
-        }
-    }
-
-    if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
-        throw new Error('Given varint doesn\'t fit into 10 bytes');
-    }
-
-    pbf.realloc(10);
-
-    writeBigVarintLow(low, high, pbf);
-    writeBigVarintHigh(high, pbf);
-}
-
-function writeBigVarintLow(low, high, pbf) {
-    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
-    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
-    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
-    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
-    pbf.buf[pbf.pos]   = low & 0x7f;
-}
-
-function writeBigVarintHigh(high, pbf) {
-    var lsb = (high & 0x07) << 4;
-
-    pbf.buf[pbf.pos++] |= lsb         | ((high >>>= 3) ? 0x80 : 0); if (!high) return;
-    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
-    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
-    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
-    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
-    pbf.buf[pbf.pos++]  = high & 0x7f;
-}
-
-function makeRoomForExtraLength(startPos, len, pbf) {
-    var extraLen =
-        len <= 0x3fff ? 1 :
-        len <= 0x1fffff ? 2 :
-        len <= 0xfffffff ? 3 : Math.ceil(Math.log(len) / (Math.LN2 * 7));
-
-    // if 1 byte isn't enough for encoding message length, shift the data to the right
-    pbf.realloc(extraLen);
-    for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
-}
-
-function writePackedVarint(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]);   }
-function writePackedSVarint(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]);  }
-function writePackedFloat(arr, pbf)    { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]);    }
-function writePackedDouble(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]);   }
-function writePackedBoolean(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]);  }
-function writePackedFixed32(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]);  }
-function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); }
-function writePackedFixed64(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]);  }
-function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); }
-
-// Buffer code below from https://github.com/feross/buffer, MIT-licensed
-
-function readUInt32(buf, pos) {
-    return ((buf[pos]) |
-        (buf[pos + 1] << 8) |
-        (buf[pos + 2] << 16)) +
-        (buf[pos + 3] * 0x1000000);
-}
-
-function writeInt32(buf, val, pos) {
-    buf[pos] = val;
-    buf[pos + 1] = (val >>> 8);
-    buf[pos + 2] = (val >>> 16);
-    buf[pos + 3] = (val >>> 24);
-}
-
-function readInt32(buf, pos) {
-    return ((buf[pos]) |
-        (buf[pos + 1] << 8) |
-        (buf[pos + 2] << 16)) +
-        (buf[pos + 3] << 24);
-}
-
-function readUtf8(buf, pos, end) {
-    var str = '';
-    var i = pos;
-
-    while (i < end) {
-        var b0 = buf[i];
-        var c = null; // codepoint
-        var bytesPerSequence =
-            b0 > 0xEF ? 4 :
-            b0 > 0xDF ? 3 :
-            b0 > 0xBF ? 2 : 1;
-
-        if (i + bytesPerSequence > end) break;
-
-        var b1, b2, b3;
-
-        if (bytesPerSequence === 1) {
-            if (b0 < 0x80) {
-                c = b0;
-            }
-        } else if (bytesPerSequence === 2) {
-            b1 = buf[i + 1];
-            if ((b1 & 0xC0) === 0x80) {
-                c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F);
-                if (c <= 0x7F) {
-                    c = null;
-                }
-            }
-        } else if (bytesPerSequence === 3) {
-            b1 = buf[i + 1];
-            b2 = buf[i + 2];
-            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
-                c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F);
-                if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) {
-                    c = null;
-                }
-            }
-        } else if (bytesPerSequence === 4) {
-            b1 = buf[i + 1];
-            b2 = buf[i + 2];
-            b3 = buf[i + 3];
-            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
-                c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F);
-                if (c <= 0xFFFF || c >= 0x110000) {
-                    c = null;
-                }
-            }
-        }
-
-        if (c === null) {
-            c = 0xFFFD;
-            bytesPerSequence = 1;
-
-        } else if (c > 0xFFFF) {
-            c -= 0x10000;
-            str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
-            c = 0xDC00 | c & 0x3FF;
-        }
-
-        str += String.fromCharCode(c);
-        i += bytesPerSequence;
-    }
-
-    return str;
-}
-
-function writeUtf8(buf, str, pos) {
-    for (var i = 0, c, lead; i < str.length; i++) {
-        c = str.charCodeAt(i); // code point
-
-        if (c > 0xD7FF && c < 0xE000) {
-            if (lead) {
-                if (c < 0xDC00) {
-                    buf[pos++] = 0xEF;
-                    buf[pos++] = 0xBF;
-                    buf[pos++] = 0xBD;
-                    lead = c;
-                    continue;
-                } else {
-                    c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
-                    lead = null;
-                }
-            } else {
-                if (c > 0xDBFF || (i + 1 === str.length)) {
-                    buf[pos++] = 0xEF;
-                    buf[pos++] = 0xBF;
-                    buf[pos++] = 0xBD;
-                } else {
-                    lead = c;
-                }
-                continue;
-            }
-        } else if (lead) {
-            buf[pos++] = 0xEF;
-            buf[pos++] = 0xBF;
-            buf[pos++] = 0xBD;
-            lead = null;
-        }
-
-        if (c < 0x80) {
-            buf[pos++] = c;
-        } else {
-            if (c < 0x800) {
-                buf[pos++] = c >> 0x6 | 0xC0;
-            } else {
-                if (c < 0x10000) {
-                    buf[pos++] = c >> 0xC | 0xE0;
-                } else {
-                    buf[pos++] = c >> 0x12 | 0xF0;
-                    buf[pos++] = c >> 0xC & 0x3F | 0x80;
-                }
-                buf[pos++] = c >> 0x6 & 0x3F | 0x80;
-            }
-            buf[pos++] = c & 0x3F | 0x80;
-        }
-    }
-    return pos;
-}
-
-},{"ieee754":1}]},{},[2])(2)
-});
-ol.ext.pbf = module.exports;
-})();
-
-goog.provide('ol.ext.vectortile');
-/** @typedef {function(*)} */
-ol.ext.vectortile;
-(function() {
-var exports = {};
-var module = {exports: exports};
-var define;
-/**
- * @fileoverview
- * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
- */
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vectortile = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);i [...]
-'use strict';
-
-module.exports = Point;
-
-function Point(x, y) {
-    this.x = x;
-    this.y = y;
-}
-
-Point.prototype = {
-    clone: function() { return new Point(this.x, this.y); },
-
-    add:     function(p) { return this.clone()._add(p);     },
-    sub:     function(p) { return this.clone()._sub(p);     },
-    mult:    function(k) { return this.clone()._mult(k);    },
-    div:     function(k) { return this.clone()._div(k);     },
-    rotate:  function(a) { return this.clone()._rotate(a);  },
-    matMult: function(m) { return this.clone()._matMult(m); },
-    unit:    function() { return this.clone()._unit(); },
-    perp:    function() { return this.clone()._perp(); },
-    round:   function() { return this.clone()._round(); },
-
-    mag: function() {
-        return Math.sqrt(this.x * this.x + this.y * this.y);
-    },
-
-    equals: function(p) {
-        return this.x === p.x &&
-               this.y === p.y;
-    },
-
-    dist: function(p) {
-        return Math.sqrt(this.distSqr(p));
-    },
-
-    distSqr: function(p) {
-        var dx = p.x - this.x,
-            dy = p.y - this.y;
-        return dx * dx + dy * dy;
-    },
-
-    angle: function() {
-        return Math.atan2(this.y, this.x);
-    },
-
-    angleTo: function(b) {
-        return Math.atan2(this.y - b.y, this.x - b.x);
-    },
-
-    angleWith: function(b) {
-        return this.angleWithSep(b.x, b.y);
-    },
-
-    // Find the angle of the two vectors, solving the formula for the cross product a x b = |a||b|sin(θ) for θ.
-    angleWithSep: function(x, y) {
-        return Math.atan2(
-            this.x * y - this.y * x,
-            this.x * x + this.y * y);
-    },
-
-    _matMult: function(m) {
-        var x = m[0] * this.x + m[1] * this.y,
-            y = m[2] * this.x + m[3] * this.y;
-        this.x = x;
-        this.y = y;
-        return this;
-    },
-
-    _add: function(p) {
-        this.x += p.x;
-        this.y += p.y;
-        return this;
-    },
-
-    _sub: function(p) {
-        this.x -= p.x;
-        this.y -= p.y;
-        return this;
-    },
-
-    _mult: function(k) {
-        this.x *= k;
-        this.y *= k;
-        return this;
-    },
-
-    _div: function(k) {
-        this.x /= k;
-        this.y /= k;
-        return this;
-    },
-
-    _unit: function() {
-        this._div(this.mag());
-        return this;
-    },
-
-    _perp: function() {
-        var y = this.y;
-        this.y = this.x;
-        this.x = -y;
-        return this;
-    },
-
-    _rotate: function(angle) {
-        var cos = Math.cos(angle),
-            sin = Math.sin(angle),
-            x = cos * this.x - sin * this.y,
-            y = sin * this.x + cos * this.y;
-        this.x = x;
-        this.y = y;
-        return this;
-    },
-
-    _round: function() {
-        this.x = Math.round(this.x);
-        this.y = Math.round(this.y);
-        return this;
-    }
-};
-
-// constructs Point from an array if necessary
-Point.convert = function (a) {
-    if (a instanceof Point) {
-        return a;
-    }
-    if (Array.isArray(a)) {
-        return new Point(a[0], a[1]);
-    }
-    return a;
-};
-
-},{}],2:[function(_dereq_,module,exports){
-module.exports.VectorTile = _dereq_('./lib/vectortile.js');
-module.exports.VectorTileFeature = _dereq_('./lib/vectortilefeature.js');
-module.exports.VectorTileLayer = _dereq_('./lib/vectortilelayer.js');
-
-},{"./lib/vectortile.js":3,"./lib/vectortilefeature.js":4,"./lib/vectortilelayer.js":5}],3:[function(_dereq_,module,exports){
-'use strict';
-
-var VectorTileLayer = _dereq_('./vectortilelayer');
-
-module.exports = VectorTile;
-
-function VectorTile(pbf, end) {
-    this.layers = pbf.readFields(readTile, {}, end);
-}
-
-function readTile(tag, layers, pbf) {
-    if (tag === 3) {
-        var layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos);
-        if (layer.length) layers[layer.name] = layer;
-    }
-}
-
-
-},{"./vectortilelayer":5}],4:[function(_dereq_,module,exports){
-'use strict';
-
-var Point = _dereq_('point-geometry');
-
-module.exports = VectorTileFeature;
-
-function VectorTileFeature(pbf, end, extent, keys, values) {
-    // Public
-    this.properties = {};
-    this.extent = extent;
-    this.type = 0;
-
-    // Private
-    this._pbf = pbf;
-    this._geometry = -1;
-    this._keys = keys;
-    this._values = values;
-
-    pbf.readFields(readFeature, this, end);
-}
-
-function readFeature(tag, feature, pbf) {
-    if (tag == 1) feature.id = pbf.readVarint();
-    else if (tag == 2) readTag(pbf, feature);
-    else if (tag == 3) feature.type = pbf.readVarint();
-    else if (tag == 4) feature._geometry = pbf.pos;
-}
-
-function readTag(pbf, feature) {
-    var end = pbf.readVarint() + pbf.pos;
-
-    while (pbf.pos < end) {
-        var key = feature._keys[pbf.readVarint()],
-            value = feature._values[pbf.readVarint()];
-        feature.properties[key] = value;
-    }
-}
-
-VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
-
-VectorTileFeature.prototype.loadGeometry = function() {
-    var pbf = this._pbf;
-    pbf.pos = this._geometry;
-
-    var end = pbf.readVarint() + pbf.pos,
-        cmd = 1,
-        length = 0,
-        x = 0,
-        y = 0,
-        lines = [],
-        line;
-
-    while (pbf.pos < end) {
-        if (!length) {
-            var cmdLen = pbf.readVarint();
-            cmd = cmdLen & 0x7;
-            length = cmdLen >> 3;
-        }
-
-        length--;
-
-        if (cmd === 1 || cmd === 2) {
-            x += pbf.readSVarint();
-            y += pbf.readSVarint();
-
-            if (cmd === 1) { // moveTo
-                if (line) lines.push(line);
-                line = [];
-            }
-
-            line.push(new Point(x, y));
-
-        } else if (cmd === 7) {
-
-            // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90
-            if (line) {
-                line.push(line[0].clone()); // closePolygon
-            }
-
-        } else {
-            throw new Error('unknown command ' + cmd);
-        }
-    }
-
-    if (line) lines.push(line);
-
-    return lines;
-};
-
-VectorTileFeature.prototype.bbox = function() {
-    var pbf = this._pbf;
-    pbf.pos = this._geometry;
-
-    var end = pbf.readVarint() + pbf.pos,
-        cmd = 1,
-        length = 0,
-        x = 0,
-        y = 0,
-        x1 = Infinity,
-        x2 = -Infinity,
-        y1 = Infinity,
-        y2 = -Infinity;
-
-    while (pbf.pos < end) {
-        if (!length) {
-            var cmdLen = pbf.readVarint();
-            cmd = cmdLen & 0x7;
-            length = cmdLen >> 3;
-        }
-
-        length--;
-
-        if (cmd === 1 || cmd === 2) {
-            x += pbf.readSVarint();
-            y += pbf.readSVarint();
-            if (x < x1) x1 = x;
-            if (x > x2) x2 = x;
-            if (y < y1) y1 = y;
-            if (y > y2) y2 = y;
-
-        } else if (cmd !== 7) {
-            throw new Error('unknown command ' + cmd);
-        }
-    }
-
-    return [x1, y1, x2, y2];
-};
-
-VectorTileFeature.prototype.toGeoJSON = function(x, y, z) {
-    var size = this.extent * Math.pow(2, z),
-        x0 = this.extent * x,
-        y0 = this.extent * y,
-        coords = this.loadGeometry(),
-        type = VectorTileFeature.types[this.type],
-        i, j;
-
-    function project(line) {
-        for (var j = 0; j < line.length; j++) {
-            var p = line[j], y2 = 180 - (p.y + y0) * 360 / size;
-            line[j] = [
-                (p.x + x0) * 360 / size - 180,
-                360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90
-            ];
-        }
-    }
-
-    switch (this.type) {
-    case 1:
-        var points = [];
-        for (i = 0; i < coords.length; i++) {
-            points[i] = coords[i][0];
-        }
-        coords = points;
-        project(coords);
-        break;
-
-    case 2:
-        for (i = 0; i < coords.length; i++) {
-            project(coords[i]);
-        }
-        break;
-
-    case 3:
-        coords = classifyRings(coords);
-        for (i = 0; i < coords.length; i++) {
-            for (j = 0; j < coords[i].length; j++) {
-                project(coords[i][j]);
-            }
-        }
-        break;
-    }
-
-    if (coords.length === 1) {
-        coords = coords[0];
-    } else {
-        type = 'Multi' + type;
-    }
-
-    var result = {
-        type: "Feature",
-        geometry: {
-            type: type,
-            coordinates: coords
-        },
-        properties: this.properties
-    };
-
-    if ('id' in this) {
-        result.id = this.id;
-    }
-
-    return result;
-};
-
-// classifies an array of rings into polygons with outer rings and holes
-
-function classifyRings(rings) {
-    var len = rings.length;
-
-    if (len <= 1) return [rings];
-
-    var polygons = [],
-        polygon,
-        ccw;
-
-    for (var i = 0; i < len; i++) {
-        var area = signedArea(rings[i]);
-        if (area === 0) continue;
-
-        if (ccw === undefined) ccw = area < 0;
-
-        if (ccw === area < 0) {
-            if (polygon) polygons.push(polygon);
-            polygon = [rings[i]];
-
-        } else {
-            polygon.push(rings[i]);
-        }
-    }
-    if (polygon) polygons.push(polygon);
-
-    return polygons;
-}
-
-function signedArea(ring) {
-    var sum = 0;
-    for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
-        p1 = ring[i];
-        p2 = ring[j];
-        sum += (p2.x - p1.x) * (p1.y + p2.y);
-    }
-    return sum;
-}
-
-},{"point-geometry":1}],5:[function(_dereq_,module,exports){
-'use strict';
-
-var VectorTileFeature = _dereq_('./vectortilefeature.js');
-
-module.exports = VectorTileLayer;
-
-function VectorTileLayer(pbf, end) {
-    // Public
-    this.version = 1;
-    this.name = null;
-    this.extent = 4096;
-    this.length = 0;
-
-    // Private
-    this._pbf = pbf;
-    this._keys = [];
-    this._values = [];
-    this._features = [];
-
-    pbf.readFields(readLayer, this, end);
-
-    this.length = this._features.length;
-}
-
-function readLayer(tag, layer, pbf) {
-    if (tag === 15) layer.version = pbf.readVarint();
-    else if (tag === 1) layer.name = pbf.readString();
-    else if (tag === 5) layer.extent = pbf.readVarint();
-    else if (tag === 2) layer._features.push(pbf.pos);
-    else if (tag === 3) layer._keys.push(pbf.readString());
-    else if (tag === 4) layer._values.push(readValueMessage(pbf));
-}
-
-function readValueMessage(pbf) {
-    var value = null,
-        end = pbf.readVarint() + pbf.pos;
-
-    while (pbf.pos < end) {
-        var tag = pbf.readVarint() >> 3;
-
-        value = tag === 1 ? pbf.readString() :
-            tag === 2 ? pbf.readFloat() :
-            tag === 3 ? pbf.readDouble() :
-            tag === 4 ? pbf.readVarint64() :
-            tag === 5 ? pbf.readVarint() :
-            tag === 6 ? pbf.readSVarint() :
-            tag === 7 ? pbf.readBoolean() : null;
-    }
-
-    return value;
-}
-
-// return feature `i` from this layer as a `VectorTileFeature`
-VectorTileLayer.prototype.feature = function(i) {
-    if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds');
-
-    this._pbf.pos = this._features[i];
-
-    var end = this._pbf.readVarint() + this._pbf.pos;
-    return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values);
-};
-
-},{"./vectortilefeature.js":4}]},{},[2])(2)
-});
-ol.ext.vectortile = module.exports;
-})();
-
-goog.provide('ol.render.Feature');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryType');
-
-
-/**
- * Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like
- * structure, optimized for rendering and styling. Geometry access through the
- * API is limited to getting the type and extent of the geometry.
- *
- * @constructor
- * @param {ol.geom.GeometryType} type Geometry type.
- * @param {Array.<number>} flatCoordinates Flat coordinates. These always need
- *     to be right-handed for polygons.
- * @param {Array.<number>|Array.<Array.<number>>} ends Ends or Endss.
- * @param {Object.<string, *>} properties Properties.
- */
-ol.render.Feature = function(type, flatCoordinates, ends, properties) {
-
-  /**
-   * @private
-   * @type {ol.Extent|undefined}
-   */
-  this.extent_;
-
-  ol.DEBUG && console.assert(type === ol.geom.GeometryType.POINT ||
-      type === ol.geom.GeometryType.MULTI_POINT ||
-      type === ol.geom.GeometryType.LINE_STRING ||
-      type === ol.geom.GeometryType.MULTI_LINE_STRING ||
-      type === ol.geom.GeometryType.POLYGON,
-      'Need a Point, MultiPoint, LineString, MultiLineString or Polygon type');
-
-  /**
-   * @private
-   * @type {ol.geom.GeometryType}
-   */
-  this.type_ = type;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.flatCoordinates_ = flatCoordinates;
-
-  /**
-   * @private
-   * @type {Array.<number>|Array.<Array.<number>>}
-   */
-  this.ends_ = ends;
-
-  /**
-   * @private
-   * @type {Object.<string, *>}
-   */
-  this.properties_ = properties;
-
-};
-
-
-/**
- * Get a feature property by its key.
- * @param {string} key Key
- * @return {*} Value for the requested key.
- * @api
- */
-ol.render.Feature.prototype.get = function(key) {
-  return this.properties_[key];
-};
-
-
-/**
- * @return {Array.<number>|Array.<Array.<number>>} Ends or endss.
- */
-ol.render.Feature.prototype.getEnds = function() {
-  return this.ends_;
-};
-
-
-/**
- * Get the extent of this feature's geometry.
- * @return {ol.Extent} Extent.
- * @api
- */
-ol.render.Feature.prototype.getExtent = function() {
-  if (!this.extent_) {
-    this.extent_ = this.type_ === ol.geom.GeometryType.POINT ?
-        ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) :
-        ol.extent.createOrUpdateFromFlatCoordinates(
-            this.flatCoordinates_, 0, this.flatCoordinates_.length, 2);
-
-  }
-  return this.extent_;
-};
-
-
-/**
- * @return {Array.<number>} Flat coordinates.
- */
-ol.render.Feature.prototype.getOrientedFlatCoordinates = function() {
-  return this.flatCoordinates_;
-};
-
-
-/**
- * @return {Array.<number>} Flat coordinates.
- */
-ol.render.Feature.prototype.getFlatCoordinates =
-    ol.render.Feature.prototype.getOrientedFlatCoordinates;
-
-
-/**
- * Get the feature for working with its geometry.
- * @return {ol.render.Feature} Feature.
- * @api
- */
-ol.render.Feature.prototype.getGeometry = function() {
-  return this;
-};
-
-
-/**
- * Get the feature properties.
- * @return {Object.<string, *>} Feature properties.
- * @api
- */
-ol.render.Feature.prototype.getProperties = function() {
-  return this.properties_;
-};
-
-
-/**
- * Get the feature for working with its geometry.
- * @return {ol.render.Feature} Feature.
- */
-ol.render.Feature.prototype.getSimplifiedGeometry =
-    ol.render.Feature.prototype.getGeometry;
-
-
-/**
- * @return {number} Stride.
- */
-ol.render.Feature.prototype.getStride = function() {
-  return 2;
-};
-
-
-/**
- * @return {undefined}
- */
-ol.render.Feature.prototype.getStyleFunction = ol.nullFunction;
-
-
-/**
- * Get the type of this feature's geometry.
- * @return {ol.geom.GeometryType} Geometry type.
- * @api
- */
-ol.render.Feature.prototype.getType = function() {
-  return this.type_;
-};
-
-//FIXME Implement projection handling
-
-goog.provide('ol.format.MVT');
-
-goog.require('ol');
-goog.require('ol.ext.pbf');
-goog.require('ol.ext.vectortile');
-goog.require('ol.format.Feature');
-goog.require('ol.format.FormatType');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.proj.Projection');
-goog.require('ol.proj.Units');
-goog.require('ol.render.Feature');
-
-
-/**
- * @classdesc
- * Feature format for reading data in the Mapbox MVT format.
- *
- * @constructor
- * @extends {ol.format.Feature}
- * @param {olx.format.MVTOptions=} opt_options Options.
- * @api
- */
-ol.format.MVT = function(opt_options) {
-
-  ol.format.Feature.call(this);
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @type {ol.proj.Projection}
-   */
-  this.defaultDataProjection = new ol.proj.Projection({
-    code: '',
-    units: ol.proj.Units.TILE_PIXELS
-  });
-
-  /**
-   * @private
-   * @type {function((ol.geom.Geometry|Object.<string, *>)=)|
-   *     function(ol.geom.GeometryType,Array.<number>,
-   *         (Array.<number>|Array.<Array.<number>>),Object.<string, *>)}
-   */
-  this.featureClass_ = options.featureClass ?
-      options.featureClass : ol.render.Feature;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.geometryName_ = options.geometryName ?
-      options.geometryName : 'geometry';
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.layerName_ = options.layerName ? options.layerName : 'layer';
-
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.layers_ = options.layers ? options.layers : null;
-
-};
-ol.inherits(ol.format.MVT, ol.format.Feature);
-
-
-/**
- * @inheritDoc
- */
-ol.format.MVT.prototype.getType = function() {
-  return ol.format.FormatType.ARRAY_BUFFER;
-};
-
-
-/**
- * @private
- * @param {Object} rawFeature Raw Mapbox feature.
- * @param {string} layer Layer.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- */
-ol.format.MVT.prototype.readFeature_ = function(
-    rawFeature, layer, opt_options) {
-  var feature = new this.featureClass_();
-  var id = rawFeature.id;
-  var values = rawFeature.properties;
-  values[this.layerName_] = layer;
-  var geometry = ol.format.Feature.transformWithOptions(
-      ol.format.MVT.readGeometry_(rawFeature), false,
-      this.adaptOptions(opt_options));
-  if (geometry) {
-    values[this.geometryName_] = geometry;
-  }
-  feature.setId(id);
-  feature.setProperties(values);
-  feature.setGeometryName(this.geometryName_);
-  return feature;
-};
-
-
-/**
- * @private
- * @param {Object} rawFeature Raw Mapbox feature.
- * @param {string} layer Layer.
- * @return {ol.render.Feature} Feature.
- */
-ol.format.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) {
-  var coords = rawFeature.loadGeometry();
-  var ends = [];
-  var flatCoordinates = [];
-  ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);
-
-  var type = rawFeature.type;
-  /** @type {ol.geom.GeometryType} */
-  var geometryType;
-  if (type === 1) {
-    geometryType = coords.length === 1 ?
-        ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT;
-  } else if (type === 2) {
-    if (coords.length === 1) {
-      geometryType = ol.geom.GeometryType.LINE_STRING;
-    } else {
-      geometryType = ol.geom.GeometryType.MULTI_LINE_STRING;
-    }
-  } else if (type === 3) {
-    geometryType = ol.geom.GeometryType.POLYGON;
-  }
-
-  var values = rawFeature.properties;
-  values[this.layerName_] = layer;
-
-  return new this.featureClass_(geometryType, flatCoordinates, ends, values);
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.format.MVT.prototype.readFeatures = function(source, opt_options) {
-  var layers = this.layers_;
-
-  var pbf = new ol.ext.pbf(/** @type {ArrayBuffer} */ (source));
-  var tile = new ol.ext.vectortile.VectorTile(pbf);
-  var features = [];
-  var featureClass = this.featureClass_;
-  var layer, feature;
-  for (var name in tile.layers) {
-    if (layers && layers.indexOf(name) == -1) {
-      continue;
-    }
-    layer = tile.layers[name];
-
-    for (var i = 0, ii = layer.length; i < ii; ++i) {
-      if (featureClass === ol.render.Feature) {
-        feature = this.readRenderFeature_(layer.feature(i), name);
-      } else {
-        feature = this.readFeature_(layer.feature(i), name, opt_options);
-      }
-      features.push(feature);
-    }
-  }
-
-  return features;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.format.MVT.prototype.readProjection = function(source) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * Sets the layers that features will be read from.
- * @param {Array.<string>} layers Layers.
- * @api
- */
-ol.format.MVT.prototype.setLayers = function(layers) {
-  this.layers_ = layers;
-};
-
-
-/**
- * @private
- * @param {Object} coords Raw feature coordinates.
- * @param {Array.<number>} flatCoordinates Flat coordinates to be populated by
- *     this function.
- * @param {Array.<number>} ends Ends to be populated by this function.
- */
-ol.format.MVT.calculateFlatCoordinates_ = function(
-    coords, flatCoordinates, ends) {
-  var end = 0;
-  for (var i = 0, ii = coords.length; i < ii; ++i) {
-    var line = coords[i];
-    var j, jj;
-    for (j = 0, jj = line.length; j < jj; ++j) {
-      var coord = line[j];
-      // Non-tilespace coords can be calculated here when a TileGrid and
-      // TileCoord are known.
-      flatCoordinates.push(coord.x, coord.y);
-    }
-    end += 2 * j;
-    ends.push(end);
-  }
-};
-
-
-/**
- * @private
- * @param {Object} rawFeature Raw Mapbox feature.
- * @return {ol.geom.Geometry} Geometry.
- */
-ol.format.MVT.readGeometry_ = function(rawFeature) {
-  var type = rawFeature.type;
-  if (type === 0) {
-    return null;
-  }
-
-  var coords = rawFeature.loadGeometry();
-  var ends = [];
-  var flatCoordinates = [];
-  ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);
-
-  var geom;
-  if (type === 1) {
-    geom = coords.length === 1 ?
-        new ol.geom.Point(null) : new ol.geom.MultiPoint(null);
-  } else if (type === 2) {
-    if (coords.length === 1) {
-      geom = new ol.geom.LineString(null);
-    } else {
-      geom = new ol.geom.MultiLineString(null);
-    }
-  } else if (type === 3) {
-    geom = new ol.geom.Polygon(null);
-  }
-
-  geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
-      ends);
-
-  return geom;
-};
-
-// FIXME add typedef for stack state objects
-goog.provide('ol.format.OSMXML');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.obj');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Feature format for reading data in the
- * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML).
- *
- * @constructor
- * @extends {ol.format.XMLFeature}
- * @api stable
- */
-ol.format.OSMXML = function() {
-  ol.format.XMLFeature.call(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
-};
-ol.inherits(ol.format.OSMXML, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.OSMXML.EXTENSIONS_ = ['.osm'];
-
-
-/**
- * @inheritDoc
- */
-ol.format.OSMXML.prototype.getExtensions = function() {
-  return ol.format.OSMXML.EXTENSIONS_;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.OSMXML.readNode_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'node', 'localName should be node');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var id = node.getAttribute('id');
-  /** @type {ol.Coordinate} */
-  var coordinates = [
-    parseFloat(node.getAttribute('lon')),
-    parseFloat(node.getAttribute('lat'))
-  ];
-  state.nodes[id] = coordinates;
-
-  var values = ol.xml.pushParseAndPop({
-    tags: {}
-  }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack);
-  if (!ol.obj.isEmpty(values.tags)) {
-    var geometry = new ol.geom.Point(coordinates);
-    ol.format.Feature.transformWithOptions(geometry, false, options);
-    var feature = new ol.Feature(geometry);
-    feature.setId(id);
-    feature.setProperties(values.tags);
-    state.features.push(feature);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.OSMXML.readWay_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'way', 'localName should be way');
-  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
-  var id = node.getAttribute('id');
-  var values = ol.xml.pushParseAndPop({
-    ndrefs: [],
-    tags: {}
-  }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack);
-  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  /** @type {Array.<number>} */
-  var flatCoordinates = [];
-  for (var i = 0, ii = values.ndrefs.length; i < ii; i++) {
-    var point = state.nodes[values.ndrefs[i]];
-    ol.array.extend(flatCoordinates, point);
-  }
-  var geometry;
-  if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) {
-    // closed way
-    geometry = new ol.geom.Polygon(null);
-    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
-        [flatCoordinates.length]);
-  } else {
-    geometry = new ol.geom.LineString(null);
-    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
-  }
-  ol.format.Feature.transformWithOptions(geometry, false, options);
-  var feature = new ol.Feature(geometry);
-  feature.setId(id);
-  feature.setProperties(values.tags);
-  state.features.push(feature);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.OSMXML.readNd_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'nd', 'localName should be nd');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  values.ndrefs.push(node.getAttribute('ref'));
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.OSMXML.readTag_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'tag', 'localName should be tag');
-  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  values.tags[node.getAttribute('k')] = node.getAttribute('v');
-};
-
-
-/**
- * @const
- * @private
- * @type {Array.<string>}
- */
-ol.format.OSMXML.NAMESPACE_URIS_ = [
-  null
-];
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'nd': ol.format.OSMXML.readNd_,
-      'tag': ol.format.OSMXML.readTag_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OSMXML.PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'node': ol.format.OSMXML.readNode_,
-      'way': ol.format.OSMXML.readWay_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OSMXML.NAMESPACE_URIS_, {
-      'tag': ol.format.OSMXML.readTag_
-    });
-
-
-/**
- * Read all features from an OSM source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.OSMXML.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  var options = this.getReadOptions(node, opt_options);
-  if (node.localName == 'osm') {
-    var state = ol.xml.pushParseAndPop({
-      nodes: {},
-      features: []
-    }, ol.format.OSMXML.PARSERS_, node, [options]);
-    if (state.features) {
-      return state.features;
-    }
-  }
-  return [];
-};
-
-
-/**
- * Read the projection from an OSM source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.OSMXML.prototype.readProjection;
-
-goog.provide('ol.format.XLink');
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';
-
-
-/**
- * @param {Node} node Node.
- * @return {boolean|undefined} Boolean.
- */
-ol.format.XLink.readHref = function(node) {
-  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
-};
-
-goog.provide('ol.format.XML');
-
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Generic format for reading non-feature XML data
- *
- * @constructor
- * @struct
- */
-ol.format.XML = function() {
-};
-
-
-/**
- * @param {Document|Node|string} source Source.
- * @return {Object} The parsed result.
- */
-ol.format.XML.prototype.read = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFromDocument(/** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readFromNode(/** @type {Node} */ (source));
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readFromDocument(doc);
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * @abstract
- * @param {Document} doc Document.
- * @return {Object} Object
- */
-ol.format.XML.prototype.readFromDocument = function(doc) {};
-
-
-/**
- * @abstract
- * @param {Node} node Node.
- * @return {Object} Object
- */
-ol.format.XML.prototype.readFromNode = function(node) {};
-
-goog.provide('ol.format.OWS');
-
-goog.require('ol');
-goog.require('ol.format.XLink');
-goog.require('ol.format.XML');
-goog.require('ol.format.XSD');
-goog.require('ol.xml');
-
-
-/**
- * @constructor
- * @extends {ol.format.XML}
- */
-ol.format.OWS = function() {
-  ol.format.XML.call(this);
-};
-ol.inherits(ol.format.OWS, ol.format.XML);
-
-
-/**
- * @param {Document} doc Document.
- * @return {Object} OWS object.
- */
-ol.format.OWS.prototype.readFromDocument = function(doc) {
-  ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
-      'doc.nodeType should be DOCUMENT');
-  for (var n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      return this.readFromNode(n);
-    }
-  }
-  return null;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {Object} OWS object.
- */
-ol.format.OWS.prototype.readFromNode = function(node) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  var owsObject = ol.xml.pushParseAndPop({},
-      ol.format.OWS.PARSERS_, node, []);
-  return owsObject ? owsObject : null;
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The address.
- */
-ol.format.OWS.readAddress_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Address',
-      'localName should be Address');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.ADDRESS_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The values.
- */
-ol.format.OWS.readAllowedValues_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'AllowedValues',
-      'localName should be AllowedValues');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The constraint.
- */
-ol.format.OWS.readConstraint_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Constraint',
-      'localName should be Constraint');
-  var name = node.getAttribute('name');
-  if (!name) {
-    return undefined;
-  }
-  return ol.xml.pushParseAndPop({'name': name},
-      ol.format.OWS.CONSTRAINT_PARSERS_, node,
-      objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The contact info.
- */
-ol.format.OWS.readContactInfo_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ContactInfo',
-      'localName should be ContactInfo');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The DCP.
- */
-ol.format.OWS.readDcp_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'DCP', 'localName should be DCP');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.DCP_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The GET object.
- */
-ol.format.OWS.readGet_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Get', 'localName should be Get');
-  var href = ol.format.XLink.readHref(node);
-  if (!href) {
-    return undefined;
-  }
-  return ol.xml.pushParseAndPop({'href': href},
-      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The HTTP object.
- */
-ol.format.OWS.readHttp_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP');
-  return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_,
-      node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The operation.
- */
-ol.format.OWS.readOperation_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Operation',
-      'localName should be Operation');
-  var name = node.getAttribute('name');
-  var value = ol.xml.pushParseAndPop({},
-      ol.format.OWS.OPERATION_PARSERS_, node, objectStack);
-  if (!value) {
-    return undefined;
-  }
-  var object = /** @type {Object} */
-      (objectStack[objectStack.length - 1]);
-  object[name] = value;
-
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The operations metadata.
- */
-ol.format.OWS.readOperationsMetadata_ = function(node,
-    objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'OperationsMetadata',
-      'localName should be OperationsMetadata');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node,
-      objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The phone.
- */
-ol.format.OWS.readPhone_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Phone', 'localName should be Phone');
-  return ol.xml.pushParseAndPop({},
-      ol.format.OWS.PHONE_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The service identification.
- */
-ol.format.OWS.readServiceIdentification_ = function(node,
-    objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ServiceIdentification',
-      'localName should be ServiceIdentification');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node,
-      objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The service contact.
- */
-ol.format.OWS.readServiceContact_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ServiceContact',
-      'localName should be ServiceContact');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node,
-      objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} The service provider.
- */
-ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ServiceProvider',
-      'localName should be ServiceProvider');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node,
-      objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {string|undefined} The value.
- */
-ol.format.OWS.readValue_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Value', 'localName should be Value');
-  return ol.format.XSD.readString(node);
-};
-
-
-/**
- * @const
- * @type {Array.<string>}
- * @private
- */
-ol.format.OWS.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/ows/1.1'
-];
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'ServiceIdentification': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceIdentification_),
-      'ServiceProvider': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceProvider_),
-      'OperationsMetadata': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readOperationsMetadata_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'DeliveryPoint': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'AdministrativeArea': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'ElectronicMailAddress': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'AllowedValues': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readAllowedValues_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Phone': ol.xml.makeObjectPropertySetter(ol.format.OWS.readPhone_),
-      'Address': ol.xml.makeObjectPropertySetter(ol.format.OWS.readAddress_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_),
-      'Post': undefined // TODO
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Operation': ol.format.OWS.readOperation_
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Constraint': ol.xml.makeObjectPropertyPusher(
-          ol.format.OWS.readConstraint_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.SERVICE_CONTACT_PARSERS_ =
-    ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'IndividualName': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'ContactInfo': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readContactInfo_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
-    ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'ServiceTypeVersion': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'ServiceType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.OWS.SERVICE_PROVIDER_PARSERS_ =
-    ol.xml.makeStructureNS(
-    ol.format.OWS.NAMESPACE_URIS_, {
-      'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref),
-      'ServiceContact': ol.xml.makeObjectPropertySetter(
-          ol.format.OWS.readServiceContact_)
-    });
-
-goog.provide('ol.geom.flat.flip');
-
-
-/**
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- * @param {number} offset Offset.
- * @param {number} end End.
- * @param {number} stride Stride.
- * @param {Array.<number>=} opt_dest Destination.
- * @param {number=} opt_destOffset Destination offset.
- * @return {Array.<number>} Flat coordinates.
- */
-ol.geom.flat.flip.flipXY = function(flatCoordinates, offset, end, stride, opt_dest, opt_destOffset) {
-  var dest, destOffset;
-  if (opt_dest !== undefined) {
-    dest = opt_dest;
-    destOffset = opt_destOffset !== undefined ? opt_destOffset : 0;
-  } else {
-    dest = [];
-    destOffset = 0;
-  }
-  var j = offset;
-  while (j < end) {
-    var x = flatCoordinates[j++];
-    dest[destOffset++] = flatCoordinates[j++];
-    dest[destOffset++] = x;
-    for (var k = 2; k < stride; ++k) {
-      dest[destOffset++] = flatCoordinates[j++];
-    }
-  }
-  dest.length = destOffset;
-  return dest;
-};
-
-goog.provide('ol.format.Polyline');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.TextFeature');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.flip');
-goog.require('ol.geom.flat.inflate');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the Encoded
- * Polyline Algorithm Format.
- *
- * @constructor
- * @extends {ol.format.TextFeature}
- * @param {olx.format.PolylineOptions=} opt_options
- *     Optional configuration object.
- * @api stable
- */
-ol.format.Polyline = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.TextFeature.call(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get('EPSG:4326');
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.factor_ = options.factor ? options.factor : 1e5;
-
-  /**
-   * @private
-   * @type {ol.geom.GeometryLayout}
-   */
-  this.geometryLayout_ = options.geometryLayout ?
-      options.geometryLayout : ol.geom.GeometryLayout.XY;
-};
-ol.inherits(ol.format.Polyline, ol.format.TextFeature);
-
-
-/**
- * Encode a list of n-dimensional points and return an encoded string
- *
- * Attention: This function will modify the passed array!
- *
- * @param {Array.<number>} numbers A list of n-dimensional points.
- * @param {number} stride The number of dimension of the points in the list.
- * @param {number=} opt_factor The factor by which the numbers will be
- *     multiplied. The remaining decimal places will get rounded away.
- *     Default is `1e5`.
- * @return {string} The encoded string.
- * @api
- */
-ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) {
-  var factor = opt_factor ? opt_factor : 1e5;
-  var d;
-
-  var lastNumbers = new Array(stride);
-  for (d = 0; d < stride; ++d) {
-    lastNumbers[d] = 0;
-  }
-
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii;) {
-    for (d = 0; d < stride; ++d, ++i) {
-      var num = numbers[i];
-      var delta = num - lastNumbers[d];
-      lastNumbers[d] = num;
-
-      numbers[i] = delta;
-    }
-  }
-
-  return ol.format.Polyline.encodeFloats(numbers, factor);
-};
-
-
-/**
- * Decode a list of n-dimensional points from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @param {number} stride The number of dimension of the points in the
- *     encoded string.
- * @param {number=} opt_factor The factor by which the resulting numbers will
- *     be divided. Default is `1e5`.
- * @return {Array.<number>} A list of n-dimensional points.
- * @api
- */
-ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) {
-  var factor = opt_factor ? opt_factor : 1e5;
-  var d;
-
-  /** @type {Array.<number>} */
-  var lastNumbers = new Array(stride);
-  for (d = 0; d < stride; ++d) {
-    lastNumbers[d] = 0;
-  }
-
-  var numbers = ol.format.Polyline.decodeFloats(encoded, factor);
-
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii;) {
-    for (d = 0; d < stride; ++d, ++i) {
-      lastNumbers[d] += numbers[i];
-
-      numbers[i] = lastNumbers[d];
-    }
-  }
-
-  return numbers;
-};
-
-
-/**
- * Encode a list of floating point numbers and return an encoded string
- *
- * Attention: This function will modify the passed array!
- *
- * @param {Array.<number>} numbers A list of floating point numbers.
- * @param {number=} opt_factor The factor by which the numbers will be
- *     multiplied. The remaining decimal places will get rounded away.
- *     Default is `1e5`.
- * @return {string} The encoded string.
- * @api
- */
-ol.format.Polyline.encodeFloats = function(numbers, opt_factor) {
-  var factor = opt_factor ? opt_factor : 1e5;
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    numbers[i] = Math.round(numbers[i] * factor);
-  }
-
-  return ol.format.Polyline.encodeSignedIntegers(numbers);
-};
-
-
-/**
- * Decode a list of floating point numbers from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @param {number=} opt_factor The factor by which the result will be divided.
- *     Default is `1e5`.
- * @return {Array.<number>} A list of floating point numbers.
- * @api
- */
-ol.format.Polyline.decodeFloats = function(encoded, opt_factor) {
-  var factor = opt_factor ? opt_factor : 1e5;
-  var numbers = ol.format.Polyline.decodeSignedIntegers(encoded);
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    numbers[i] /= factor;
-  }
-  return numbers;
-};
-
-
-/**
- * Encode a list of signed integers and return an encoded string
- *
- * Attention: This function will modify the passed array!
- *
- * @param {Array.<number>} numbers A list of signed integers.
- * @return {string} The encoded string.
- */
-ol.format.Polyline.encodeSignedIntegers = function(numbers) {
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    var num = numbers[i];
-    numbers[i] = (num < 0) ? ~(num << 1) : (num << 1);
-  }
-  return ol.format.Polyline.encodeUnsignedIntegers(numbers);
-};
-
-
-/**
- * Decode a list of signed integers from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @return {Array.<number>} A list of signed integers.
- */
-ol.format.Polyline.decodeSignedIntegers = function(encoded) {
-  var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded);
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    var num = numbers[i];
-    numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1);
-  }
-  return numbers;
-};
-
-
-/**
- * Encode a list of unsigned integers and return an encoded string
- *
- * @param {Array.<number>} numbers A list of unsigned integers.
- * @return {string} The encoded string.
- */
-ol.format.Polyline.encodeUnsignedIntegers = function(numbers) {
-  var encoded = '';
-  var i, ii;
-  for (i = 0, ii = numbers.length; i < ii; ++i) {
-    encoded += ol.format.Polyline.encodeUnsignedInteger(numbers[i]);
-  }
-  return encoded;
-};
-
-
-/**
- * Decode a list of unsigned integers from an encoded string
- *
- * @param {string} encoded An encoded string.
- * @return {Array.<number>} A list of unsigned integers.
- */
-ol.format.Polyline.decodeUnsignedIntegers = function(encoded) {
-  var numbers = [];
-  var current = 0;
-  var shift = 0;
-  var i, ii;
-  for (i = 0, ii = encoded.length; i < ii; ++i) {
-    var b = encoded.charCodeAt(i) - 63;
-    current |= (b & 0x1f) << shift;
-    if (b < 0x20) {
-      numbers.push(current);
-      current = 0;
-      shift = 0;
-    } else {
-      shift += 5;
-    }
-  }
-  return numbers;
-};
-
-
-/**
- * Encode one single unsigned integer and return an encoded string
- *
- * @param {number} num Unsigned integer that should be encoded.
- * @return {string} The encoded string.
- */
-ol.format.Polyline.encodeUnsignedInteger = function(num) {
-  var value, encoded = '';
-  while (num >= 0x20) {
-    value = (0x20 | (num & 0x1f)) + 63;
-    encoded += String.fromCharCode(value);
-    num >>= 5;
-  }
-  value = num + 63;
-  encoded += String.fromCharCode(value);
-  return encoded;
-};
-
-
-/**
- * Read the feature from the Polyline source. The coordinates are assumed to be
- * in two dimensions and in latitude, longitude order.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
- */
-ol.format.Polyline.prototype.readFeature;
-
-
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) {
-  var geometry = this.readGeometryFromText(text, opt_options);
-  return new ol.Feature(geometry);
-};
-
-
-/**
- * Read the feature from the source. As Polyline sources contain a single
- * feature, this will return the feature in an array.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.Polyline.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.readFeaturesFromText = function(text, opt_options) {
-  var feature = this.readFeatureFromText(text, opt_options);
-  return [feature];
-};
-
-
-/**
- * Read the geometry from the source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
- * @api stable
- */
-ol.format.Polyline.prototype.readGeometry;
-
-
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.readGeometryFromText = function(text, opt_options) {
-  var stride = ol.geom.SimpleGeometry.getStrideForLayout(this.geometryLayout_);
-  var flatCoordinates = ol.format.Polyline.decodeDeltas(
-      text, stride, this.factor_);
-  ol.geom.flat.flip.flipXY(
-      flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates);
-  var coordinates = ol.geom.flat.inflate.coordinates(
-      flatCoordinates, 0, flatCoordinates.length, stride);
-
-  return /** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(
-          new ol.geom.LineString(coordinates, this.geometryLayout_), false,
-          this.adaptOptions(opt_options)));
-};
-
-
-/**
- * Read the projection from a Polyline source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.Polyline.prototype.readProjection;
-
-
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) {
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    return this.writeGeometryText(geometry, opt_options);
-  } else {
-    ol.asserts.assert(false, 40); // Expected `feature` to have a geometry
-    return '';
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.writeFeaturesText = function(features, opt_options) {
-  ol.DEBUG && console.assert(features.length == 1,
-      'features array should have 1 item');
-  return this.writeFeatureText(features[0], opt_options);
-};
-
-
-/**
- * Write a single geometry in Polyline format.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} Geometry.
- * @api stable
- */
-ol.format.Polyline.prototype.writeGeometry;
-
-
-/**
- * @inheritDoc
- */
-ol.format.Polyline.prototype.writeGeometryText = function(geometry, opt_options) {
-  geometry = /** @type {ol.geom.LineString} */
-      (ol.format.Feature.transformWithOptions(
-          geometry, true, this.adaptOptions(opt_options)));
-  var flatCoordinates = geometry.getFlatCoordinates();
-  var stride = geometry.getStride();
-  ol.geom.flat.flip.flipXY(
-      flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates);
-  return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_);
-};
-
-goog.provide('ol.format.TopoJSON');
-
-goog.require('ol');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.JSONFeature');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.obj');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Feature format for reading data in the TopoJSON format.
- *
- * @constructor
- * @extends {ol.format.JSONFeature}
- * @param {olx.format.TopoJSONOptions=} opt_options Options.
- * @api stable
- */
-ol.format.TopoJSON = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.JSONFeature.call(this);
-
-  /**
-   * @inheritDoc
-   */
-  this.defaultDataProjection = ol.proj.get(
-      options.defaultDataProjection ?
-          options.defaultDataProjection : 'EPSG:4326');
-
-};
-ol.inherits(ol.format.TopoJSON, ol.format.JSONFeature);
-
-
-/**
- * @const {Array.<string>}
- * @private
- */
-ol.format.TopoJSON.EXTENSIONS_ = ['.topojson'];
-
-
-/**
- * Concatenate arcs into a coordinate array.
- * @param {Array.<number>} indices Indices of arcs to concatenate.  Negative
- *     values indicate arcs need to be reversed.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs (already
- *     transformed).
- * @return {Array.<ol.Coordinate>} Coordinates array.
- * @private
- */
-ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) {
-  /** @type {Array.<ol.Coordinate>} */
-  var coordinates = [];
-  var index, arc;
-  var i, ii;
-  var j, jj;
-  for (i = 0, ii = indices.length; i < ii; ++i) {
-    index = indices[i];
-    if (i > 0) {
-      // splicing together arcs, discard last point
-      coordinates.pop();
-    }
-    if (index >= 0) {
-      // forward arc
-      arc = arcs[index];
-    } else {
-      // reverse arc
-      arc = arcs[~index].slice().reverse();
-    }
-    coordinates.push.apply(coordinates, arc);
-  }
-  // provide fresh copies of coordinate arrays
-  for (j = 0, jj = coordinates.length; j < jj; ++j) {
-    coordinates[j] = coordinates[j].slice();
-  }
-  return coordinates;
-};
-
-
-/**
- * Create a point from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @return {ol.geom.Point} Geometry.
- * @private
- */
-ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) {
-  var coordinates = object.coordinates;
-  if (scale && translate) {
-    ol.format.TopoJSON.transformVertex_(coordinates, scale, translate);
-  }
-  return new ol.geom.Point(coordinates);
-};
-
-
-/**
- * Create a multi-point from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @return {ol.geom.MultiPoint} Geometry.
- * @private
- */
-ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale,
-    translate) {
-  var coordinates = object.coordinates;
-  var i, ii;
-  if (scale && translate) {
-    for (i = 0, ii = coordinates.length; i < ii; ++i) {
-      ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate);
-    }
-  }
-  return new ol.geom.MultiPoint(coordinates);
-};
-
-
-/**
- * Create a linestring from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.LineString} Geometry.
- * @private
- */
-ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
-  var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
-  return new ol.geom.LineString(coordinates);
-};
-
-
-/**
- * Create a multi-linestring from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.MultiLineString} Geometry.
- * @private
- */
-ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) {
-  var coordinates = [];
-  var i, ii;
-  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
-    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
-  }
-  return new ol.geom.MultiLineString(coordinates);
-};
-
-
-/**
- * Create a polygon from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.Polygon} Geometry.
- * @private
- */
-ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) {
-  var coordinates = [];
-  var i, ii;
-  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
-    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
-  }
-  return new ol.geom.Polygon(coordinates);
-};
-
-
-/**
- * Create a multi-polygon from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @return {ol.geom.MultiPolygon} Geometry.
- * @private
- */
-ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) {
-  var coordinates = [];
-  var polyArray, ringCoords, j, jj;
-  var i, ii;
-  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
-    // for each polygon
-    polyArray = object.arcs[i];
-    ringCoords = [];
-    for (j = 0, jj = polyArray.length; j < jj; ++j) {
-      // for each ring
-      ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs);
-    }
-    coordinates[i] = ringCoords;
-  }
-  return new ol.geom.MultiPolygon(coordinates);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.TopoJSON.prototype.getExtensions = function() {
-  return ol.format.TopoJSON.EXTENSIONS_;
-};
-
-
-/**
- * Create features from a TopoJSON GeometryCollection object.
- *
- * @param {TopoJSONGeometryCollection} collection TopoJSON Geometry
- *     object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Array of features.
- * @private
- */
-ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function(
-    collection, arcs, scale, translate, opt_options) {
-  var geometries = collection.geometries;
-  var features = [];
-  var i, ii;
-  for (i = 0, ii = geometries.length; i < ii; ++i) {
-    features[i] = ol.format.TopoJSON.readFeatureFromGeometry_(
-        geometries[i], arcs, scale, translate, opt_options);
-  }
-  return features;
-};
-
-
-/**
- * Create a feature from a TopoJSON geometry object.
- *
- * @param {TopoJSONGeometry} object TopoJSON geometry object.
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @private
- */
-ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs,
-    scale, translate, opt_options) {
-  var geometry;
-  var type = object.type;
-  var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type];
-  if ((type === 'Point') || (type === 'MultiPoint')) {
-    geometry = geometryReader(object, scale, translate);
-  } else {
-    geometry = geometryReader(object, arcs);
-  }
-  var feature = new ol.Feature();
-  feature.setGeometry(/** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(geometry, false, opt_options)));
-  if (object.id !== undefined) {
-    feature.setId(object.id);
-  }
-  if (object.properties) {
-    feature.setProperties(object.properties);
-  }
-  return feature;
-};
-
-
-/**
- * Read all features from a TopoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.TopoJSON.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.TopoJSON.prototype.readFeaturesFromObject = function(
-    object, opt_options) {
-  if (object.type == 'Topology') {
-    var topoJSONTopology = /** @type {TopoJSONTopology} */ (object);
-    var transform, scale = null, translate = null;
-    if (topoJSONTopology.transform) {
-      transform = topoJSONTopology.transform;
-      scale = transform.scale;
-      translate = transform.translate;
-    }
-    var arcs = topoJSONTopology.arcs;
-    if (transform) {
-      ol.format.TopoJSON.transformArcs_(arcs, scale, translate);
-    }
-    /** @type {Array.<ol.Feature>} */
-    var features = [];
-    var topoJSONFeatures = ol.obj.getValues(topoJSONTopology.objects);
-    var i, ii;
-    var feature;
-    for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) {
-      if (topoJSONFeatures[i].type === 'GeometryCollection') {
-        feature = /** @type {TopoJSONGeometryCollection} */
-            (topoJSONFeatures[i]);
-        features.push.apply(features,
-            ol.format.TopoJSON.readFeaturesFromGeometryCollection_(
-                feature, arcs, scale, translate, opt_options));
-      } else {
-        feature = /** @type {TopoJSONGeometry} */
-            (topoJSONFeatures[i]);
-        features.push(ol.format.TopoJSON.readFeatureFromGeometry_(
-            feature, arcs, scale, translate, opt_options));
-      }
-    }
-    return features;
-  } else {
-    return [];
-  }
-};
-
-
-/**
- * Apply a linear transform to array of arcs.  The provided array of arcs is
- * modified in place.
- *
- * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @private
- */
-ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) {
-  var i, ii;
-  for (i = 0, ii = arcs.length; i < ii; ++i) {
-    ol.format.TopoJSON.transformArc_(arcs[i], scale, translate);
-  }
-};
-
-
-/**
- * Apply a linear transform to an arc.  The provided arc is modified in place.
- *
- * @param {Array.<ol.Coordinate>} arc Arc.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @private
- */
-ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) {
-  var x = 0;
-  var y = 0;
-  var vertex;
-  var i, ii;
-  for (i = 0, ii = arc.length; i < ii; ++i) {
-    vertex = arc[i];
-    x += vertex[0];
-    y += vertex[1];
-    vertex[0] = x;
-    vertex[1] = y;
-    ol.format.TopoJSON.transformVertex_(vertex, scale, translate);
-  }
-};
-
-
-/**
- * Apply a linear transform to a vertex.  The provided vertex is modified in
- * place.
- *
- * @param {ol.Coordinate} vertex Vertex.
- * @param {Array.<number>} scale Scale for each dimension.
- * @param {Array.<number>} translate Translation for each dimension.
- * @private
- */
-ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
-  vertex[0] = vertex[0] * scale[0] + translate[0];
-  vertex[1] = vertex[1] * scale[1] + translate[1];
-};
-
-
-/**
- * Read the projection from a TopoJSON source.
- *
- * @function
- * @param {Document|Node|Object|string} object Source.
- * @return {ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.TopoJSON.prototype.readProjection = function(object) {
-  return this.defaultDataProjection;
-};
-
-
-/**
- * @const
- * @private
- * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>}
- */
-ol.format.TopoJSON.GEOMETRY_READERS_ = {
-  'Point': ol.format.TopoJSON.readPointGeometry_,
-  'LineString': ol.format.TopoJSON.readLineStringGeometry_,
-  'Polygon': ol.format.TopoJSON.readPolygonGeometry_,
-  'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_,
-  'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_,
-  'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_
-};
-
-goog.provide('ol.format.WFS');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.format.GML3');
-goog.require('ol.format.GMLBase');
-goog.require('ol.format.filter');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.format.XSD');
-goog.require('ol.geom.Geometry');
-goog.require('ol.obj');
-goog.require('ol.proj');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Feature format for reading and writing data in the WFS format.
- * By default, supports WFS version 1.1.0. You can pass a GML format
- * as option if you want to read a WFS that contains GML2 (WFS 1.0.0).
- * Also see {@link ol.format.GMLBase} which is used by this format.
- *
- * @constructor
- * @param {olx.format.WFSOptions=} opt_options
- *     Optional configuration object.
- * @extends {ol.format.XMLFeature}
- * @api stable
- */
-ol.format.WFS = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {Array.<string>|string|undefined}
-   */
-  this.featureType_ = options.featureType;
-
-  /**
-   * @private
-   * @type {Object.<string, string>|string|undefined}
-   */
-  this.featureNS_ = options.featureNS;
-
-  /**
-   * @private
-   * @type {ol.format.GMLBase}
-   */
-  this.gmlFormat_ = options.gmlFormat ?
-      options.gmlFormat : new ol.format.GML3();
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.schemaLocation_ = options.schemaLocation ?
-      options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION;
-
-  ol.format.XMLFeature.call(this);
-};
-ol.inherits(ol.format.WFS, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.FEATURE_PREFIX = 'feature';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.OGCNS = 'http://www.opengis.net/ogc';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.WFSNS = 'http://www.opengis.net/wfs';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' +
-    'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd';
-
-
-/**
- * Read all features from a WFS FeatureCollection.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.WFS.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) {
-  var context = /** @type {ol.XmlNodeStackItem} */ ({
-    'featureType': this.featureType_,
-    'featureNS': this.featureNS_
-  });
-  ol.obj.assign(context, this.getReadOptions(node,
-      opt_options ? opt_options : {}));
-  var objectStack = [context];
-  this.gmlFormat_.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][
-      'featureMember'] =
-      ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal);
-  var features = ol.xml.pushParseAndPop([],
-      this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
-      objectStack, this.gmlFormat_);
-  if (!features) {
-    features = [];
-  }
-  return features;
-};
-
-
-/**
- * Read transaction response of the source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.WFSTransactionResponse|undefined} Transaction response.
- * @api stable
- */
-ol.format.WFS.prototype.readTransactionResponse = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readTransactionResponseFromDocument(
-        /** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readTransactionResponseFromNode(/** @type {Node} */ (source));
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readTransactionResponseFromDocument(doc);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * Read feature collection metadata of the source.
- *
- * @param {Document|Node|Object|string} source Source.
- * @return {ol.WFSFeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
- * @api stable
- */
-ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) {
-  if (ol.xml.isDocument(source)) {
-    return this.readFeatureCollectionMetadataFromDocument(
-        /** @type {Document} */ (source));
-  } else if (ol.xml.isNode(source)) {
-    return this.readFeatureCollectionMetadataFromNode(
-        /** @type {Node} */ (source));
-  } else if (typeof source === 'string') {
-    var doc = ol.xml.parse(source);
-    return this.readFeatureCollectionMetadataFromDocument(doc);
-  } else {
-    return undefined;
-  }
-};
-
-
-/**
- * @param {Document} doc Document.
- * @return {ol.WFSFeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
- */
-ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument = function(doc) {
-  ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
-      'doc.nodeType should be DOCUMENT');
-  for (var n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      return this.readFeatureCollectionMetadataFromNode(n);
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = {
-  'http://www.opengis.net/gml': {
-    'boundedBy': ol.xml.makeObjectPropertySetter(
-        ol.format.GMLBase.prototype.readGeometryElement, 'bounds')
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {ol.WFSFeatureCollectionMetadata|undefined}
- *     FeatureCollection metadata.
- */
-ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'FeatureCollection',
-      'localName should be FeatureCollection');
-  var result = {};
-  var value = ol.format.XSD.readNonNegativeIntegerString(
-      node.getAttribute('numberOfFeatures'));
-  result['numberOfFeatures'] = value;
-  return ol.xml.pushParseAndPop(
-      /** @type {ol.WFSFeatureCollectionMetadata} */ (result),
-      ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_);
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'totalInserted': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger),
-    'totalUpdated': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger),
-    'totalDeleted': ol.xml.makeObjectPropertySetter(
-        ol.format.XSD.readNonNegativeInteger)
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Transaction Summary.
- * @private
- */
-ol.format.WFS.readTransactionSummary_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WFS.OGC_FID_PARSERS_ = {
-  'http://www.opengis.net/ogc': {
-    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
-      return node.getAttribute('fid');
-    })
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- */
-ol.format.WFS.fidParser_ = function(node, objectStack) {
-  ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Feature': ol.format.WFS.fidParser_
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Insert results.
- * @private
- */
-ol.format.WFS.readInsertResults_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = {
-  'http://www.opengis.net/wfs': {
-    'TransactionSummary': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readTransactionSummary_, 'transactionSummary'),
-    'InsertResults': ol.xml.makeObjectPropertySetter(
-        ol.format.WFS.readInsertResults_, 'insertIds')
-  }
-};
-
-
-/**
- * @param {Document} doc Document.
- * @return {ol.WFSTransactionResponse|undefined} Transaction response.
- */
-ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) {
-  ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
-      'doc.nodeType should be DOCUMENT');
-  for (var n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      return this.readTransactionResponseFromNode(n);
-    }
-  }
-  return undefined;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {ol.WFSTransactionResponse|undefined} Transaction response.
- */
-ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should  be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'TransactionResponse',
-      'localName should be TransactionResponse');
-  return ol.xml.pushParseAndPop(
-      /** @type {ol.WFSTransactionResponse} */({}),
-      ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []);
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.WFS.QUERY_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeFeature_ = function(node, feature, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  var featureType = context['featureType'];
-  var featureNS = context['featureNS'];
-  var child = ol.xml.createElementNS(featureNS, featureType);
-  node.appendChild(child);
-  ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {number|string} fid Feature identifier.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) {
-  var filter = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter');
-  var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'FeatureId');
-  filter.appendChild(child);
-  child.setAttribute('fid', fid);
-  node.appendChild(filter);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeDelete_ = function(node, feature, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  ol.asserts.assert(feature.getId() !== undefined, 26); // Features must have an id set
-  var featureType = context['featureType'];
-  var featurePrefix = context['featurePrefix'];
-  featurePrefix = featurePrefix ? featurePrefix :
-      ol.format.WFS.FEATURE_PREFIX;
-  var featureNS = context['featureNS'];
-  node.setAttribute('typeName', featurePrefix + ':' + featureType);
-  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
-      featureNS);
-  var fid = feature.getId();
-  if (fid !== undefined) {
-    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.Feature} feature Feature.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  ol.asserts.assert(feature.getId() !== undefined, 27); // Features must have an id set
-  var featureType = context['featureType'];
-  var featurePrefix = context['featurePrefix'];
-  featurePrefix = featurePrefix ? featurePrefix :
-      ol.format.WFS.FEATURE_PREFIX;
-  var featureNS = context['featureNS'];
-  node.setAttribute('typeName', featurePrefix + ':' + featureType);
-  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
-      featureNS);
-  var fid = feature.getId();
-  if (fid !== undefined) {
-    var keys = feature.getKeys();
-    var values = [];
-    for (var i = 0, ii = keys.length; i < ii; i++) {
-      var value = feature.get(keys[i]);
-      if (value !== undefined) {
-        values.push({name: keys[i], value: value});
-      }
-    }
-    ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ (
-        {node: node, 'srsName': context['srsName']}),
-        ol.format.WFS.TRANSACTION_SERIALIZERS_,
-        ol.xml.makeSimpleNodeFactory('Property'), values,
-        objectStack);
-    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Object} pair Property name and value.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeProperty_ = function(node, pair, objectStack) {
-  var name = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Name');
-  node.appendChild(name);
-  ol.format.XSD.writeStringTextNode(name, pair.name);
-  if (pair.value !== undefined && pair.value !== null) {
-    var value = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Value');
-    node.appendChild(value);
-    if (pair.value instanceof ol.geom.Geometry) {
-      ol.format.GML3.prototype.writeGeometryElement(value,
-          pair.value, objectStack);
-    } else {
-      ol.format.XSD.writeStringTextNode(value, pair.value);
-    }
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {{vendorId: string, safeToIgnore: boolean, value: string}}
- *     nativeElement The native element.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) {
-  if (nativeElement.vendorId) {
-    node.setAttribute('vendorId', nativeElement.vendorId);
-  }
-  if (nativeElement.safeToIgnore !== undefined) {
-    node.setAttribute('safeToIgnore', nativeElement.safeToIgnore);
-  }
-  if (nativeElement.value !== undefined) {
-    ol.format.XSD.writeStringTextNode(node, nativeElement.value);
-  }
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.WFS.TRANSACTION_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_),
-    'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_),
-    'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_),
-    'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_),
-    'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_)
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {string} featureType Feature type.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) {
-  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var featurePrefix = context['featurePrefix'];
-  var featureNS = context['featureNS'];
-  var propertyNames = context['propertyNames'];
-  var srsName = context['srsName'];
-  var prefix = featurePrefix ? featurePrefix + ':' : '';
-  node.setAttribute('typeName', prefix + featureType);
-  if (srsName) {
-    node.setAttribute('srsName', srsName);
-  }
-  if (featureNS) {
-    ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
-        featureNS);
-  }
-  var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context));
-  item.node = node;
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.QUERY_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames,
-      objectStack);
-  var filter = context['filter'];
-  if (filter) {
-    var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter');
-    node.appendChild(child);
-    ol.format.WFS.writeFilterCondition_(child, filter, objectStack);
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.Filter} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeFilterCondition_ = function(node, filter, objectStack) {
-  /** @type {ol.XmlNodeStackItem} */
-  var item = {node: node};
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.GETFEATURE_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory(filter.getTagName()),
-      [filter], objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.Bbox} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  context['srsName'] = filter.srsName;
-
-  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
-  ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.Intersects} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeIntersectsFilter_ = function(node, filter, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  context['srsName'] = filter.srsName;
-
-  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
-  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.Within} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeWithinFilter_ = function(node, filter, objectStack) {
-  var context = objectStack[objectStack.length - 1];
-  context['srsName'] = filter.srsName;
-
-  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
-  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.LogicalBinary} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeLogicalFilter_ = function(node, filter, objectStack) {
-  /** @type {ol.XmlNodeStackItem} */
-  var item = {node: node};
-  var conditionA = filter.conditionA;
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.GETFEATURE_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory(conditionA.getTagName()),
-      [conditionA], objectStack);
-  var conditionB = filter.conditionB;
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.GETFEATURE_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory(conditionB.getTagName()),
-      [conditionB], objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.Not} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeNotFilter_ = function(node, filter, objectStack) {
-  /** @type {ol.XmlNodeStackItem} */
-  var item = {node: node};
-  var condition = filter.condition;
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.GETFEATURE_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory(condition.getTagName()),
-      [condition], objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.ComparisonBinary} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) {
-  if (filter.matchCase !== undefined) {
-    node.setAttribute('matchCase', filter.matchCase.toString());
-  }
-  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
-  ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.IsNull} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) {
-  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.IsBetween} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeIsBetweenFilter_ = function(node, filter, objectStack) {
-  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
-
-  var lowerBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'LowerBoundary');
-  node.appendChild(lowerBoundary);
-  ol.format.WFS.writeOgcLiteral_(lowerBoundary, '' + filter.lowerBoundary);
-
-  var upperBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'UpperBoundary');
-  node.appendChild(upperBoundary);
-  ol.format.WFS.writeOgcLiteral_(upperBoundary, '' + filter.upperBoundary);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {ol.format.filter.IsLike} filter Filter.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeIsLikeFilter_ = function(node, filter, objectStack) {
-  node.setAttribute('wildCard', filter.wildCard);
-  node.setAttribute('singleChar', filter.singleChar);
-  node.setAttribute('escapeChar', filter.escapeChar);
-  if (filter.matchCase !== undefined) {
-    node.setAttribute('matchCase', filter.matchCase.toString());
-  }
-  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
-  ol.format.WFS.writeOgcLiteral_(node, '' + filter.pattern);
-};
-
-
-/**
- * @param {string} tagName Tag name.
- * @param {Node} node Node.
- * @param {string} value Value.
- * @private
- */
-ol.format.WFS.writeOgcExpression_ = function(tagName, node, value) {
-  var property = ol.xml.createElementNS(ol.format.WFS.OGCNS, tagName);
-  ol.format.XSD.writeStringTextNode(property, value);
-  node.appendChild(property);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {string} value PropertyName value.
- * @private
- */
-ol.format.WFS.writeOgcPropertyName_ = function(node, value) {
-  ol.format.WFS.writeOgcExpression_('PropertyName', node, value);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {string} value PropertyName value.
- * @private
- */
-ol.format.WFS.writeOgcLiteral_ = function(node, value) {
-  ol.format.WFS.writeOgcExpression_('Literal', node, value);
-};
-
-
-/**
- * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
- * @private
- */
-ol.format.WFS.GETFEATURE_SERIALIZERS_ = {
-  'http://www.opengis.net/wfs': {
-    'Query': ol.xml.makeChildAppender(ol.format.WFS.writeQuery_)
-  },
-  'http://www.opengis.net/ogc': {
-    'And': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_),
-    'Or': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_),
-    'Not': ol.xml.makeChildAppender(ol.format.WFS.writeNotFilter_),
-    'BBOX': ol.xml.makeChildAppender(ol.format.WFS.writeBboxFilter_),
-    'Intersects': ol.xml.makeChildAppender(ol.format.WFS.writeIntersectsFilter_),
-    'Within': ol.xml.makeChildAppender(ol.format.WFS.writeWithinFilter_),
-    'PropertyIsEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
-    'PropertyIsNotEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
-    'PropertyIsLessThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
-    'PropertyIsLessThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
-    'PropertyIsGreaterThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
-    'PropertyIsGreaterThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
-    'PropertyIsNull': ol.xml.makeChildAppender(ol.format.WFS.writeIsNullFilter_),
-    'PropertyIsBetween': ol.xml.makeChildAppender(ol.format.WFS.writeIsBetweenFilter_),
-    'PropertyIsLike': ol.xml.makeChildAppender(ol.format.WFS.writeIsLikeFilter_)
-  }
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<string>} featureTypes Feature types.
- * @param {Array.<*>} objectStack Node stack.
- * @private
- */
-ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) {
-  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
-  var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context));
-  item.node = node;
-  ol.xml.pushSerializeAndPop(item,
-      ol.format.WFS.GETFEATURE_SERIALIZERS_,
-      ol.xml.makeSimpleNodeFactory('Query'), featureTypes,
-      objectStack);
-};
-
-
-/**
- * Encode format as WFS `GetFeature` and return the Node.
- *
- * @param {olx.format.WFSWriteGetFeatureOptions} options Options.
- * @return {Node} Result.
- * @api stable
- */
-ol.format.WFS.prototype.writeGetFeature = function(options) {
-  var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'GetFeature');
-  node.setAttribute('service', 'WFS');
-  node.setAttribute('version', '1.1.0');
-  var filter;
-  if (options) {
-    if (options.handle) {
-      node.setAttribute('handle', options.handle);
-    }
-    if (options.outputFormat) {
-      node.setAttribute('outputFormat', options.outputFormat);
-    }
-    if (options.maxFeatures !== undefined) {
-      node.setAttribute('maxFeatures', options.maxFeatures);
-    }
-    if (options.resultType) {
-      node.setAttribute('resultType', options.resultType);
-    }
-    if (options.startIndex !== undefined) {
-      node.setAttribute('startIndex', options.startIndex);
-    }
-    if (options.count !== undefined) {
-      node.setAttribute('count', options.count);
-    }
-    filter = options.filter;
-    if (options.bbox) {
-      ol.asserts.assert(options.geometryName,
-          12); // `options.geometryName` must also be provided when `options.bbox` is set
-      var bbox = ol.format.filter.bbox(
-          /** @type {string} */ (options.geometryName), options.bbox, options.srsName);
-      if (filter) {
-        // if bbox and filter are both set, combine the two into a single filter
-        filter = ol.format.filter.and(filter, bbox);
-      } else {
-        filter = bbox;
-      }
-    }
-  }
-  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
-      'xsi:schemaLocation', this.schemaLocation_);
-  /** @type {ol.XmlNodeStackItem} */
-  var context = {
-    node: node,
-    'srsName': options.srsName,
-    'featureNS': options.featureNS ? options.featureNS : this.featureNS_,
-    'featurePrefix': options.featurePrefix,
-    'geometryName': options.geometryName,
-    'filter': filter,
-    'propertyNames': options.propertyNames ? options.propertyNames : []
-  };
-  ol.asserts.assert(Array.isArray(options.featureTypes),
-      11); // `options.featureTypes` should be an Array
-  ol.format.WFS.writeGetFeature_(node, /** @type {!Array.<string>} */ (options.featureTypes), [context]);
-  return node;
-};
-
-
-/**
- * Encode format as WFS `Transaction` and return the Node.
- *
- * @param {Array.<ol.Feature>} inserts The features to insert.
- * @param {Array.<ol.Feature>} updates The features to update.
- * @param {Array.<ol.Feature>} deletes The features to delete.
- * @param {olx.format.WFSWriteTransactionOptions} options Write options.
- * @return {Node} Result.
- * @api stable
- */
-ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes,
-    options) {
-  var objectStack = [];
-  var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Transaction');
-  node.setAttribute('service', 'WFS');
-  node.setAttribute('version', '1.1.0');
-  var baseObj;
-  /** @type {ol.XmlNodeStackItem} */
-  var obj;
-  if (options) {
-    baseObj = options.gmlOptions ? options.gmlOptions : {};
-    if (options.handle) {
-      node.setAttribute('handle', options.handle);
-    }
-  }
-  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
-      'xsi:schemaLocation', this.schemaLocation_);
-  if (inserts) {
-    obj = {node: node, 'featureNS': options.featureNS,
-      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
-      'srsName': options.srsName};
-    ol.obj.assign(obj, baseObj);
-    ol.xml.pushSerializeAndPop(obj,
-        ol.format.WFS.TRANSACTION_SERIALIZERS_,
-        ol.xml.makeSimpleNodeFactory('Insert'), inserts,
-        objectStack);
-  }
-  if (updates) {
-    obj = {node: node, 'featureNS': options.featureNS,
-      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
-      'srsName': options.srsName};
-    ol.obj.assign(obj, baseObj);
-    ol.xml.pushSerializeAndPop(obj,
-        ol.format.WFS.TRANSACTION_SERIALIZERS_,
-        ol.xml.makeSimpleNodeFactory('Update'), updates,
-        objectStack);
-  }
-  if (deletes) {
-    ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS,
-      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
-      'srsName': options.srsName},
-    ol.format.WFS.TRANSACTION_SERIALIZERS_,
-    ol.xml.makeSimpleNodeFactory('Delete'), deletes,
-    objectStack);
-  }
-  if (options.nativeElements) {
-    ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS,
-      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
-      'srsName': options.srsName},
-    ol.format.WFS.TRANSACTION_SERIALIZERS_,
-    ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements,
-    objectStack);
-  }
-  return node;
-};
-
-
-/**
- * Read the projection from a WFS source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @return {?ol.proj.Projection} Projection.
- * @api stable
- */
-ol.format.WFS.prototype.readProjection;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WFS.prototype.readProjectionFromDocument = function(doc) {
-  ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
-      'doc.nodeType should be a DOCUMENT');
-  for (var n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      return this.readProjectionFromNode(n);
-    }
-  }
-  return null;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.format.WFS.prototype.readProjectionFromNode = function(node) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'FeatureCollection',
-      'localName should be FeatureCollection');
-
-  if (node.firstElementChild &&
-      node.firstElementChild.firstElementChild) {
-    node = node.firstElementChild.firstElementChild;
-    for (var n = node.firstElementChild; n; n = n.nextElementSibling) {
-      if (!(n.childNodes.length === 0 ||
-          (n.childNodes.length === 1 &&
-          n.firstChild.nodeType === 3))) {
-        var objectStack = [{}];
-        this.gmlFormat_.readGeometryElement(n, objectStack);
-        return ol.proj.get(objectStack.pop().srsName);
-      }
-    }
-  }
-
-  return null;
-};
-
-goog.provide('ol.format.WKT');
-
-goog.require('ol');
-goog.require('ol.Feature');
-goog.require('ol.format.Feature');
-goog.require('ol.format.TextFeature');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-
-
-/**
- * @classdesc
- * Geometry format for reading and writing data in the `WellKnownText` (WKT)
- * format.
- *
- * @constructor
- * @extends {ol.format.TextFeature}
- * @param {olx.format.WKTOptions=} opt_options Options.
- * @api stable
- */
-ol.format.WKT = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.format.TextFeature.call(this);
-
-  /**
-   * Split GeometryCollection into multiple features.
-   * @type {boolean}
-   * @private
-   */
-  this.splitCollection_ = options.splitCollection !== undefined ?
-      options.splitCollection : false;
-
-};
-ol.inherits(ol.format.WKT, ol.format.TextFeature);
-
-
-/**
- * @const
- * @type {string}
- */
-ol.format.WKT.EMPTY = 'EMPTY';
-
-
-/**
- * @param {ol.geom.Point} geom Point geometry.
- * @return {string} Coordinates part of Point as WKT.
- * @private
- */
-ol.format.WKT.encodePointGeometry_ = function(geom) {
-  var coordinates = geom.getCoordinates();
-  if (coordinates.length === 0) {
-    return '';
-  }
-  return coordinates[0] + ' ' + coordinates[1];
-};
-
-
-/**
- * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
- * @return {string} Coordinates part of MultiPoint as WKT.
- * @private
- */
-ol.format.WKT.encodeMultiPointGeometry_ = function(geom) {
-  var array = [];
-  var components = geom.getPoints();
-  for (var i = 0, ii = components.length; i < ii; ++i) {
-    array.push('(' + ol.format.WKT.encodePointGeometry_(components[i]) + ')');
-  }
-  return array.join(',');
-};
-
-
-/**
- * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
- * @return {string} Coordinates part of GeometryCollection as WKT.
- * @private
- */
-ol.format.WKT.encodeGeometryCollectionGeometry_ = function(geom) {
-  var array = [];
-  var geoms = geom.getGeometries();
-  for (var i = 0, ii = geoms.length; i < ii; ++i) {
-    array.push(ol.format.WKT.encode_(geoms[i]));
-  }
-  return array.join(',');
-};
-
-
-/**
- * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
- * @return {string} Coordinates part of LineString as WKT.
- * @private
- */
-ol.format.WKT.encodeLineStringGeometry_ = function(geom) {
-  var coordinates = geom.getCoordinates();
-  var array = [];
-  for (var i = 0, ii = coordinates.length; i < ii; ++i) {
-    array.push(coordinates[i][0] + ' ' + coordinates[i][1]);
-  }
-  return array.join(',');
-};
-
-
-/**
- * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
- * @return {string} Coordinates part of MultiLineString as WKT.
- * @private
- */
-ol.format.WKT.encodeMultiLineStringGeometry_ = function(geom) {
-  var array = [];
-  var components = geom.getLineStrings();
-  for (var i = 0, ii = components.length; i < ii; ++i) {
-    array.push('(' + ol.format.WKT.encodeLineStringGeometry_(
-        components[i]) + ')');
-  }
-  return array.join(',');
-};
-
-
-/**
- * @param {ol.geom.Polygon} geom Polygon geometry.
- * @return {string} Coordinates part of Polygon as WKT.
- * @private
- */
-ol.format.WKT.encodePolygonGeometry_ = function(geom) {
-  var array = [];
-  var rings = geom.getLinearRings();
-  for (var i = 0, ii = rings.length; i < ii; ++i) {
-    array.push('(' + ol.format.WKT.encodeLineStringGeometry_(
-        rings[i]) + ')');
-  }
-  return array.join(',');
-};
-
-
-/**
- * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
- * @return {string} Coordinates part of MultiPolygon as WKT.
- * @private
- */
-ol.format.WKT.encodeMultiPolygonGeometry_ = function(geom) {
-  var array = [];
-  var components = geom.getPolygons();
-  for (var i = 0, ii = components.length; i < ii; ++i) {
-    array.push('(' + ol.format.WKT.encodePolygonGeometry_(
-        components[i]) + ')');
-  }
-  return array.join(',');
-};
-
-
-/**
- * Encode a geometry as WKT.
- * @param {ol.geom.Geometry} geom The geometry to encode.
- * @return {string} WKT string for the geometry.
- * @private
- */
-ol.format.WKT.encode_ = function(geom) {
-  var type = geom.getType();
-  var geometryEncoder = ol.format.WKT.GeometryEncoder_[type];
-  ol.DEBUG && console.assert(geometryEncoder, 'geometryEncoder should be defined');
-  var enc = geometryEncoder(geom);
-  type = type.toUpperCase();
-  if (enc.length === 0) {
-    return type + ' ' + ol.format.WKT.EMPTY;
-  }
-  return type + '(' + enc + ')';
-};
-
-
-/**
- * @const
- * @type {Object.<string, function(ol.geom.Geometry): string>}
- * @private
- */
-ol.format.WKT.GeometryEncoder_ = {
-  'Point': ol.format.WKT.encodePointGeometry_,
-  'LineString': ol.format.WKT.encodeLineStringGeometry_,
-  'Polygon': ol.format.WKT.encodePolygonGeometry_,
-  'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_,
-  'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_,
-  'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_,
-  'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_
-};
-
-
-/**
- * Parse a WKT string.
- * @param {string} wkt WKT string.
- * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined}
- *     The geometry created.
- * @private
- */
-ol.format.WKT.prototype.parse_ = function(wkt) {
-  var lexer = new ol.format.WKT.Lexer(wkt);
-  var parser = new ol.format.WKT.Parser(lexer);
-  return parser.parse();
-};
-
-
-/**
- * Read a feature from a WKT source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.Feature} Feature.
- * @api stable
- */
-ol.format.WKT.prototype.readFeature;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) {
-  var geom = this.readGeometryFromText(text, opt_options);
-  if (geom) {
-    var feature = new ol.Feature();
-    feature.setGeometry(geom);
-    return feature;
-  }
-  return null;
-};
-
-
-/**
- * Read all features from a WKT source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.WKT.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.readFeaturesFromText = function(text, opt_options) {
-  var geometries = [];
-  var geometry = this.readGeometryFromText(text, opt_options);
-  if (this.splitCollection_ &&
-      geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
-    geometries = (/** @type {ol.geom.GeometryCollection} */ (geometry))
-        .getGeometriesArray();
-  } else {
-    geometries = [geometry];
-  }
-  var feature, features = [];
-  for (var i = 0, ii = geometries.length; i < ii; ++i) {
-    feature = new ol.Feature();
-    feature.setGeometry(geometries[i]);
-    features.push(feature);
-  }
-  return features;
-};
-
-
-/**
- * Read a single geometry from a WKT source.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Read options.
- * @return {ol.geom.Geometry} Geometry.
- * @api stable
- */
-ol.format.WKT.prototype.readGeometry;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) {
-  var geometry = this.parse_(text);
-  if (geometry) {
-    return /** @type {ol.geom.Geometry} */ (
-        ol.format.Feature.transformWithOptions(geometry, false, opt_options));
-  } else {
-    return null;
-  }
-};
-
-
-/**
- * Encode a feature as a WKT string.
- *
- * @function
- * @param {ol.Feature} feature Feature.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} WKT string.
- * @api stable
- */
-ol.format.WKT.prototype.writeFeature;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    return this.writeGeometryText(geometry, opt_options);
-  }
-  return '';
-};
-
-
-/**
- * Encode an array of features as a WKT string.
- *
- * @function
- * @param {Array.<ol.Feature>} features Features.
- * @param {olx.format.WriteOptions=} opt_options Write options.
- * @return {string} WKT string.
- * @api stable
- */
-ol.format.WKT.prototype.writeFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.writeFeaturesText = function(features, opt_options) {
-  if (features.length == 1) {
-    return this.writeFeatureText(features[0], opt_options);
-  }
-  var geometries = [];
-  for (var i = 0, ii = features.length; i < ii; ++i) {
-    geometries.push(features[i].getGeometry());
-  }
-  var collection = new ol.geom.GeometryCollection(geometries);
-  return this.writeGeometryText(collection, opt_options);
-};
-
-
-/**
- * Write a single geometry as a WKT string.
- *
- * @function
- * @param {ol.geom.Geometry} geometry Geometry.
- * @return {string} WKT string.
- * @api stable
- */
-ol.format.WKT.prototype.writeGeometry;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) {
-  return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ (
-      ol.format.Feature.transformWithOptions(geometry, true, opt_options)));
-};
-
-
-/**
- * @const
- * @enum {number}
- */
-ol.format.WKT.TokenType = {
-  TEXT: 1,
-  LEFT_PAREN: 2,
-  RIGHT_PAREN: 3,
-  NUMBER: 4,
-  COMMA: 5,
-  EOF: 6
-};
-
-
-/**
- * Class to tokenize a WKT string.
- * @param {string} wkt WKT string.
- * @constructor
- * @protected
- */
-ol.format.WKT.Lexer = function(wkt) {
-
-  /**
-   * @type {string}
-   */
-  this.wkt = wkt;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.index_ = -1;
-};
-
-
-/**
- * @param {string} c Character.
- * @return {boolean} Whether the character is alphabetic.
- * @private
- */
-ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) {
-  return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
-};
-
-
-/**
- * @param {string} c Character.
- * @param {boolean=} opt_decimal Whether the string number
- *     contains a dot, i.e. is a decimal number.
- * @return {boolean} Whether the character is numeric.
- * @private
- */
-ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) {
-  var decimal = opt_decimal !== undefined ? opt_decimal : false;
-  return c >= '0' && c <= '9' || c == '.' && !decimal;
-};
-
-
-/**
- * @param {string} c Character.
- * @return {boolean} Whether the character is whitespace.
- * @private
- */
-ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
-  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
-};
-
-
-/**
- * @return {string} Next string character.
- * @private
- */
-ol.format.WKT.Lexer.prototype.nextChar_ = function() {
-  return this.wkt.charAt(++this.index_);
-};
-
-
-/**
- * Fetch and return the next token.
- * @return {!ol.WKTToken} Next string token.
- */
-ol.format.WKT.Lexer.prototype.nextToken = function() {
-  var c = this.nextChar_();
-  var token = {position: this.index_, value: c};
-
-  if (c == '(') {
-    token.type = ol.format.WKT.TokenType.LEFT_PAREN;
-  } else if (c == ',') {
-    token.type = ol.format.WKT.TokenType.COMMA;
-  } else if (c == ')') {
-    token.type = ol.format.WKT.TokenType.RIGHT_PAREN;
-  } else if (this.isNumeric_(c) || c == '-') {
-    token.type = ol.format.WKT.TokenType.NUMBER;
-    token.value = this.readNumber_();
-  } else if (this.isAlpha_(c)) {
-    token.type = ol.format.WKT.TokenType.TEXT;
-    token.value = this.readText_();
-  } else if (this.isWhiteSpace_(c)) {
-    return this.nextToken();
-  } else if (c === '') {
-    token.type = ol.format.WKT.TokenType.EOF;
-  } else {
-    throw new Error('Unexpected character: ' + c);
-  }
-
-  return token;
-};
-
-
-/**
- * @return {number} Numeric token value.
- * @private
- */
-ol.format.WKT.Lexer.prototype.readNumber_ = function() {
-  var c, index = this.index_;
-  var decimal = false;
-  var scientificNotation = false;
-  do {
-    if (c == '.') {
-      decimal = true;
-    } else if (c == 'e' || c == 'E') {
-      scientificNotation = true;
-    }
-    c = this.nextChar_();
-  } while (
-      this.isNumeric_(c, decimal) ||
-      // if we haven't detected a scientific number before, 'e' or 'E'
-      // hint that we should continue to read
-      !scientificNotation && (c == 'e' || c == 'E') ||
-      // once we know that we have a scientific number, both '-' and '+'
-      // are allowed
-      scientificNotation && (c == '-' || c == '+')
-  );
-  return parseFloat(this.wkt.substring(index, this.index_--));
-};
-
-
-/**
- * @return {string} String token value.
- * @private
- */
-ol.format.WKT.Lexer.prototype.readText_ = function() {
-  var c, index = this.index_;
-  do {
-    c = this.nextChar_();
-  } while (this.isAlpha_(c));
-  return this.wkt.substring(index, this.index_--).toUpperCase();
-};
-
-
-/**
- * Class to parse the tokens from the WKT string.
- * @param {ol.format.WKT.Lexer} lexer The lexer.
- * @constructor
- * @protected
- */
-ol.format.WKT.Parser = function(lexer) {
-
-  /**
-   * @type {ol.format.WKT.Lexer}
-   * @private
-   */
-  this.lexer_ = lexer;
-
-  /**
-   * @type {ol.WKTToken}
-   * @private
-   */
-  this.token_;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.dimension_ = 2;
-};
-
-
-/**
- * Fetch the next token form the lexer and replace the active token.
- * @private
- */
-ol.format.WKT.Parser.prototype.consume_ = function() {
-  this.token_ = this.lexer_.nextToken();
-};
-
-
-/**
- * If the given type matches the current token, consume it.
- * @param {ol.format.WKT.TokenType} type Token type.
- * @return {boolean} Whether the token matches the given type.
- */
-ol.format.WKT.Parser.prototype.match = function(type) {
-  var isMatch = this.token_.type == type;
-  if (isMatch) {
-    this.consume_();
-  }
-  return isMatch;
-};
-
-
-/**
- * Try to parse the tokens provided by the lexer.
- * @return {ol.geom.Geometry|ol.geom.GeometryCollection} The geometry.
- */
-ol.format.WKT.Parser.prototype.parse = function() {
-  this.consume_();
-  var geometry = this.parseGeometry_();
-  ol.DEBUG && console.assert(this.token_.type == ol.format.WKT.TokenType.EOF,
-      'token type should be end of file');
-  return geometry;
-};
-
-
-/**
- * @return {!(ol.geom.Geometry|ol.geom.GeometryCollection)} The geometry.
- * @private
- */
-ol.format.WKT.Parser.prototype.parseGeometry_ = function() {
-  var token = this.token_;
-  if (this.match(ol.format.WKT.TokenType.TEXT)) {
-    var geomType = token.value;
-    if (geomType == ol.geom.GeometryType.GEOMETRY_COLLECTION.toUpperCase()) {
-      var geometries = this.parseGeometryCollectionText_();
-      return new ol.geom.GeometryCollection(geometries);
-    } else {
-      var parser = ol.format.WKT.Parser.GeometryParser_[geomType];
-      var ctor = ol.format.WKT.Parser.GeometryConstructor_[geomType];
-      if (!parser || !ctor) {
-        throw new Error('Invalid geometry type: ' + geomType);
-      }
-      var coordinates = parser.call(this);
-      return new ctor(coordinates);
-    }
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<ol.geom.Geometry>} A collection of geometries.
- * @private
- */
-ol.format.WKT.Parser.prototype.parseGeometryCollectionText_ = function() {
-  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
-    var geometries = [];
-    do {
-      geometries.push(this.parseGeometry_());
-    } while (this.match(ol.format.WKT.TokenType.COMMA));
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return geometries;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {Array.<number>} All values in a point.
- * @private
- */
-ol.format.WKT.Parser.prototype.parsePointText_ = function() {
-  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
-    var coordinates = this.parsePoint_();
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return null;
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} All points in a linestring.
- * @private
- */
-ol.format.WKT.Parser.prototype.parseLineStringText_ = function() {
-  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
-    var coordinates = this.parsePointList_();
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} All points in a polygon.
- * @private
- */
-ol.format.WKT.Parser.prototype.parsePolygonText_ = function() {
-  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
-    var coordinates = this.parseLineStringTextList_();
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} All points in a multipoint.
- * @private
- */
-ol.format.WKT.Parser.prototype.parseMultiPointText_ = function() {
-  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
-    var coordinates;
-    if (this.token_.type == ol.format.WKT.TokenType.LEFT_PAREN) {
-      coordinates = this.parsePointTextList_();
-    } else {
-      coordinates = this.parsePointList_();
-    }
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} All linestring points
- *                                        in a multilinestring.
- * @private
- */
-ol.format.WKT.Parser.prototype.parseMultiLineStringText_ = function() {
-  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
-    var coordinates = this.parseLineStringTextList_();
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon.
- * @private
- */
-ol.format.WKT.Parser.prototype.parseMultiPolygonText_ = function() {
-  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
-    var coordinates = this.parsePolygonTextList_();
-    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
-      return coordinates;
-    }
-  } else if (this.isEmptyGeometry_()) {
-    return [];
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<number>} A point.
- * @private
- */
-ol.format.WKT.Parser.prototype.parsePoint_ = function() {
-  var coordinates = [];
-  for (var i = 0; i < this.dimension_; ++i) {
-    var token = this.token_;
-    if (this.match(ol.format.WKT.TokenType.NUMBER)) {
-      coordinates.push(token.value);
-    } else {
-      break;
-    }
-  }
-  if (coordinates.length == this.dimension_) {
-    return coordinates;
-  }
-  throw new Error(this.formatErrorMessage_());
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
- */
-ol.format.WKT.Parser.prototype.parsePointList_ = function() {
-  var coordinates = [this.parsePoint_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parsePoint_());
-  }
-  return coordinates;
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
- */
-ol.format.WKT.Parser.prototype.parsePointTextList_ = function() {
-  var coordinates = [this.parsePointText_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parsePointText_());
-  }
-  return coordinates;
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
- */
-ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() {
-  var coordinates = [this.parseLineStringText_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parseLineStringText_());
-  }
-  return coordinates;
-};
-
-
-/**
- * @return {!Array.<!Array.<number>>} An array of points.
- * @private
- */
-ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() {
-  var coordinates = [this.parsePolygonText_()];
-  while (this.match(ol.format.WKT.TokenType.COMMA)) {
-    coordinates.push(this.parsePolygonText_());
-  }
-  return coordinates;
-};
-
-
-/**
- * @return {boolean} Whether the token implies an empty geometry.
- * @private
- */
-ol.format.WKT.Parser.prototype.isEmptyGeometry_ = function() {
-  var isEmpty = this.token_.type == ol.format.WKT.TokenType.TEXT &&
-      this.token_.value == ol.format.WKT.EMPTY;
-  if (isEmpty) {
-    this.consume_();
-  }
-  return isEmpty;
-};
-
-
-/**
- * Create an error message for an unexpected token error.
- * @return {string} Error message.
- * @private
- */
-ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() {
-  return 'Unexpected `' + this.token_.value + '` at position ' +
-      this.token_.position + ' in `' + this.lexer_.wkt + '`';
-};
-
-
-/**
- * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)}
- * @private
- */
-ol.format.WKT.Parser.GeometryConstructor_ = {
-  'POINT': ol.geom.Point,
-  'LINESTRING': ol.geom.LineString,
-  'POLYGON': ol.geom.Polygon,
-  'MULTIPOINT': ol.geom.MultiPoint,
-  'MULTILINESTRING': ol.geom.MultiLineString,
-  'MULTIPOLYGON': ol.geom.MultiPolygon
-};
-
-
-/**
- * @enum {(function(): Array)}
- * @private
- */
-ol.format.WKT.Parser.GeometryParser_ = {
-  'POINT': ol.format.WKT.Parser.prototype.parsePointText_,
-  'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_,
-  'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_,
-  'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_,
-  'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_,
-  'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_
-};
-
-goog.provide('ol.format.WMSCapabilities');
-
-goog.require('ol');
-goog.require('ol.format.XLink');
-goog.require('ol.format.XML');
-goog.require('ol.format.XSD');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Format for reading WMS capabilities data
- *
- * @constructor
- * @extends {ol.format.XML}
- * @api
- */
-ol.format.WMSCapabilities = function() {
-
-  ol.format.XML.call(this);
-
-  /**
-   * @type {string|undefined}
-   */
-  this.version = undefined;
-};
-ol.inherits(ol.format.WMSCapabilities, ol.format.XML);
-
-
-/**
- * Read a WMS capabilities document.
- *
- * @function
- * @param {Document|Node|string} source The XML source.
- * @return {Object} An object representing the WMS capabilities.
- * @api
- */
-ol.format.WMSCapabilities.prototype.read;
-
-
-/**
- * @param {Document} doc Document.
- * @return {Object} WMS Capability object.
- */
-ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) {
-  ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
-      'doc.nodeType should be DOCUMENT');
-  for (var n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      return this.readFromNode(n);
-    }
-  }
-  return null;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {Object} WMS Capability object.
- */
-ol.format.WMSCapabilities.prototype.readFromNode = function(node) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'WMS_Capabilities' ||
-      node.localName == 'WMT_MS_Capabilities',
-      'localName should be WMS_Capabilities or WMT_MS_Capabilities');
-  this.version = node.getAttribute('version').trim();
-  var wmsCapabilityObject = ol.xml.pushParseAndPop({
-    'version': this.version
-  }, ol.format.WMSCapabilities.PARSERS_, node, []);
-  return wmsCapabilityObject ? wmsCapabilityObject : null;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Attribution object.
- */
-ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Attribution',
-      'localName should be Attribution');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object} Bounding box object.
- */
-ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'BoundingBox',
-      'localName should be BoundingBox');
-
-  var extent = [
-    ol.format.XSD.readDecimalString(node.getAttribute('minx')),
-    ol.format.XSD.readDecimalString(node.getAttribute('miny')),
-    ol.format.XSD.readDecimalString(node.getAttribute('maxx')),
-    ol.format.XSD.readDecimalString(node.getAttribute('maxy'))
-  ];
-
-  var resolutions = [
-    ol.format.XSD.readDecimalString(node.getAttribute('resx')),
-    ol.format.XSD.readDecimalString(node.getAttribute('resy'))
-  ];
-
-  return {
-    'crs': node.getAttribute('CRS'),
-    'extent': extent,
-    'res': resolutions
-  };
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {ol.Extent|undefined} Bounding box object.
- */
-ol.format.WMSCapabilities.readEXGeographicBoundingBox_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'EX_GeographicBoundingBox',
-      'localName should be EX_GeographicBoundingBox');
-  var geographicBoundingBox = ol.xml.pushParseAndPop(
-      {},
-      ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_,
-      node, objectStack);
-  if (!geographicBoundingBox) {
-    return undefined;
-  }
-  var westBoundLongitude = /** @type {number|undefined} */
-      (geographicBoundingBox['westBoundLongitude']);
-  var southBoundLatitude = /** @type {number|undefined} */
-      (geographicBoundingBox['southBoundLatitude']);
-  var eastBoundLongitude = /** @type {number|undefined} */
-      (geographicBoundingBox['eastBoundLongitude']);
-  var northBoundLatitude = /** @type {number|undefined} */
-      (geographicBoundingBox['northBoundLatitude']);
-  if (westBoundLongitude === undefined || southBoundLatitude === undefined ||
-      eastBoundLongitude === undefined || northBoundLatitude === undefined) {
-    return undefined;
-  }
-  return [
-    westBoundLongitude, southBoundLatitude,
-    eastBoundLongitude, northBoundLatitude
-  ];
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Capability object.
- */
-ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Capability',
-      'localName should be Capability');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Service object.
- */
-ol.format.WMSCapabilities.readService_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Service',
-      'localName should be Service');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact information object.
- */
-ol.format.WMSCapabilities.readContactInformation_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType shpuld be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ContactInformation',
-      'localName should be ContactInformation');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_,
-      node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact person object.
- */
-ol.format.WMSCapabilities.readContactPersonPrimary_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ContactPersonPrimary',
-      'localName should be ContactPersonPrimary');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_,
-      node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Contact address object.
- */
-ol.format.WMSCapabilities.readContactAddress_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'ContactAddress',
-      'localName should be ContactAddress');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_,
-      node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Array.<string>|undefined} Format array.
- */
-ol.format.WMSCapabilities.readException_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Exception',
-      'localName should be Exception');
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @private
- * @return {Object|undefined} Layer object.
- */
-ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Layer object.
- */
-ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer');
-  var parentLayerObject = /**  @type {Object.<string,*>} */
-      (objectStack[objectStack.length - 1]);
-
-  var layerObject = ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
-
-  if (!layerObject) {
-    return undefined;
-  }
-  var queryable =
-      ol.format.XSD.readBooleanString(node.getAttribute('queryable'));
-  if (queryable === undefined) {
-    queryable = parentLayerObject['queryable'];
-  }
-  layerObject['queryable'] = queryable !== undefined ? queryable : false;
-
-  var cascaded = ol.format.XSD.readNonNegativeIntegerString(
-      node.getAttribute('cascaded'));
-  if (cascaded === undefined) {
-    cascaded = parentLayerObject['cascaded'];
-  }
-  layerObject['cascaded'] = cascaded;
-
-  var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
-  if (opaque === undefined) {
-    opaque = parentLayerObject['opaque'];
-  }
-  layerObject['opaque'] = opaque !== undefined ? opaque : false;
-
-  var noSubsets =
-      ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
-  if (noSubsets === undefined) {
-    noSubsets = parentLayerObject['noSubsets'];
-  }
-  layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false;
-
-  var fixedWidth =
-      ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
-  if (!fixedWidth) {
-    fixedWidth = parentLayerObject['fixedWidth'];
-  }
-  layerObject['fixedWidth'] = fixedWidth;
-
-  var fixedHeight =
-      ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
-  if (!fixedHeight) {
-    fixedHeight = parentLayerObject['fixedHeight'];
-  }
-  layerObject['fixedHeight'] = fixedHeight;
-
-  // See 7.2.4.8
-  var addKeys = ['Style', 'CRS', 'AuthorityURL'];
-  addKeys.forEach(function(key) {
-    if (key in parentLayerObject) {
-      var childValue = layerObject[key] || [];
-      layerObject[key] = childValue.concat(parentLayerObject[key]);
-    }
-  });
-
-  var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
-    'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'];
-  replaceKeys.forEach(function(key) {
-    if (!(key in layerObject)) {
-      var parentValue = parentLayerObject[key];
-      layerObject[key] = parentValue;
-    }
-  });
-
-  return layerObject;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object} Dimension object.
- */
-ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Dimension',
-      'localName should be Dimension');
-  var dimensionObject = {
-    'name': node.getAttribute('name'),
-    'units': node.getAttribute('units'),
-    'unitSymbol': node.getAttribute('unitSymbol'),
-    'default': node.getAttribute('default'),
-    'multipleValues': ol.format.XSD.readBooleanString(
-        node.getAttribute('multipleValues')),
-    'nearestValue': ol.format.XSD.readBooleanString(
-        node.getAttribute('nearestValue')),
-    'current': ol.format.XSD.readBooleanString(node.getAttribute('current')),
-    'values': ol.format.XSD.readString(node)
-  };
-  return dimensionObject;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
- */
-ol.format.WMSCapabilities.readFormatOnlineresource_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_,
-      node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Request object.
- */
-ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Request',
-      'localName should be Request');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} DCP type object.
- */
-ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'DCPType',
-      'localName should be DCPType');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} HTTP object.
- */
-ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Operation type object.
- */
-ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Online resource object.
- */
-ol.format.WMSCapabilities.readSizedFormatOnlineresource_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  var formatOnlineresource =
-      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
-  if (formatOnlineresource) {
-    var size = [
-      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')),
-      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height'))
-    ];
-    formatOnlineresource['size'] = size;
-    return formatOnlineresource;
-  }
-  return undefined;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Authority URL object.
- */
-ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'AuthorityURL',
-      'localName should be AuthorityURL');
-  var authorityObject =
-      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
-  if (authorityObject) {
-    authorityObject['name'] = node.getAttribute('name');
-    return authorityObject;
-  }
-  return undefined;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Metadata URL object.
- */
-ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'MetadataURL',
-      'localName should be MetadataURL');
-  var metadataObject =
-      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
-  if (metadataObject) {
-    metadataObject['type'] = node.getAttribute('type');
-    return metadataObject;
-  }
-  return undefined;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Style object.
- */
-ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style');
-  return ol.xml.pushParseAndPop(
-      {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<string>|undefined} Keyword list.
- */
-ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'KeywordList',
-      'localName should be KeywordList');
-  return ol.xml.pushParseAndPop(
-      [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @const
- * @private
- * @type {Array.<string>}
- */
-ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/wms'
-];
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Service': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readService_),
-      'Capability': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readCapability_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Request': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readRequest_),
-      'Exception': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readException_),
-      'Layer': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readCapabilityLayer_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'KeywordList': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readKeywordList_),
-      'OnlineResource': ol.xml.makeObjectPropertySetter(
-          ol.format.XLink.readHref),
-      'ContactInformation': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readContactInformation_),
-      'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'AccessConstraints': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'LayerLimit': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger),
-      'MaxWidth': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger),
-      'MaxHeight': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'ContactPersonPrimary': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readContactPersonPrimary_),
-      'ContactPosition': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'ContactAddress': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readContactAddress_),
-      'ContactVoiceTelephone': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'ContactFacsimileTelephone': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'ContactElectronicMailAddress': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'ContactPerson': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'ContactOrganization': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'StateOrProvince': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'KeywordList': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readKeywordList_),
-      'CRS': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
-      'EX_GeographicBoundingBox': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readEXGeographicBoundingBox_),
-      'BoundingBox': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readBoundingBox_),
-      'Dimension': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readDimension_),
-      'Attribution': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readAttribution_),
-      'AuthorityURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readAuthorityURL_),
-      'Identifier': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
-      'MetadataURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readMetadataURL_),
-      'DataURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'FeatureListURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'Style': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readStyle_),
-      'MinScaleDenominator': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'MaxScaleDenominator': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'Layer': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readLayer_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'OnlineResource': ol.xml.makeObjectPropertySetter(
-          ol.format.XLink.readHref),
-      'LogoURL': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readSizedFormatOnlineresource_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ =
-    ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'westBoundLongitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'eastBoundLongitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'southBoundLatitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'northBoundLatitude': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'GetCapabilities': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readOperationType_),
-      'GetMap': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readOperationType_),
-      'GetFeatureInfo': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readOperationType_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
-      'DCPType': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readDCPType_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'HTTP': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readHTTP_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Get': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'Post': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'LegendURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMSCapabilities.readSizedFormatOnlineresource_),
-      'StyleSheetURL': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_),
-      'StyleURL': ol.xml.makeObjectPropertySetter(
-          ol.format.WMSCapabilities.readFormatOnlineresource_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ =
-    ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
-      'OnlineResource': ol.xml.makeObjectPropertySetter(
-          ol.format.XLink.readHref)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
-      'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString)
-    });
-
-goog.provide('ol.format.WMSGetFeatureInfo');
-
-goog.require('ol');
-goog.require('ol.array');
-goog.require('ol.format.GML2');
-goog.require('ol.format.XMLFeature');
-goog.require('ol.obj');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Format for reading WMSGetFeatureInfo format. It uses
- * {@link ol.format.GML2} to read features.
- *
- * @constructor
- * @extends {ol.format.XMLFeature}
- * @param {olx.format.WMSGetFeatureInfoOptions=} opt_options Options.
- * @api
- */
-ol.format.WMSGetFeatureInfo = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver';
-
-
-  /**
-   * @private
-   * @type {ol.format.GML2}
-   */
-  this.gmlFormat_ = new ol.format.GML2();
-
-
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.layers_ = options.layers ? options.layers : null;
-
-  ol.format.XMLFeature.call(this);
-};
-ol.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature);
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';
-
-
-/**
- * @const
- * @type {string}
- * @private
- */
-ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';
-
-
-/**
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Array.<ol.Feature>} Features.
- * @private
- */
-ol.format.WMSGetFeatureInfo.prototype.readFeatures_ = function(node, objectStack) {
-
-  node.setAttribute('namespaceURI', this.featureNS_);
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  var localName = node.localName;
-  /** @type {Array.<ol.Feature>} */
-  var features = [];
-  if (node.childNodes.length === 0) {
-    return features;
-  }
-  if (localName == 'msGMLOutput') {
-    for (var i = 0, ii = node.childNodes.length; i < ii; i++) {
-      var layer = node.childNodes[i];
-      if (layer.nodeType !== Node.ELEMENT_NODE) {
-        continue;
-      }
-      var context = objectStack[0];
-
-      ol.DEBUG && console.assert(layer.localName.indexOf(
-          ol.format.WMSGetFeatureInfo.layerIdentifier_) >= 0,
-          'localName of layer node should match layerIdentifier');
-
-      var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_;
-      var layerName = layer.localName.replace(toRemove, '');
-
-      if (this.layers_ && !ol.array.includes(this.layers_, layerName)) {
-        continue;
-      }
-
-      var featureType = layerName +
-          ol.format.WMSGetFeatureInfo.featureIdentifier_;
-
-      context['featureType'] = featureType;
-      context['featureNS'] = this.featureNS_;
-
-      var parsers = {};
-      parsers[featureType] = ol.xml.makeArrayPusher(
-          this.gmlFormat_.readFeatureElement, this.gmlFormat_);
-      var parsersNS = ol.xml.makeStructureNS(
-          [context['featureNS'], null], parsers);
-      layer.setAttribute('namespaceURI', this.featureNS_);
-      var layerFeatures = ol.xml.pushParseAndPop(
-          [], parsersNS, layer, objectStack, this.gmlFormat_);
-      if (layerFeatures) {
-        ol.array.extend(features, layerFeatures);
-      }
-    }
-  }
-  if (localName == 'FeatureCollection') {
-    var gmlFeatures = ol.xml.pushParseAndPop([],
-        this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
-        [{}], this.gmlFormat_);
-    if (gmlFeatures) {
-      features = gmlFeatures;
-    }
-  }
-  return features;
-};
-
-
-/**
- * Read all features from a WMSGetFeatureInfo response.
- *
- * @function
- * @param {Document|Node|Object|string} source Source.
- * @param {olx.format.ReadOptions=} opt_options Options.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.format.WMSGetFeatureInfo.prototype.readFeatures;
-
-
-/**
- * @inheritDoc
- */
-ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = function(node, opt_options) {
-  var options = {};
-  if (opt_options) {
-    ol.obj.assign(options, this.getReadOptions(node, opt_options));
-  }
-  return this.readFeatures_(node, [options]);
-};
-
-goog.provide('ol.format.WMTSCapabilities');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.format.OWS');
-goog.require('ol.format.XLink');
-goog.require('ol.format.XML');
-goog.require('ol.format.XSD');
-goog.require('ol.xml');
-
-
-/**
- * @classdesc
- * Format for reading WMTS capabilities data.
- *
- * @constructor
- * @extends {ol.format.XML}
- * @api
- */
-ol.format.WMTSCapabilities = function() {
-  ol.format.XML.call(this);
-
-  /**
-   * @type {ol.format.OWS}
-   * @private
-   */
-  this.owsParser_ = new ol.format.OWS();
-};
-ol.inherits(ol.format.WMTSCapabilities, ol.format.XML);
-
-
-/**
- * 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) {
-  ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
-      'doc.nodeType should be DOCUMENT');
-  for (var n = doc.firstChild; n; n = n.nextSibling) {
-    if (n.nodeType == Node.ELEMENT_NODE) {
-      return this.readFromNode(n);
-    }
-  }
-  return null;
-};
-
-
-/**
- * @param {Node} node Node.
- * @return {Object} WMTS Capability object.
- */
-ol.format.WMTSCapabilities.prototype.readFromNode = function(node) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Capabilities',
-      'localName should be Capabilities');
-  var version = node.getAttribute('version').trim();
-  var WMTSCapabilityObject = this.owsParser_.readFromNode(node);
-  if (!WMTSCapabilityObject) {
-    return null;
-  }
-  WMTSCapabilityObject['version'] = version;
-  WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject,
-      ol.format.WMTSCapabilities.PARSERS_, node, []);
-  return WMTSCapabilityObject ? WMTSCapabilityObject : null;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Attribution object.
- */
-ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Contents',
-      'localName should be Contents');
-
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Layers object.
- */
-ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) {
-  ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
-      'node.nodeType should be ELEMENT');
-  ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer');
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Tile Matrix Set object.
- */
-ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Style object.
- */
-ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) {
-  var style = ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack);
-  if (!style) {
-    return undefined;
-  }
-  var isDefault = node.getAttribute('isDefault') === 'true';
-  style['isDefault'] = isDefault;
-  return style;
-
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Tile Matrix Set Link object.
- */
-ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
-    objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Dimension object.
- */
-ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Resource URL object.
- */
-ol.format.WMTSCapabilities.readResourceUrl_ = function(node, objectStack) {
-  var format = node.getAttribute('format');
-  var template = node.getAttribute('template');
-  var resourceType = node.getAttribute('resourceType');
-  var resource = {};
-  if (format) {
-    resource['format'] = format;
-  }
-  if (template) {
-    resource['template'] = template;
-  }
-  if (resourceType) {
-    resource['resourceType'] = resourceType;
-  }
-  return resource;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} WGS84 BBox object.
- */
-ol.format.WMTSCapabilities.readWgs84BoundingBox_ = function(node, objectStack) {
-  var coordinates = ol.xml.pushParseAndPop([],
-      ol.format.WMTSCapabilities.WGS84_BBOX_READERS_, node, objectStack);
-  if (coordinates.length != 2) {
-    return undefined;
-  }
-  return ol.extent.boundingExtent(coordinates);
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Legend object.
- */
-ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
-  var legend = {};
-  legend['format'] = node.getAttribute('format');
-  legend['href'] = ol.format.XLink.readHref(node);
-  return legend;
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} Coordinates object.
- */
-ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) {
-  var coordinates = ol.format.XSD.readString(node).split(' ');
-  if (!coordinates || coordinates.length != 2) {
-    return undefined;
-  }
-  var x = +coordinates[0];
-  var y = +coordinates[1];
-  if (isNaN(x) || isNaN(y)) {
-    return undefined;
-  }
-  return [x, y];
-};
-
-
-/**
- * @private
- * @param {Node} node Node.
- * @param {Array.<*>} objectStack Object stack.
- * @return {Object|undefined} TileMatrix object.
- */
-ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
-  return ol.xml.pushParseAndPop({},
-      ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
-};
-
-
-/**
- * @const
- * @private
- * @type {Array.<string>}
- */
-ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/wmts/1.0'
-];
-
-
-/**
- * @const
- * @private
- * @type {Array.<string>}
- */
-ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
-  null,
-  'http://www.opengis.net/ows/1.1'
-];
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'Contents': ol.xml.makeObjectPropertySetter(
-          ol.format.WMTSCapabilities.readContents_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.CONTENTS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'Layer': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readLayer_),
-      'TileMatrixSet': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readTileMatrixSet_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'Style': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readStyle_),
-      'Format': ol.xml.makeObjectPropertyPusher(
-          ol.format.XSD.readString),
-      'TileMatrixSetLink': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readTileMatrixSetLink_),
-      'Dimension': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readDimensions_),
-      'ResourceURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readResourceUrl_)
-    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'Abstract': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'WGS84BoundingBox': ol.xml.makeObjectPropertySetter(
-          ol.format.WMTSCapabilities.readWgs84BoundingBox_),
-      'Identifier': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    }));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'LegendURL': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readLegendUrl_)
-    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'Title': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'Identifier': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    }));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'TileMatrixSet': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.DIMENSION_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'Default': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'Value': ol.xml.makeObjectPropertyPusher(
-          ol.format.XSD.readString)
-    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'Identifier': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    }));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.WGS84_BBOX_READERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'LowerCorner': ol.xml.makeArrayPusher(
-          ol.format.WMTSCapabilities.readCoordinates_),
-      'UpperCorner': ol.xml.makeArrayPusher(
-          ol.format.WMTSCapabilities.readCoordinates_)
-    });
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.TMS_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'WellKnownScaleSet': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'TileMatrix': ol.xml.makeObjectPropertyPusher(
-          ol.format.WMTSCapabilities.readTileMatrix_)
-    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'SupportedCRS': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString),
-      'Identifier': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    }));
-
-
-/**
- * @const
- * @type {Object.<string, Object.<string, ol.XmlParser>>}
- * @private
- */
-ol.format.WMTSCapabilities.TM_PARSERS_ = ol.xml.makeStructureNS(
-    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
-      'TopLeftCorner': ol.xml.makeObjectPropertySetter(
-          ol.format.WMTSCapabilities.readCoordinates_),
-      'ScaleDenominator': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readDecimal),
-      'TileWidth': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger),
-      'TileHeight': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger),
-      'MatrixWidth': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger),
-      'MatrixHeight': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readNonNegativeInteger)
-    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
-      'Identifier': ol.xml.makeObjectPropertySetter(
-          ol.format.XSD.readString)
-    }));
-
-// FIXME handle geolocation not supported
-
-goog.provide('ol.Geolocation');
-
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.geom.Polygon');
-goog.require('ol.has');
-goog.require('ol.math');
-goog.require('ol.proj');
-goog.require('ol.sphere.WGS84');
-
-
-/**
- * @classdesc
- * Helper class for providing HTML5 Geolocation capabilities.
- * The [Geolocation API](http://www.w3.org/TR/geolocation-API/)
- * is used to locate a user's position.
- *
- * To get notified of position changes, register a listener for the generic
- * `change` event on your instance of `ol.Geolocation`.
- *
- * Example:
- *
- *     var geolocation = new ol.Geolocation({
- *       // take the projection to use from the map's view
- *       projection: view.getProjection()
- *     });
- *     // listen to changes in position
- *     geolocation.on('change', function(evt) {
- *       window.console.log(geolocation.getPosition());
- *     });
- *
- * @fires error
- * @constructor
- * @extends {ol.Object}
- * @param {olx.GeolocationOptions=} opt_options Options.
- * @api stable
- */
-ol.Geolocation = function(opt_options) {
-
-  ol.Object.call(this);
-
-  var options = opt_options || {};
-
-  /**
-   * The unprojected (EPSG:4326) device position.
-   * @private
-   * @type {ol.Coordinate}
-   */
-  this.position_ = null;
-
-  /**
-   * @private
-   * @type {ol.TransformFunction}
-   */
-  this.transform_ = ol.proj.identityTransform;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.watchId_ = undefined;
-
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Geolocation.Property.PROJECTION),
-      this.handleProjectionChanged_, this);
-  ol.events.listen(
-      this, ol.Object.getChangeEventType(ol.Geolocation.Property.TRACKING),
-      this.handleTrackingChanged_, this);
-
-  if (options.projection !== undefined) {
-    this.setProjection(ol.proj.get(options.projection));
-  }
-  if (options.trackingOptions !== undefined) {
-    this.setTrackingOptions(options.trackingOptions);
-  }
-
-  this.setTracking(options.tracking !== undefined ? options.tracking : false);
-
-};
-ol.inherits(ol.Geolocation, ol.Object);
-
-
-/**
- * @inheritDoc
- */
-ol.Geolocation.prototype.disposeInternal = function() {
-  this.setTracking(false);
-  ol.Object.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * @private
- */
-ol.Geolocation.prototype.handleProjectionChanged_ = function() {
-  var projection = this.getProjection();
-  if (projection) {
-    this.transform_ = ol.proj.getTransformFromProjections(
-        ol.proj.get('EPSG:4326'), projection);
-    if (this.position_) {
-      this.set(
-          ol.Geolocation.Property.POSITION, this.transform_(this.position_));
-    }
-  }
-};
-
-
-/**
- * @private
- */
-ol.Geolocation.prototype.handleTrackingChanged_ = function() {
-  if (ol.has.GEOLOCATION) {
-    var tracking = this.getTracking();
-    if (tracking && this.watchId_ === undefined) {
-      this.watchId_ = navigator.geolocation.watchPosition(
-          this.positionChange_.bind(this),
-          this.positionError_.bind(this),
-          this.getTrackingOptions());
-    } else if (!tracking && this.watchId_ !== undefined) {
-      navigator.geolocation.clearWatch(this.watchId_);
-      this.watchId_ = undefined;
-    }
-  }
-};
-
-
-/**
- * @private
- * @param {GeolocationPosition} position position event.
- */
-ol.Geolocation.prototype.positionChange_ = function(position) {
-  var coords = position.coords;
-  this.set(ol.Geolocation.Property.ACCURACY, coords.accuracy);
-  this.set(ol.Geolocation.Property.ALTITUDE,
-      coords.altitude === null ? undefined : coords.altitude);
-  this.set(ol.Geolocation.Property.ALTITUDE_ACCURACY,
-      coords.altitudeAccuracy === null ?
-      undefined : coords.altitudeAccuracy);
-  this.set(ol.Geolocation.Property.HEADING, coords.heading === null ?
-      undefined : ol.math.toRadians(coords.heading));
-  if (!this.position_) {
-    this.position_ = [coords.longitude, coords.latitude];
-  } else {
-    this.position_[0] = coords.longitude;
-    this.position_[1] = coords.latitude;
-  }
-  var projectedPosition = this.transform_(this.position_);
-  this.set(ol.Geolocation.Property.POSITION, projectedPosition);
-  this.set(ol.Geolocation.Property.SPEED,
-      coords.speed === null ? undefined : coords.speed);
-  var geometry = ol.geom.Polygon.circular(
-      ol.sphere.WGS84, this.position_, coords.accuracy);
-  geometry.applyTransform(this.transform_);
-  this.set(ol.Geolocation.Property.ACCURACY_GEOMETRY, geometry);
-  this.changed();
-};
-
-/**
- * Triggered when the Geolocation returns an error.
- * @event error
- * @api
- */
-
-/**
- * @private
- * @param {GeolocationPositionError} error error object.
- */
-ol.Geolocation.prototype.positionError_ = function(error) {
-  error.type = ol.events.EventType.ERROR;
-  this.setTracking(false);
-  this.dispatchEvent(/** @type {{type: string, target: undefined}} */ (error));
-};
-
-
-/**
- * Get the accuracy of the position in meters.
- * @return {number|undefined} The accuracy of the position measurement in
- *     meters.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.Geolocation.Property.ACCURACY));
-};
-
-
-/**
- * Get a geometry of the position accuracy.
- * @return {?ol.geom.Geometry} A geometry of the position accuracy.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getAccuracyGeometry = function() {
-  return /** @type {?ol.geom.Geometry} */ (
-      this.get(ol.Geolocation.Property.ACCURACY_GEOMETRY) || null);
-};
-
-
-/**
- * Get the altitude associated with the position.
- * @return {number|undefined} The altitude of the position in meters above mean
- *     sea level.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getAltitude = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.Geolocation.Property.ALTITUDE));
-};
-
-
-/**
- * Get the altitude accuracy of the position.
- * @return {number|undefined} The accuracy of the altitude measurement in
- *     meters.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getAltitudeAccuracy = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.Geolocation.Property.ALTITUDE_ACCURACY));
-};
-
-
-/**
- * Get the heading as radians clockwise from North.
- * @return {number|undefined} The heading of the device in radians from north.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getHeading = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.Geolocation.Property.HEADING));
-};
-
-
-/**
- * Get the position of the device.
- * @return {ol.Coordinate|undefined} The current position of the device reported
- *     in the current projection.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getPosition = function() {
-  return /** @type {ol.Coordinate|undefined} */ (
-      this.get(ol.Geolocation.Property.POSITION));
-};
-
-
-/**
- * Get the projection associated with the position.
- * @return {ol.proj.Projection|undefined} The projection the position is
- *     reported in.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getProjection = function() {
-  return /** @type {ol.proj.Projection|undefined} */ (
-      this.get(ol.Geolocation.Property.PROJECTION));
-};
-
-
-/**
- * Get the speed in meters per second.
- * @return {number|undefined} The instantaneous speed of the device in meters
- *     per second.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getSpeed = function() {
-  return /** @type {number|undefined} */ (
-      this.get(ol.Geolocation.Property.SPEED));
-};
-
-
-/**
- * Determine if the device location is being tracked.
- * @return {boolean} The device location is being tracked.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getTracking = function() {
-  return /** @type {boolean} */ (
-      this.get(ol.Geolocation.Property.TRACKING));
-};
-
-
-/**
- * Get the tracking options.
- * @see http://www.w3.org/TR/geolocation-API/#position-options
- * @return {GeolocationPositionOptions|undefined} PositionOptions as defined by
- *     the [HTML5 Geolocation spec
- *     ](http://www.w3.org/TR/geolocation-API/#position_options_interface).
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.getTrackingOptions = function() {
-  return /** @type {GeolocationPositionOptions|undefined} */ (
-      this.get(ol.Geolocation.Property.TRACKING_OPTIONS));
-};
-
-
-/**
- * Set the projection to use for transforming the coordinates.
- * @param {ol.proj.Projection} projection The projection the position is
- *     reported in.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.setProjection = function(projection) {
-  this.set(ol.Geolocation.Property.PROJECTION, projection);
-};
-
-
-/**
- * Enable or disable tracking.
- * @param {boolean} tracking Enable tracking.
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.setTracking = function(tracking) {
-  this.set(ol.Geolocation.Property.TRACKING, tracking);
-};
-
-
-/**
- * Set the tracking options.
- * @see http://www.w3.org/TR/geolocation-API/#position-options
- * @param {GeolocationPositionOptions} options PositionOptions as defined by the
- *     [HTML5 Geolocation spec
- *     ](http://www.w3.org/TR/geolocation-API/#position_options_interface).
- * @observable
- * @api stable
- */
-ol.Geolocation.prototype.setTrackingOptions = function(options) {
-  this.set(ol.Geolocation.Property.TRACKING_OPTIONS, options);
-};
-
-
-/**
- * @enum {string}
- */
-ol.Geolocation.Property = {
-  ACCURACY: 'accuracy',
-  ACCURACY_GEOMETRY: 'accuracyGeometry',
-  ALTITUDE: 'altitude',
-  ALTITUDE_ACCURACY: 'altitudeAccuracy',
-  HEADING: 'heading',
-  POSITION: 'position',
-  PROJECTION: 'projection',
-  SPEED: 'speed',
-  TRACKING: 'tracking',
-  TRACKING_OPTIONS: 'trackingOptions'
-};
-
-goog.provide('ol.geom.Circle');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.geom.flat.deflate');
-
-
-/**
- * @classdesc
- * Circle geometry.
- *
- * @constructor
- * @extends {ol.geom.SimpleGeometry}
- * @param {ol.Coordinate} center Center.
- * @param {number=} opt_radius Radius.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api
- */
-ol.geom.Circle = function(center, opt_radius, opt_layout) {
-  ol.geom.SimpleGeometry.call(this);
-  var radius = opt_radius ? opt_radius : 0;
-  this.setCenterAndRadius(center, radius, opt_layout);
-};
-ol.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);
-
-
-/**
- * Make a complete copy of the geometry.
- * @return {!ol.geom.Circle} Clone.
- * @api
- */
-ol.geom.Circle.prototype.clone = function() {
-  var circle = new ol.geom.Circle(null);
-  circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
-  return circle;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Circle.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
-  var flatCoordinates = this.flatCoordinates;
-  var dx = x - flatCoordinates[0];
-  var dy = y - flatCoordinates[1];
-  var squaredDistance = dx * dx + dy * dy;
-  if (squaredDistance < minSquaredDistance) {
-    var i;
-    if (squaredDistance === 0) {
-      for (i = 0; i < this.stride; ++i) {
-        closestPoint[i] = flatCoordinates[i];
-      }
-    } else {
-      var delta = this.getRadius() / Math.sqrt(squaredDistance);
-      closestPoint[0] = flatCoordinates[0] + delta * dx;
-      closestPoint[1] = flatCoordinates[1] + delta * dy;
-      for (i = 2; i < this.stride; ++i) {
-        closestPoint[i] = flatCoordinates[i];
-      }
-    }
-    closestPoint.length = this.stride;
-    return squaredDistance;
-  } else {
-    return minSquaredDistance;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Circle.prototype.containsXY = function(x, y) {
-  var flatCoordinates = this.flatCoordinates;
-  var dx = x - flatCoordinates[0];
-  var dy = y - flatCoordinates[1];
-  return dx * dx + dy * dy <= this.getRadiusSquared_();
-};
-
-
-/**
- * Return the center of the circle as {@link ol.Coordinate coordinate}.
- * @return {ol.Coordinate} Center.
- * @api
- */
-ol.geom.Circle.prototype.getCenter = function() {
-  return this.flatCoordinates.slice(0, this.stride);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.geom.Circle.prototype.computeExtent = function(extent) {
-  var flatCoordinates = this.flatCoordinates;
-  var radius = flatCoordinates[this.stride] - flatCoordinates[0];
-  return ol.extent.createOrUpdate(
-      flatCoordinates[0] - radius, flatCoordinates[1] - radius,
-      flatCoordinates[0] + radius, flatCoordinates[1] + radius,
-      extent);
-};
-
-
-/**
- * Return the radius of the circle.
- * @return {number} Radius.
- * @api
- */
-ol.geom.Circle.prototype.getRadius = function() {
-  return Math.sqrt(this.getRadiusSquared_());
-};
-
-
-/**
- * @private
- * @return {number} Radius squared.
- */
-ol.geom.Circle.prototype.getRadiusSquared_ = function() {
-  var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0];
-  var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1];
-  return dx * dx + dy * dy;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.geom.Circle.prototype.getType = function() {
-  return ol.geom.GeometryType.CIRCLE;
-};
-
-
-/**
- * @inheritDoc
- * @api stable
- */
-ol.geom.Circle.prototype.intersectsExtent = function(extent) {
-  var circleExtent = this.getExtent();
-  if (ol.extent.intersects(extent, circleExtent)) {
-    var center = this.getCenter();
-
-    if (extent[0] <= center[0] && extent[2] >= center[0]) {
-      return true;
-    }
-    if (extent[1] <= center[1] && extent[3] >= center[1]) {
-      return true;
-    }
-
-    return ol.extent.forEachCorner(extent, this.intersectsCoordinate, this);
-  }
-  return false;
-
-};
-
-
-/**
- * Set the center of the circle as {@link ol.Coordinate coordinate}.
- * @param {ol.Coordinate} center Center.
- * @api
- */
-ol.geom.Circle.prototype.setCenter = function(center) {
-  var stride = this.stride;
-  ol.DEBUG && console.assert(center.length == stride,
-      'center array length should match stride');
-  var radius = this.flatCoordinates[stride] - this.flatCoordinates[0];
-  var flatCoordinates = center.slice();
-  flatCoordinates[stride] = flatCoordinates[0] + radius;
-  var i;
-  for (i = 1; i < stride; ++i) {
-    flatCoordinates[stride + i] = center[i];
-  }
-  this.setFlatCoordinates(this.layout, flatCoordinates);
-};
-
-
-/**
- * Set the center (as {@link ol.Coordinate coordinate}) and the radius (as
- * number) of the circle.
- * @param {ol.Coordinate} center Center.
- * @param {number} radius Radius.
- * @param {ol.geom.GeometryLayout=} opt_layout Layout.
- * @api
- */
-ol.geom.Circle.prototype.setCenterAndRadius = function(center, radius, opt_layout) {
-  if (!center) {
-    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
-  } else {
-    this.setLayout(opt_layout, center, 0);
-    if (!this.flatCoordinates) {
-      this.flatCoordinates = [];
-    }
-    /** @type {Array.<number>} */
-    var flatCoordinates = this.flatCoordinates;
-    var offset = ol.geom.flat.deflate.coordinate(
-        flatCoordinates, 0, center, this.stride);
-    flatCoordinates[offset++] = flatCoordinates[0] + radius;
-    var i, ii;
-    for (i = 1, ii = this.stride; i < ii; ++i) {
-      flatCoordinates[offset++] = flatCoordinates[i];
-    }
-    flatCoordinates.length = offset;
-    this.changed();
-  }
-};
-
-
-/**
- * @param {ol.geom.GeometryLayout} layout Layout.
- * @param {Array.<number>} flatCoordinates Flat coordinates.
- */
-ol.geom.Circle.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
-  this.setFlatCoordinatesInternal(layout, flatCoordinates);
-  this.changed();
-};
-
-
-/**
- * Set the radius of the circle. The radius is in the units of the projection.
- * @param {number} radius Radius.
- * @api
- */
-ol.geom.Circle.prototype.setRadius = function(radius) {
-  this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
-  this.changed();
-};
-
-
-/**
- * Transform each coordinate of the circle from one coordinate reference system
- * to another. The geometry is modified in place.
- * If you do not want the geometry modified in place, first clone() it and
- * then use this function on the clone.
- *
- * Internally a circle is currently represented by two points: the center of
- * the circle `[cx, cy]`, and the point to the right of the circle
- * `[cx + r, cy]`. This `transform` function just transforms these two points.
- * So the resulting geometry is also a circle, and that circle does not
- * correspond to the shape that would be obtained by transforming every point
- * of the original circle.
- *
- * @param {ol.ProjectionLike} source The current projection.  Can be a
- *     string identifier or a {@link ol.proj.Projection} object.
- * @param {ol.ProjectionLike} destination The desired projection.  Can be a
- *     string identifier or a {@link ol.proj.Projection} object.
- * @return {ol.geom.Circle} This geometry.  Note that original geometry is
- *     modified in place.
- * @function
- * @api stable
- */
-ol.geom.Circle.prototype.transform;
-
-goog.provide('ol.geom.flat.geodesic');
-
-goog.require('ol');
-goog.require('ol.math');
-goog.require('ol.proj');
-
-
-/**
- * @private
- * @param {function(number): ol.Coordinate} interpolate Interpolate function.
- * @param {ol.TransformFunction} transform Transform from longitude/latitude to
- *     projected coordinates.
- * @param {number} squaredTolerance Squared tolerance.
- * @return {Array.<number>} Flat coordinates.
- */
-ol.geom.flat.geodesic.line_ = function(interpolate, transform, squaredTolerance) {
-  // FIXME reduce garbage generation
-  // FIXME optimize stack operations
-
-  /** @type {Array.<number>} */
-  var flatCoordinates = [];
-
-  var geoA = interpolate(0);
-  var geoB = interpolate(1);
-
-  var a = transform(geoA);
-  var b = transform(geoB);
-
-  /** @type {Array.<ol.Coordinate>} */
-  var geoStack = [geoB, geoA];
-  /** @type {Array.<ol.Coordinate>} */
-  var stack = [b, a];
-  /** @type {Array.<number>} */
-  var fractionStack = [1, 0];
-
-  /** @type {Object.<string, boolean>} */
-  var fractions = {};
-
-  var maxIterations = 1e5;
-  var geoM, m, fracA, fracB, fracM, key;
-
-  while (--maxIterations > 0 && fractionStack.length > 0) {
-    // Pop the a coordinate off the stack
-    fracA = fractionStack.pop();
-    geoA = geoStack.pop();
-    a = stack.pop();
-    // Add the a coordinate if it has not been added yet
-    key = fracA.toString();
-    if (!(key in fractions)) {
-      flatCoordinates.push(a[0], a[1]);
-      fractions[key] = true;
-    }
-    // Pop the b coordinate off the stack
-    fracB = fractionStack.pop();
-    geoB = geoStack.pop();
-    b = stack.pop();
-    // Find the m point between the a and b coordinates
-    fracM = (fracA + fracB) / 2;
-    geoM = interpolate(fracM);
-    m = transform(geoM);
-    if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1],
-        b[0], b[1]) < squaredTolerance) {
-      // If the m point is sufficiently close to the straight line, then we
-      // discard it.  Just use the b coordinate and move on to the next line
-      // segment.
-      flatCoordinates.push(b[0], b[1]);
-      key = fracB.toString();
-      ol.DEBUG && console.assert(!(key in fractions),
-          'fractions object should contain key : ' + key);
-      fractions[key] = true;
-    } else {
-      // Otherwise, we need to subdivide the current line segment.  Split it
-      // into two and push the two line segments onto the stack.
-      fractionStack.push(fracB, fracM, fracM, fracA);
-      stack.push(b, m, m, a);
-      geoStack.push(geoB, geoM, geoM, geoA);
-    }
-  }
-  ol.DEBUG && console.assert(maxIterations > 0,
-      'maxIterations should be more than 0');
-
-  return flatCoordinates;
-};
-
-
-/**
-* Generate a great-circle arcs between two lat/lon points.
-* @param {number} lon1 Longitude 1 in degrees.
-* @param {number} lat1 Latitude 1 in degrees.
-* @param {number} lon2 Longitude 2 in degrees.
-* @param {number} lat2 Latitude 2 in degrees.
- * @param {ol.proj.Projection} projection Projection.
-* @param {number} squaredTolerance Squared tolerance.
-* @return {Array.<number>} Flat coordinates.
-*/
-ol.geom.flat.geodesic.greatCircleArc = function(
-    lon1, lat1, lon2, lat2, projection, squaredTolerance) {
-
-  var geoProjection = ol.proj.get('EPSG:4326');
-
-  var cosLat1 = Math.cos(ol.math.toRadians(lat1));
-  var sinLat1 = Math.sin(ol.math.toRadians(lat1));
-  var cosLat2 = Math.cos(ol.math.toRadians(lat2));
-  var sinLat2 = Math.sin(ol.math.toRadians(lat2));
-  var cosDeltaLon = Math.cos(ol.math.toRadians(lon2 - lon1));
-  var sinDeltaLon = Math.sin(ol.math.toRadians(lon2 - lon1));
-  var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon;
-
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
-       */
-      function(frac) {
-        if (1 <= d) {
-          return [lon2, lat2];
-        }
-        var D = frac * Math.acos(d);
-        var cosD = Math.cos(D);
-        var sinD = Math.sin(D);
-        var y = sinDeltaLon * cosLat2;
-        var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon;
-        var theta = Math.atan2(y, x);
-        var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta));
-        var lon = ol.math.toRadians(lon1) +
-            Math.atan2(Math.sin(theta) * sinD * cosLat1,
-                       cosD - sinLat1 * Math.sin(lat));
-        return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
-      }, ol.proj.getTransform(geoProjection, projection), squaredTolerance);
-};
-
-
-/**
- * Generate a meridian (line at constant longitude).
- * @param {number} lon Longitude.
- * @param {number} lat1 Latitude 1.
- * @param {number} lat2 Latitude 2.
- * @param {ol.proj.Projection} projection Projection.
- * @param {number} squaredTolerance Squared tolerance.
- * @return {Array.<number>} Flat coordinates.
- */
-ol.geom.flat.geodesic.meridian = function(lon, lat1, lat2, projection, squaredTolerance) {
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
-       */
-      function(frac) {
-        return [lon, lat1 + ((lat2 - lat1) * frac)];
-      },
-      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
-};
-
-
-/**
- * Generate a parallel (line at constant latitude).
- * @param {number} lat Latitude.
- * @param {number} lon1 Longitude 1.
- * @param {number} lon2 Longitude 2.
- * @param {ol.proj.Projection} projection Projection.
- * @param {number} squaredTolerance Squared tolerance.
- * @return {Array.<number>} Flat coordinates.
- */
-ol.geom.flat.geodesic.parallel = function(lat, lon1, lon2, projection, squaredTolerance) {
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-  return ol.geom.flat.geodesic.line_(
-      /**
-       * @param {number} frac Fraction.
-       * @return {ol.Coordinate} Coordinate.
-       */
-      function(frac) {
-        return [lon1 + ((lon2 - lon1) * frac), lat];
-      },
-      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
-};
-
-goog.provide('ol.Graticule');
-
-goog.require('ol');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.flat.geodesic');
-goog.require('ol.math');
-goog.require('ol.proj');
-goog.require('ol.render.Event');
-goog.require('ol.style.Stroke');
-
-
-/**
- * Render a grid for a coordinate system on a map.
- * @constructor
- * @param {olx.GraticuleOptions=} opt_options Options.
- * @api
- */
-ol.Graticule = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * @type {ol.Map}
-   * @private
-   */
-  this.map_ = null;
-
-  /**
-   * @type {ol.proj.Projection}
-   * @private
-   */
-  this.projection_ = null;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLat_ = Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLon_ = Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLat_ = -Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLon_ = -Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLatP_ = Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLonP_ = Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLatP_ = -Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.minLonP_ = -Infinity;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.targetSize_ = options.targetSize !== undefined ?
-      options.targetSize : 100;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100;
-  ol.DEBUG && console.assert(this.maxLines_ > 0,
-      'this.maxLines_ should be more than 0');
-
-  /**
-   * @type {Array.<ol.geom.LineString>}
-   * @private
-   */
-  this.meridians_ = [];
-
-  /**
-   * @type {Array.<ol.geom.LineString>}
-   * @private
-   */
-  this.parallels_ = [];
-
-  /**
-   * @type {ol.style.Stroke}
-   * @private
-   */
-  this.strokeStyle_ = options.strokeStyle !== undefined ?
-      options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_;
-
-  /**
-   * @type {ol.TransformFunction|undefined}
-   * @private
-   */
-  this.fromLonLatTransform_ = undefined;
-
-  /**
-   * @type {ol.TransformFunction|undefined}
-   * @private
-   */
-  this.toLonLatTransform_ = undefined;
-
-  /**
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.projectionCenterLonLat_ = null;
-
-  this.setMap(options.map !== undefined ? options.map : null);
-};
-
-
-/**
- * @type {ol.style.Stroke}
- * @private
- * @const
- */
-ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
-  color: 'rgba(0,0,0,0.2)'
-});
-
-
-/**
- * TODO can be configurable
- * @type {Array.<number>}
- * @private
- */
-ol.Graticule.intervals_ = [90, 45, 30, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05,
-  0.01, 0.005, 0.002, 0.001];
-
-
-/**
- * @param {number} lon Longitude.
- * @param {number} minLat Minimal latitude.
- * @param {number} maxLat Maximal latitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {ol.Extent} extent Extent.
- * @param {number} index Index.
- * @return {number} Index.
- * @private
- */
-ol.Graticule.prototype.addMeridian_ = function(lon, minLat, maxLat, squaredTolerance, extent, index) {
-  var lineString = this.getMeridian_(lon, minLat, maxLat,
-      squaredTolerance, index);
-  if (ol.extent.intersects(lineString.getExtent(), extent)) {
-    this.meridians_[index++] = lineString;
-  }
-  return index;
-};
-
-
-/**
- * @param {number} lat Latitude.
- * @param {number} minLon Minimal longitude.
- * @param {number} maxLon Maximal longitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @param {ol.Extent} extent Extent.
- * @param {number} index Index.
- * @return {number} Index.
- * @private
- */
-ol.Graticule.prototype.addParallel_ = function(lat, minLon, maxLon, squaredTolerance, extent, index) {
-  var lineString = this.getParallel_(lat, minLon, maxLon, squaredTolerance,
-      index);
-  if (ol.extent.intersects(lineString.getExtent(), extent)) {
-    this.parallels_[index++] = lineString;
-  }
-  return index;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Coordinate} center Center.
- * @param {number} resolution Resolution.
- * @param {number} squaredTolerance Squared tolerance.
- * @private
- */
-ol.Graticule.prototype.createGraticule_ = function(extent, center, resolution, squaredTolerance) {
-
-  var interval = this.getInterval_(resolution);
-  if (interval == -1) {
-    this.meridians_.length = this.parallels_.length = 0;
-    return;
-  }
-
-  var centerLonLat = this.toLonLatTransform_(center);
-  var centerLon = centerLonLat[0];
-  var centerLat = centerLonLat[1];
-  var maxLines = this.maxLines_;
-  var cnt, idx, lat, lon;
-
-  var validExtent = [
-    Math.max(extent[0], this.minLonP_),
-    Math.max(extent[1], this.minLatP_),
-    Math.min(extent[2], this.maxLonP_),
-    Math.min(extent[3], this.maxLatP_)
-  ];
-
-  validExtent = ol.proj.transformExtent(validExtent, this.projection_,
-      'EPSG:4326');
-  var maxLat = validExtent[3];
-  var maxLon = validExtent[2];
-  var minLat = validExtent[1];
-  var minLon = validExtent[0];
-
-  // Create meridians
-
-  centerLon = Math.floor(centerLon / interval) * interval;
-  lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);
-
-  idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);
-
-  cnt = 0;
-  while (lon != this.minLon_ && cnt++ < maxLines) {
-    lon = Math.max(lon - interval, this.minLon_);
-    idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
-  }
-
-  lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);
-
-  cnt = 0;
-  while (lon != this.maxLon_ && cnt++ < maxLines) {
-    lon = Math.min(lon + interval, this.maxLon_);
-    idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
-  }
-
-  this.meridians_.length = idx;
-
-  // Create parallels
-
-  centerLat = Math.floor(centerLat / interval) * interval;
-  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);
-
-  idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0);
-
-  cnt = 0;
-  while (lat != this.minLat_ && cnt++ < maxLines) {
-    lat = Math.max(lat - interval, this.minLat_);
-    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
-  }
-
-  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);
-
-  cnt = 0;
-  while (lat != this.maxLat_ && cnt++ < maxLines) {
-    lat = Math.min(lat + interval, this.maxLat_);
-    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
-  }
-
-  this.parallels_.length = idx;
-
-};
-
-
-/**
- * @param {number} resolution Resolution.
- * @return {number} The interval in degrees.
- * @private
- */
-ol.Graticule.prototype.getInterval_ = function(resolution) {
-  var centerLon = this.projectionCenterLonLat_[0];
-  var centerLat = this.projectionCenterLonLat_[1];
-  var interval = -1;
-  var i, ii, delta, dist;
-  var target = Math.pow(this.targetSize_ * resolution, 2);
-  /** @type {Array.<number>} **/
-  var p1 = [];
-  /** @type {Array.<number>} **/
-  var p2 = [];
-  for (i = 0, ii = ol.Graticule.intervals_.length; i < ii; ++i) {
-    delta = ol.Graticule.intervals_[i] / 2;
-    p1[0] = centerLon - delta;
-    p1[1] = centerLat - delta;
-    p2[0] = centerLon + delta;
-    p2[1] = centerLat + delta;
-    this.fromLonLatTransform_(p1, p1);
-    this.fromLonLatTransform_(p2, p2);
-    dist = Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2);
-    if (dist <= target) {
-      break;
-    }
-    interval = ol.Graticule.intervals_[i];
-  }
-  return interval;
-};
-
-
-/**
- * Get the map associated with this graticule.
- * @return {ol.Map} The map.
- * @api
- */
-ol.Graticule.prototype.getMap = function() {
-  return this.map_;
-};
-
-
-/**
- * @param {number} lon Longitude.
- * @param {number} minLat Minimal latitude.
- * @param {number} maxLat Maximal latitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.LineString} The meridian line string.
- * @param {number} index Index.
- * @private
- */
-ol.Graticule.prototype.getMeridian_ = function(lon, minLat, maxLat,
-                                               squaredTolerance, index) {
-  ol.DEBUG && console.assert(lon >= this.minLon_,
-      'lon should be larger than or equal to this.minLon_');
-  ol.DEBUG && console.assert(lon <= this.maxLon_,
-      'lon should be smaller than or equal to this.maxLon_');
-  var flatCoordinates = ol.geom.flat.geodesic.meridian(lon,
-      minLat, maxLat, this.projection_, squaredTolerance);
-  ol.DEBUG && console.assert(flatCoordinates.length > 0,
-      'flatCoordinates cannot be empty');
-  var lineString = this.meridians_[index] !== undefined ?
-      this.meridians_[index] : new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
-  return lineString;
-};
-
-
-/**
- * Get the list of meridians.  Meridians are lines of equal longitude.
- * @return {Array.<ol.geom.LineString>} The meridians.
- * @api
- */
-ol.Graticule.prototype.getMeridians = function() {
-  return this.meridians_;
-};
-
-
-/**
- * @param {number} lat Latitude.
- * @param {number} minLon Minimal longitude.
- * @param {number} maxLon Maximal longitude.
- * @param {number} squaredTolerance Squared tolerance.
- * @return {ol.geom.LineString} The parallel line string.
- * @param {number} index Index.
- * @private
- */
-ol.Graticule.prototype.getParallel_ = function(lat, minLon, maxLon,
-                                               squaredTolerance, index) {
-  ol.DEBUG && console.assert(lat >= this.minLat_,
-      'lat should be larger than or equal to this.minLat_');
-  ol.DEBUG && console.assert(lat <= this.maxLat_,
-      'lat should be smaller than or equal to this.maxLat_');
-  var flatCoordinates = ol.geom.flat.geodesic.parallel(lat,
-      this.minLon_, this.maxLon_, this.projection_, squaredTolerance);
-  ol.DEBUG && console.assert(flatCoordinates.length > 0,
-      'flatCoordinates cannot be empty');
-  var lineString = this.parallels_[index] !== undefined ?
-      this.parallels_[index] : new ol.geom.LineString(null);
-  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
-  return lineString;
-};
-
-
-/**
- * Get the list of parallels.  Pallels are lines of equal latitude.
- * @return {Array.<ol.geom.LineString>} The parallels.
- * @api
- */
-ol.Graticule.prototype.getParallels = function() {
-  return this.parallels_;
-};
-
-
-/**
- * @param {ol.render.Event} e Event.
- * @private
- */
-ol.Graticule.prototype.handlePostCompose_ = function(e) {
-  var vectorContext = e.vectorContext;
-  var frameState = e.frameState;
-  var extent = frameState.extent;
-  var viewState = frameState.viewState;
-  var center = viewState.center;
-  var projection = viewState.projection;
-  var resolution = viewState.resolution;
-  var pixelRatio = frameState.pixelRatio;
-  var squaredTolerance =
-      resolution * resolution / (4 * pixelRatio * pixelRatio);
-
-  var updateProjectionInfo = !this.projection_ ||
-      !ol.proj.equivalent(this.projection_, projection);
-
-  if (updateProjectionInfo) {
-    this.updateProjectionInfo_(projection);
-  }
-
-  //Fix the extent if wrapped.
-  //(note: this is the same extent as vectorContext.extent_)
-  var offsetX = 0;
-  if (projection.canWrapX()) {
-    var projectionExtent = projection.getExtent();
-    var worldWidth = ol.extent.getWidth(projectionExtent);
-    var x = frameState.focus[0];
-    if (x < projectionExtent[0] || x > projectionExtent[2]) {
-      var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
-      offsetX = worldWidth * worldsAway;
-      extent = [
-        extent[0] + offsetX, extent[1],
-        extent[2] + offsetX, extent[3]
-      ];
-    }
-  }
-
-  this.createGraticule_(extent, center, resolution, squaredTolerance);
-
-  // Draw the lines
-  vectorContext.setFillStrokeStyle(null, this.strokeStyle_);
-  var i, l, line;
-  for (i = 0, l = this.meridians_.length; i < l; ++i) {
-    line = this.meridians_[i];
-    vectorContext.drawLineString(line, null);
-  }
-  for (i = 0, l = this.parallels_.length; i < l; ++i) {
-    line = this.parallels_[i];
-    vectorContext.drawLineString(line, null);
-  }
-};
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @private
- */
-ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-
-  var extent = projection.getExtent();
-  var worldExtent = projection.getWorldExtent();
-  var worldExtentP = ol.proj.transformExtent(worldExtent,
-      epsg4326Projection, projection);
-
-  var maxLat = worldExtent[3];
-  var maxLon = worldExtent[2];
-  var minLat = worldExtent[1];
-  var minLon = worldExtent[0];
-
-  var maxLatP = worldExtentP[3];
-  var maxLonP = worldExtentP[2];
-  var minLatP = worldExtentP[1];
-  var minLonP = worldExtentP[0];
-
-  ol.DEBUG && console.assert(maxLat !== undefined, 'maxLat should be defined');
-  ol.DEBUG && console.assert(maxLon !== undefined, 'maxLon should be defined');
-  ol.DEBUG && console.assert(minLat !== undefined, 'minLat should be defined');
-  ol.DEBUG && console.assert(minLon !== undefined, 'minLon should be defined');
-
-  ol.DEBUG && console.assert(maxLatP !== undefined,
-      'projected maxLat should be defined');
-  ol.DEBUG && console.assert(maxLonP !== undefined,
-      'projected maxLon should be defined');
-  ol.DEBUG && console.assert(minLatP !== undefined,
-      'projected minLat should be defined');
-  ol.DEBUG && console.assert(minLonP !== undefined,
-      'projected minLon should be defined');
-
-  this.maxLat_ = maxLat;
-  this.maxLon_ = maxLon;
-  this.minLat_ = minLat;
-  this.minLon_ = minLon;
-
-  this.maxLatP_ = maxLatP;
-  this.maxLonP_ = maxLonP;
-  this.minLatP_ = minLatP;
-  this.minLonP_ = minLonP;
-
-
-  this.fromLonLatTransform_ = ol.proj.getTransform(
-      epsg4326Projection, projection);
-
-  this.toLonLatTransform_ = ol.proj.getTransform(
-      projection, epsg4326Projection);
-
-  this.projectionCenterLonLat_ = this.toLonLatTransform_(
-      ol.extent.getCenter(extent));
-
-  this.projection_ = projection;
-};
-
-
-/**
- * Set the map for this graticule.  The graticule will be rendered on the
- * provided map.
- * @param {ol.Map} map Map.
- * @api
- */
-ol.Graticule.prototype.setMap = function(map) {
-  if (this.map_) {
-    this.map_.un(ol.render.Event.Type.POSTCOMPOSE,
-        this.handlePostCompose_, this);
-    this.map_.render();
-  }
-  if (map) {
-    map.on(ol.render.Event.Type.POSTCOMPOSE,
-        this.handlePostCompose_, this);
-    map.render();
-  }
-  this.map_ = map;
-};
-
-goog.provide('ol.ImageTile');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-
-
-/**
- * @constructor
- * @extends {ol.Tile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Tile.State} state State.
- * @param {string} src Image source URI.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
- */
-ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) {
-
-  ol.Tile.call(this, tileCoord, state);
-
-  /**
-   * Image URI
-   *
-   * @private
-   * @type {string}
-   */
-  this.src_ = src;
-
-  /**
-   * @private
-   * @type {Image}
-   */
-  this.image_ = new Image();
-  if (crossOrigin !== null) {
-    this.image_.crossOrigin = crossOrigin;
-  }
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.imageListenerKeys_ = null;
-
-  /**
-   * @private
-   * @type {ol.TileLoadFunctionType}
-   */
-  this.tileLoadFunction_ = tileLoadFunction;
-
-};
-ol.inherits(ol.ImageTile, ol.Tile);
-
-
-/**
- * @inheritDoc
- */
-ol.ImageTile.prototype.disposeInternal = function() {
-  if (this.state == ol.Tile.State.LOADING) {
-    this.unlistenImage_();
-  }
-  if (this.interimTile) {
-    this.interimTile.dispose();
-  }
-  this.state = ol.Tile.State.ABORT;
-  this.changed();
-  ol.Tile.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * Get the image element for this tile.
- * @inheritDoc
- * @api
- */
-ol.ImageTile.prototype.getImage = function() {
-  return this.image_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.ImageTile.prototype.getKey = function() {
-  return this.src_;
-};
-
-
-/**
- * Tracks loading or read errors.
- *
- * @private
- */
-ol.ImageTile.prototype.handleImageError_ = function() {
-  this.state = ol.Tile.State.ERROR;
-  this.unlistenImage_();
-  this.changed();
-};
-
-
-/**
- * Tracks successful image load.
- *
- * @private
- */
-ol.ImageTile.prototype.handleImageLoad_ = function() {
-  if (this.image_.naturalWidth && this.image_.naturalHeight) {
-    this.state = ol.Tile.State.LOADED;
-  } else {
-    this.state = ol.Tile.State.EMPTY;
-  }
-  this.unlistenImage_();
-  this.changed();
-};
-
-
-/**
- * Load the image or retry if loading previously failed.
- * Loading is taken care of by the tile queue, and calling this method is
- * only needed for preloading or for reloading in case of an error.
- * @api
- */
-ol.ImageTile.prototype.load = function() {
-  if (this.state == ol.Tile.State.IDLE || this.state == ol.Tile.State.ERROR) {
-    this.state = ol.Tile.State.LOADING;
-    this.changed();
-    ol.DEBUG && console.assert(!this.imageListenerKeys_,
-        'this.imageListenerKeys_ should be null');
-    this.imageListenerKeys_ = [
-      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
-          this.handleImageError_, this),
-      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
-          this.handleImageLoad_, this)
-    ];
-    this.tileLoadFunction_(this, this.src_);
-  }
-};
-
-
-/**
- * Discards event handlers which listen for load completion or errors.
- *
- * @private
- */
-ol.ImageTile.prototype.unlistenImage_ = function() {
-  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.imageListenerKeys_ = null;
-};
-
-// FIXME should handle all geo-referenced data, not just vector data
-
-goog.provide('ol.interaction.DragAndDrop');
-
-goog.require('ol');
-goog.require('ol.functions');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.EventType');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.proj');
-
-
-/**
- * @classdesc
- * Handles input of vector data by drag and drop.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @fires ol.interaction.DragAndDrop.Event
- * @param {olx.interaction.DragAndDropOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.DragAndDrop = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.DragAndDrop.handleEvent
-  });
-
-  /**
-   * @private
-   * @type {Array.<function(new: ol.format.Feature)>}
-   */
-  this.formatConstructors_ = options.formatConstructors ?
-      options.formatConstructors : [];
-
-  /**
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.projection_ = options.projection ?
-      ol.proj.get(options.projection) : null;
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.dropListenKeys_ = null;
-
-  /**
-   * @private
-   * @type {Element}
-   */
-  this.target = options.target ? options.target : null;
-
-};
-ol.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);
-
-
-/**
- * @param {Event} event Event.
- * @this {ol.interaction.DragAndDrop}
- * @private
- */
-ol.interaction.DragAndDrop.handleDrop_ = function(event) {
-  var files = event.dataTransfer.files;
-  var i, ii, file;
-  for (i = 0, ii = files.length; i < ii; ++i) {
-    file = files.item(i);
-    var reader = new FileReader();
-    reader.addEventListener(ol.events.EventType.LOAD,
-        this.handleResult_.bind(this, file));
-    reader.readAsText(file);
-  }
-};
-
-
-/**
- * @param {Event} event Event.
- * @private
- */
-ol.interaction.DragAndDrop.handleStop_ = function(event) {
-  event.stopPropagation();
-  event.preventDefault();
-  event.dataTransfer.dropEffect = 'copy';
-};
-
-
-/**
- * @param {File} file File.
- * @param {Event} event Load event.
- * @private
- */
-ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, event) {
-  var result = event.target.result;
-  var map = this.getMap();
-  var projection = this.projection_;
-  if (!projection) {
-    var view = map.getView();
-    projection = view.getProjection();
-    ol.DEBUG && console.assert(projection !== undefined,
-        'projection should be defined');
-  }
-  var formatConstructors = this.formatConstructors_;
-  var features = [];
-  var i, ii;
-  for (i = 0, ii = formatConstructors.length; i < ii; ++i) {
-    var formatConstructor = formatConstructors[i];
-    var format = new formatConstructor();
-    features = this.tryReadFeatures_(format, result, {
-      featureProjection: projection
-    });
-    if (features && features.length > 0) {
-      break;
-    }
-  }
-  this.dispatchEvent(
-      new ol.interaction.DragAndDrop.Event(
-          ol.interaction.DragAndDrop.EventType.ADD_FEATURES, file,
-          features, projection));
-};
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} unconditionally and
- * neither prevents the browser default nor stops event propagation.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.DragAndDrop}
- * @api
- */
-ol.interaction.DragAndDrop.handleEvent = ol.functions.TRUE;
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.DragAndDrop.prototype.setMap = function(map) {
-  if (this.dropListenKeys_) {
-    this.dropListenKeys_.forEach(ol.events.unlistenByKey);
-    this.dropListenKeys_ = null;
-  }
-  ol.interaction.Interaction.prototype.setMap.call(this, map);
-  if (map) {
-    var dropArea = this.target ? this.target : map.getViewport();
-    this.dropListenKeys_ = [
-      ol.events.listen(dropArea, ol.events.EventType.DROP,
-          ol.interaction.DragAndDrop.handleDrop_, this),
-      ol.events.listen(dropArea, ol.events.EventType.DRAGENTER,
-          ol.interaction.DragAndDrop.handleStop_, this),
-      ol.events.listen(dropArea, ol.events.EventType.DRAGOVER,
-          ol.interaction.DragAndDrop.handleStop_, this),
-      ol.events.listen(dropArea, ol.events.EventType.DROP,
-          ol.interaction.DragAndDrop.handleStop_, this)
-    ];
-  }
-};
-
-
-/**
- * @param {ol.format.Feature} format Format.
- * @param {string} text Text.
- * @param {olx.format.ReadOptions} options Read options.
- * @private
- * @return {Array.<ol.Feature>} Features.
- */
-ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text, options) {
-  try {
-    return format.readFeatures(text, options);
-  } catch (e) {
-    return null;
-  }
-};
-
-
-/**
- * @enum {string}
- */
-ol.interaction.DragAndDrop.EventType = {
-  /**
-   * Triggered when features are added
-   * @event ol.interaction.DragAndDrop.Event#addfeatures
-   * @api stable
-   */
-  ADD_FEATURES: 'addfeatures'
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances
- * of this type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.interaction.DragAndDropEvent}
- * @param {ol.interaction.DragAndDrop.EventType} type Type.
- * @param {File} file File.
- * @param {Array.<ol.Feature>=} opt_features Features.
- * @param {ol.proj.Projection=} opt_projection Projection.
- */
-ol.interaction.DragAndDrop.Event = function(type, file, opt_features, opt_projection) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The features parsed from dropped data.
-   * @type {Array.<ol.Feature>|undefined}
-   * @api stable
-   */
-  this.features = opt_features;
-
-  /**
-   * The dropped file.
-   * @type {File}
-   * @api stable
-   */
-  this.file = file;
-
-  /**
-   * The feature projection.
-   * @type {ol.proj.Projection|undefined}
-   * @api
-   */
-  this.projection = opt_projection;
-
-};
-ol.inherits(ol.interaction.DragAndDrop.Event, ol.events.Event);
-
-goog.provide('ol.interaction.DragRotateAndZoom');
-
-goog.require('ol');
-goog.require('ol.View');
-goog.require('ol.events.condition');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
-
-
-/**
- * @classdesc
- * Allows the user to zoom and rotate the map by clicking and dragging
- * on the map.  By default, this interaction is limited to when the shift
- * key is held down.
- *
- * This interaction is only supported for mouse devices.
- *
- * And this interaction is not included in the default interactions.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.DragRotateAndZoomOptions=} opt_options Options.
- * @api stable
- */
-ol.interaction.DragRotateAndZoom = function(opt_options) {
-
-  var options = opt_options ? opt_options : {};
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_,
-    handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_,
-    handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_
-  });
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.shiftKeyOnly;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastAngle_ = undefined;
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.lastMagnitude_ = undefined;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.lastScaleDelta_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.duration_ = options.duration !== undefined ? options.duration : 400;
-
-};
-ol.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
- */
-ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return;
-  }
-
-  var map = mapBrowserEvent.map;
-  var size = map.getSize();
-  var offset = mapBrowserEvent.pixel;
-  var deltaX = offset[0] - size[0] / 2;
-  var deltaY = size[1] / 2 - offset[1];
-  var theta = Math.atan2(deltaY, deltaX);
-  var magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
-  var view = map.getView();
-  if (this.lastAngle_ !== undefined) {
-    var angleDelta = theta - this.lastAngle_;
-    ol.interaction.Interaction.rotateWithoutConstraints(
-        map, view, view.getRotation() - angleDelta);
-  }
-  this.lastAngle_ = theta;
-  if (this.lastMagnitude_ !== undefined) {
-    var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude);
-    ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution);
-  }
-  if (this.lastMagnitude_ !== undefined) {
-    this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
-  }
-  this.lastMagnitude_ = magnitude;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
- */
-ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return true;
-  }
-
-  var map = mapBrowserEvent.map;
-  var view = map.getView();
-  view.setHint(ol.View.Hint.INTERACTING, -1);
-  var direction = this.lastScaleDelta_ - 1;
-  ol.interaction.Interaction.rotate(map, view, view.getRotation());
-  ol.interaction.Interaction.zoom(map, view, view.getResolution(),
-      undefined, this.duration_, direction);
-  this.lastScaleDelta_ = 0;
-  return false;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.DragRotateAndZoom}
- * @private
- */
-ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) {
-  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
-    return false;
-  }
-
-  if (this.condition_(mapBrowserEvent)) {
-    mapBrowserEvent.map.getView().setHint(ol.View.Hint.INTERACTING, 1);
-    this.lastAngle_ = undefined;
-    this.lastMagnitude_ = undefined;
-    return true;
-  } else {
-    return false;
-  }
-};
-
-goog.provide('ol.loadingstrategy');
-
-
-/**
- * Strategy function for loading all features with a single request.
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @return {Array.<ol.Extent>} Extents.
- * @api
- */
-ol.loadingstrategy.all = function(extent, resolution) {
-  return [[-Infinity, -Infinity, Infinity, Infinity]];
-};
-
-
-/**
- * Strategy function for loading features based on the view's extent and
- * resolution.
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @return {Array.<ol.Extent>} Extents.
- * @api
- */
-ol.loadingstrategy.bbox = function(extent, resolution) {
-  return [extent];
-};
-
-
-/**
- * Creates a strategy function for loading features based on a tile grid.
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @return {function(ol.Extent, number): Array.<ol.Extent>} Loading strategy.
- * @api
- */
-ol.loadingstrategy.tile = function(tileGrid) {
-  return (
-      /**
-       * @param {ol.Extent} extent Extent.
-       * @param {number} resolution Resolution.
-       * @return {Array.<ol.Extent>} Extents.
-       */
-      function(extent, resolution) {
-        var z = tileGrid.getZForResolution(resolution);
-        var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
-        /** @type {Array.<ol.Extent>} */
-        var extents = [];
-        /** @type {ol.TileCoord} */
-        var tileCoord = [z, 0, 0];
-        for (tileCoord[1] = tileRange.minX; tileCoord[1] <= tileRange.maxX;
-             ++tileCoord[1]) {
-          for (tileCoord[2] = tileRange.minY; tileCoord[2] <= tileRange.maxY;
-               ++tileCoord[2]) {
-            extents.push(tileGrid.getTileCoordExtent(tileCoord));
-          }
-        }
-        return extents;
-      });
-};
-
-goog.provide('ol.ext.rbush');
-/** @typedef {function(*)} */
-ol.ext.rbush;
-(function() {
-var exports = {};
-var module = {exports: exports};
-var define;
-/**
- * @fileoverview
- * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
- */
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.rbush = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)r [...]
-'use strict';
-
-module.exports = partialSort;
-
-// Floyd-Rivest selection algorithm:
-// Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right];
-// The k-th element will have the (k - left + 1)th smallest value in [left, right]
-
-function partialSort(arr, k, left, right, compare) {
-    left = left || 0;
-    right = right || (arr.length - 1);
-    compare = compare || defaultCompare;
-
-    while (right > left) {
-        if (right - left > 600) {
-            var n = right - left + 1;
-            var m = k - left + 1;
-            var z = Math.log(n);
-            var s = 0.5 * Math.exp(2 * z / 3);
-            var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
-            var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
-            var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
-            partialSort(arr, k, newLeft, newRight, compare);
-        }
-
-        var t = arr[k];
-        var i = left;
-        var j = right;
-
-        swap(arr, left, k);
-        if (compare(arr[right], t) > 0) swap(arr, left, right);
-
-        while (i < j) {
-            swap(arr, i, j);
-            i++;
-            j--;
-            while (compare(arr[i], t) < 0) i++;
-            while (compare(arr[j], t) > 0) j--;
-        }
-
-        if (compare(arr[left], t) === 0) swap(arr, left, j);
-        else {
-            j++;
-            swap(arr, j, right);
-        }
-
-        if (j <= k) left = j + 1;
-        if (k <= j) right = j - 1;
-    }
-}
-
-function swap(arr, i, j) {
-    var tmp = arr[i];
-    arr[i] = arr[j];
-    arr[j] = tmp;
-}
-
-function defaultCompare(a, b) {
-    return a < b ? -1 : a > b ? 1 : 0;
-}
-
-},{}],2:[function(_dereq_,module,exports){
-'use strict';
-
-module.exports = rbush;
-
-var quickselect = _dereq_('quickselect');
-
-function rbush(maxEntries, format) {
-    if (!(this instanceof rbush)) return new rbush(maxEntries, format);
-
-    // max entries in a node is 9 by default; min node fill is 40% for best performance
-    this._maxEntries = Math.max(4, maxEntries || 9);
-    this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
-
-    if (format) {
-        this._initFormat(format);
-    }
-
-    this.clear();
-}
-
-rbush.prototype = {
-
-    all: function () {
-        return this._all(this.data, []);
-    },
-
-    search: function (bbox) {
-
-        var node = this.data,
-            result = [],
-            toBBox = this.toBBox;
-
-        if (!intersects(bbox, node)) return result;
-
-        var nodesToSearch = [],
-            i, len, child, childBBox;
-
-        while (node) {
-            for (i = 0, len = node.children.length; i < len; i++) {
-
-                child = node.children[i];
-                childBBox = node.leaf ? toBBox(child) : child;
-
-                if (intersects(bbox, childBBox)) {
-                    if (node.leaf) result.push(child);
-                    else if (contains(bbox, childBBox)) this._all(child, result);
-                    else nodesToSearch.push(child);
-                }
-            }
-            node = nodesToSearch.pop();
-        }
-
-        return result;
-    },
-
-    collides: function (bbox) {
-
-        var node = this.data,
-            toBBox = this.toBBox;
-
-        if (!intersects(bbox, node)) return false;
-
-        var nodesToSearch = [],
-            i, len, child, childBBox;
-
-        while (node) {
-            for (i = 0, len = node.children.length; i < len; i++) {
-
-                child = node.children[i];
-                childBBox = node.leaf ? toBBox(child) : child;
-
-                if (intersects(bbox, childBBox)) {
-                    if (node.leaf || contains(bbox, childBBox)) return true;
-                    nodesToSearch.push(child);
-                }
-            }
-            node = nodesToSearch.pop();
-        }
-
-        return false;
-    },
-
-    load: function (data) {
-        if (!(data && data.length)) return this;
-
-        if (data.length < this._minEntries) {
-            for (var i = 0, len = data.length; i < len; i++) {
-                this.insert(data[i]);
-            }
-            return this;
-        }
-
-        // recursively build the tree with the given data from stratch using OMT algorithm
-        var node = this._build(data.slice(), 0, data.length - 1, 0);
-
-        if (!this.data.children.length) {
-            // save as is if tree is empty
-            this.data = node;
-
-        } else if (this.data.height === node.height) {
-            // split root if trees have the same height
-            this._splitRoot(this.data, node);
-
-        } else {
-            if (this.data.height < node.height) {
-                // swap trees if inserted one is bigger
-                var tmpNode = this.data;
-                this.data = node;
-                node = tmpNode;
-            }
-
-            // insert the small tree into the large tree at appropriate level
-            this._insert(node, this.data.height - node.height - 1, true);
-        }
-
-        return this;
-    },
-
-    insert: function (item) {
-        if (item) this._insert(item, this.data.height - 1);
-        return this;
-    },
-
-    clear: function () {
-        this.data = createNode([]);
-        return this;
-    },
-
-    remove: function (item, equalsFn) {
-        if (!item) return this;
-
-        var node = this.data,
-            bbox = this.toBBox(item),
-            path = [],
-            indexes = [],
-            i, parent, index, goingUp;
-
-        // depth-first iterative tree traversal
-        while (node || path.length) {
-
-            if (!node) { // go up
-                node = path.pop();
-                parent = path[path.length - 1];
-                i = indexes.pop();
-                goingUp = true;
-            }
-
-            if (node.leaf) { // check current node
-                index = findItem(item, node.children, equalsFn);
-
-                if (index !== -1) {
-                    // item found, remove the item and condense tree upwards
-                    node.children.splice(index, 1);
-                    path.push(node);
-                    this._condense(path);
-                    return this;
-                }
-            }
-
-            if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
-                path.push(node);
-                indexes.push(i);
-                i = 0;
-                parent = node;
-                node = node.children[0];
-
-            } else if (parent) { // go right
-                i++;
-                node = parent.children[i];
-                goingUp = false;
-
-            } else node = null; // nothing found
-        }
-
-        return this;
-    },
-
-    toBBox: function (item) { return item; },
-
-    compareMinX: compareNodeMinX,
-    compareMinY: compareNodeMinY,
-
-    toJSON: function () { return this.data; },
-
-    fromJSON: function (data) {
-        this.data = data;
-        return this;
-    },
-
-    _all: function (node, result) {
-        var nodesToSearch = [];
-        while (node) {
-            if (node.leaf) result.push.apply(result, node.children);
-            else nodesToSearch.push.apply(nodesToSearch, node.children);
-
-            node = nodesToSearch.pop();
-        }
-        return result;
-    },
-
-    _build: function (items, left, right, height) {
-
-        var N = right - left + 1,
-            M = this._maxEntries,
-            node;
-
-        if (N <= M) {
-            // reached leaf level; return leaf
-            node = createNode(items.slice(left, right + 1));
-            calcBBox(node, this.toBBox);
-            return node;
-        }
-
-        if (!height) {
-            // target height of the bulk-loaded tree
-            height = Math.ceil(Math.log(N) / Math.log(M));
-
-            // target number of root entries to maximize storage utilization
-            M = Math.ceil(N / Math.pow(M, height - 1));
-        }
-
-        node = createNode([]);
-        node.leaf = false;
-        node.height = height;
-
-        // split the items into M mostly square tiles
-
-        var N2 = Math.ceil(N / M),
-            N1 = N2 * Math.ceil(Math.sqrt(M)),
-            i, j, right2, right3;
-
-        multiSelect(items, left, right, N1, this.compareMinX);
-
-        for (i = left; i <= right; i += N1) {
-
-            right2 = Math.min(i + N1 - 1, right);
-
-            multiSelect(items, i, right2, N2, this.compareMinY);
-
-            for (j = i; j <= right2; j += N2) {
-
-                right3 = Math.min(j + N2 - 1, right2);
-
-                // pack each entry recursively
-                node.children.push(this._build(items, j, right3, height - 1));
-            }
-        }
-
-        calcBBox(node, this.toBBox);
-
-        return node;
-    },
-
-    _chooseSubtree: function (bbox, node, level, path) {
-
-        var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
-
-        while (true) {
-            path.push(node);
-
-            if (node.leaf || path.length - 1 === level) break;
-
-            minArea = minEnlargement = Infinity;
-
-            for (i = 0, len = node.children.length; i < len; i++) {
-                child = node.children[i];
-                area = bboxArea(child);
-                enlargement = enlargedArea(bbox, child) - area;
-
-                // choose entry with the least area enlargement
-                if (enlargement < minEnlargement) {
-                    minEnlargement = enlargement;
-                    minArea = area < minArea ? area : minArea;
-                    targetNode = child;
-
-                } else if (enlargement === minEnlargement) {
-                    // otherwise choose one with the smallest area
-                    if (area < minArea) {
-                        minArea = area;
-                        targetNode = child;
-                    }
-                }
-            }
-
-            node = targetNode || node.children[0];
-        }
-
-        return node;
-    },
-
-    _insert: function (item, level, isNode) {
-
-        var toBBox = this.toBBox,
-            bbox = isNode ? item : toBBox(item),
-            insertPath = [];
-
-        // find the best node for accommodating the item, saving all nodes along the path too
-        var node = this._chooseSubtree(bbox, this.data, level, insertPath);
-
-        // put the item into the node
-        node.children.push(item);
-        extend(node, bbox);
-
-        // split on node overflow; propagate upwards if necessary
-        while (level >= 0) {
-            if (insertPath[level].children.length > this._maxEntries) {
-                this._split(insertPath, level);
-                level--;
-            } else break;
-        }
-
-        // adjust bboxes along the insertion path
-        this._adjustParentBBoxes(bbox, insertPath, level);
-    },
-
-    // split overflowed node into two
-    _split: function (insertPath, level) {
-
-        var node = insertPath[level],
-            M = node.children.length,
-            m = this._minEntries;
-
-        this._chooseSplitAxis(node, m, M);
-
-        var splitIndex = this._chooseSplitIndex(node, m, M);
-
-        var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
-        newNode.height = node.height;
-        newNode.leaf = node.leaf;
-
-        calcBBox(node, this.toBBox);
-        calcBBox(newNode, this.toBBox);
-
-        if (level) insertPath[level - 1].children.push(newNode);
-        else this._splitRoot(node, newNode);
-    },
-
-    _splitRoot: function (node, newNode) {
-        // split root node
-        this.data = createNode([node, newNode]);
-        this.data.height = node.height + 1;
-        this.data.leaf = false;
-        calcBBox(this.data, this.toBBox);
-    },
-
-    _chooseSplitIndex: function (node, m, M) {
-
-        var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
-
-        minOverlap = minArea = Infinity;
-
-        for (i = m; i <= M - m; i++) {
-            bbox1 = distBBox(node, 0, i, this.toBBox);
-            bbox2 = distBBox(node, i, M, this.toBBox);
-
-            overlap = intersectionArea(bbox1, bbox2);
-            area = bboxArea(bbox1) + bboxArea(bbox2);
-
-            // choose distribution with minimum overlap
-            if (overlap < minOverlap) {
-                minOverlap = overlap;
-                index = i;
-
-                minArea = area < minArea ? area : minArea;
-
-            } else if (overlap === minOverlap) {
-                // otherwise choose distribution with minimum area
-                if (area < minArea) {
-                    minArea = area;
-                    index = i;
-                }
-            }
-        }
-
-        return index;
-    },
-
-    // sorts node children by the best axis for split
-    _chooseSplitAxis: function (node, m, M) {
-
-        var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX,
-            compareMinY = node.leaf ? this.compareMinY : compareNodeMinY,
-            xMargin = this._allDistMargin(node, m, M, compareMinX),
-            yMargin = this._allDistMargin(node, m, M, compareMinY);
-
-        // if total distributions margin value is minimal for x, sort by minX,
-        // otherwise it's already sorted by minY
-        if (xMargin < yMargin) node.children.sort(compareMinX);
-    },
-
-    // total margin of all possible split distributions where each node is at least m full
-    _allDistMargin: function (node, m, M, compare) {
-
-        node.children.sort(compare);
-
-        var toBBox = this.toBBox,
-            leftBBox = distBBox(node, 0, m, toBBox),
-            rightBBox = distBBox(node, M - m, M, toBBox),
-            margin = bboxMargin(leftBBox) + bboxMargin(rightBBox),
-            i, child;
-
-        for (i = m; i < M - m; i++) {
-            child = node.children[i];
-            extend(leftBBox, node.leaf ? toBBox(child) : child);
-            margin += bboxMargin(leftBBox);
-        }
-
-        for (i = M - m - 1; i >= m; i--) {
-            child = node.children[i];
-            extend(rightBBox, node.leaf ? toBBox(child) : child);
-            margin += bboxMargin(rightBBox);
-        }
-
-        return margin;
-    },
-
-    _adjustParentBBoxes: function (bbox, path, level) {
-        // adjust bboxes along the given tree path
-        for (var i = level; i >= 0; i--) {
-            extend(path[i], bbox);
-        }
-    },
-
-    _condense: function (path) {
-        // go through the path, removing empty nodes and updating bboxes
-        for (var i = path.length - 1, siblings; i >= 0; i--) {
-            if (path[i].children.length === 0) {
-                if (i > 0) {
-                    siblings = path[i - 1].children;
-                    siblings.splice(siblings.indexOf(path[i]), 1);
-
-                } else this.clear();
-
-            } else calcBBox(path[i], this.toBBox);
-        }
-    },
-
-    _initFormat: function (format) {
-        // data format (minX, minY, maxX, maxY accessors)
-
-        // uses eval-type function compilation instead of just accepting a toBBox function
-        // because the algorithms are very sensitive to sorting functions performance,
-        // so they should be dead simple and without inner calls
-
-        var compareArr = ['return a', ' - b', ';'];
-
-        this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
-        this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
-
-        this.toBBox = new Function('a',
-            'return {minX: a' + format[0] +
-            ', minY: a' + format[1] +
-            ', maxX: a' + format[2] +
-            ', maxY: a' + format[3] + '};');
-    }
-};
-
-function findItem(item, items, equalsFn) {
-    if (!equalsFn) return items.indexOf(item);
-
-    for (var i = 0; i < items.length; i++) {
-        if (equalsFn(item, items[i])) return i;
-    }
-    return -1;
-}
-
-// calculate node's bbox from bboxes of its children
-function calcBBox(node, toBBox) {
-    distBBox(node, 0, node.children.length, toBBox, node);
-}
-
-// min bounding rectangle of node children from k to p-1
-function distBBox(node, k, p, toBBox, destNode) {
-    if (!destNode) destNode = createNode(null);
-    destNode.minX = Infinity;
-    destNode.minY = Infinity;
-    destNode.maxX = -Infinity;
-    destNode.maxY = -Infinity;
-
-    for (var i = k, child; i < p; i++) {
-        child = node.children[i];
-        extend(destNode, node.leaf ? toBBox(child) : child);
-    }
-
-    return destNode;
-}
-
-function extend(a, b) {
-    a.minX = Math.min(a.minX, b.minX);
-    a.minY = Math.min(a.minY, b.minY);
-    a.maxX = Math.max(a.maxX, b.maxX);
-    a.maxY = Math.max(a.maxY, b.maxY);
-    return a;
-}
-
-function compareNodeMinX(a, b) { return a.minX - b.minX; }
-function compareNodeMinY(a, b) { return a.minY - b.minY; }
-
-function bboxArea(a)   { return (a.maxX - a.minX) * (a.maxY - a.minY); }
-function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
-
-function enlargedArea(a, b) {
-    return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
-           (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
-}
-
-function intersectionArea(a, b) {
-    var minX = Math.max(a.minX, b.minX),
-        minY = Math.max(a.minY, b.minY),
-        maxX = Math.min(a.maxX, b.maxX),
-        maxY = Math.min(a.maxY, b.maxY);
-
-    return Math.max(0, maxX - minX) *
-           Math.max(0, maxY - minY);
-}
-
-function contains(a, b) {
-    return a.minX <= b.minX &&
-           a.minY <= b.minY &&
-           b.maxX <= a.maxX &&
-           b.maxY <= a.maxY;
-}
-
-function intersects(a, b) {
-    return b.minX <= a.maxX &&
-           b.minY <= a.maxY &&
-           b.maxX >= a.minX &&
-           b.maxY >= a.minY;
-}
-
-function createNode(children) {
-    return {
-        children: children,
-        height: 1,
-        leaf: true,
-        minX: Infinity,
-        minY: Infinity,
-        maxX: -Infinity,
-        maxY: -Infinity
-    };
-}
-
-// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
-// combines selection algorithm with binary divide & conquer approach
-
-function multiSelect(arr, left, right, n, compare) {
-    var stack = [left, right],
-        mid;
-
-    while (stack.length) {
-        right = stack.pop();
-        left = stack.pop();
-
-        if (right - left <= n) continue;
-
-        mid = left + Math.ceil((right - left) / n / 2) * n;
-        quickselect(arr, mid, left, right, compare);
-
-        stack.push(left, mid, mid, right);
-    }
-}
-
-},{"quickselect":1}]},{},[2])(2)
-});
-ol.ext.rbush = module.exports;
-})();
-
-goog.provide('ol.structs.RBush');
-
-goog.require('ol');
-goog.require('ol.ext.rbush');
-goog.require('ol.extent');
-goog.require('ol.obj');
-
-
-/**
- * Wrapper around the RBush by Vladimir Agafonkin.
- *
- * @constructor
- * @param {number=} opt_maxEntries Max entries.
- * @see https://github.com/mourner/rbush
- * @struct
- * @template T
- */
-ol.structs.RBush = function(opt_maxEntries) {
-
-  /**
-   * @private
-   */
-  this.rbush_ = ol.ext.rbush(opt_maxEntries);
-
-  /**
-   * A mapping between the objects added to this rbush wrapper
-   * and the objects that are actually added to the internal rbush.
-   * @private
-   * @type {Object.<number, ol.RBushEntry>}
-   */
-  this.items_ = {};
-
-  if (ol.DEBUG) {
-    /**
-     * @private
-     * @type {number}
-     */
-    this.readers_ = 0;
-  }
-};
-
-
-/**
- * Insert a value into the RBush.
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
- */
-ol.structs.RBush.prototype.insert = function(extent, value) {
-  if (ol.DEBUG && this.readers_) {
-    throw new Error('Can not insert value while reading');
-  }
-  /** @type {ol.RBushEntry} */
-  var item = {
-    minX: extent[0],
-    minY: extent[1],
-    maxX: extent[2],
-    maxY: extent[3],
-    value: value
-  };
-
-  this.rbush_.insert(item);
-  // remember the object that was added to the internal rbush
-  ol.DEBUG && console.assert(!(ol.getUid(value) in this.items_),
-      'uid (%s) of value (%s) already exists', ol.getUid(value), value);
-  this.items_[ol.getUid(value)] = item;
-};
-
-
-/**
- * Bulk-insert values into the RBush.
- * @param {Array.<ol.Extent>} extents Extents.
- * @param {Array.<T>} values Values.
- */
-ol.structs.RBush.prototype.load = function(extents, values) {
-  if (ol.DEBUG && this.readers_) {
-    throw new Error('Can not insert values while reading');
-  }
-  ol.DEBUG && console.assert(extents.length === values.length,
-      'extens and values must have same length (%s === %s)',
-      extents.length, values.length);
-
-  var items = new Array(values.length);
-  for (var i = 0, l = values.length; i < l; i++) {
-    var extent = extents[i];
-    var value = values[i];
-
-    /** @type {ol.RBushEntry} */
-    var item = {
-      minX: extent[0],
-      minY: extent[1],
-      maxX: extent[2],
-      maxY: extent[3],
-      value: value
-    };
-    items[i] = item;
-    ol.DEBUG && console.assert(!(ol.getUid(value) in this.items_),
-        'uid (%s) of value (%s) already exists', ol.getUid(value), value);
-    this.items_[ol.getUid(value)] = item;
-  }
-  this.rbush_.load(items);
-};
-
-
-/**
- * Remove a value from the RBush.
- * @param {T} value Value.
- * @return {boolean} Removed.
- */
-ol.structs.RBush.prototype.remove = function(value) {
-  if (ol.DEBUG && this.readers_) {
-    throw new Error('Can not remove value while reading');
-  }
-  var uid = ol.getUid(value);
-  ol.DEBUG && console.assert(uid in this.items_,
-      'uid (%s) of value (%s) does not exist', uid, value);
-
-  // get the object in which the value was wrapped when adding to the
-  // internal rbush. then use that object to do the removal.
-  var item = this.items_[uid];
-  delete this.items_[uid];
-  return this.rbush_.remove(item) !== null;
-};
-
-
-/**
- * Update the extent of a value in the RBush.
- * @param {ol.Extent} extent Extent.
- * @param {T} value Value.
- */
-ol.structs.RBush.prototype.update = function(extent, value) {
-  ol.DEBUG && console.assert(ol.getUid(value) in this.items_,
-      'uid (%s) of value (%s) does not exist', ol.getUid(value), value);
-
-  var item = this.items_[ol.getUid(value)];
-  var bbox = [item.minX, item.minY, item.maxX, item.maxY];
-  if (!ol.extent.equals(bbox, extent)) {
-    if (ol.DEBUG && this.readers_) {
-      throw new Error('Can not update extent while reading');
-    }
-    this.remove(value);
-    this.insert(extent, value);
-  }
-};
-
-
-/**
- * Return all values in the RBush.
- * @return {Array.<T>} All.
- */
-ol.structs.RBush.prototype.getAll = function() {
-  var items = this.rbush_.all();
-  return items.map(function(item) {
-    return item.value;
-  });
-};
-
-
-/**
- * Return all values in the given extent.
- * @param {ol.Extent} extent Extent.
- * @return {Array.<T>} All in extent.
- */
-ol.structs.RBush.prototype.getInExtent = function(extent) {
-  /** @type {ol.RBushEntry} */
-  var bbox = {
-    minX: extent[0],
-    minY: extent[1],
-    maxX: extent[2],
-    maxY: extent[3]
-  };
-  var items = this.rbush_.search(bbox);
-  return items.map(function(item) {
-    return item.value;
-  });
-};
-
-
-/**
- * Calls a callback function with each value in the tree.
- * If the callback returns a truthy value, this value is returned without
- * checking the rest of the tree.
- * @param {function(this: S, T): *} callback Callback.
- * @param {S=} opt_this The object to use as `this` in `callback`.
- * @return {*} Callback return value.
- * @template S
- */
-ol.structs.RBush.prototype.forEach = function(callback, opt_this) {
-  if (ol.DEBUG) {
-    ++this.readers_;
-    try {
-      return this.forEach_(this.getAll(), callback, opt_this);
-    } finally {
-      --this.readers_;
-    }
-  } else {
-    return this.forEach_(this.getAll(), callback, opt_this);
-  }
-};
-
-
-/**
- * Calls a callback function with each value in the provided extent.
- * @param {ol.Extent} extent Extent.
- * @param {function(this: S, T): *} callback Callback.
- * @param {S=} opt_this The object to use as `this` in `callback`.
- * @return {*} Callback return value.
- * @template S
- */
-ol.structs.RBush.prototype.forEachInExtent = function(extent, callback, opt_this) {
-  if (ol.DEBUG) {
-    ++this.readers_;
-    try {
-      return this.forEach_(this.getInExtent(extent), callback, opt_this);
-    } finally {
-      --this.readers_;
-    }
-  } else {
-    return this.forEach_(this.getInExtent(extent), callback, opt_this);
-  }
-};
-
-
-/**
- * @param {Array.<T>} values Values.
- * @param {function(this: S, T): *} callback Callback.
- * @param {S=} opt_this The object to use as `this` in `callback`.
- * @private
- * @return {*} Callback return value.
- * @template S
- */
-ol.structs.RBush.prototype.forEach_ = function(values, callback, opt_this) {
-  var result;
-  for (var i = 0, l = values.length; i < l; i++) {
-    result = callback.call(opt_this, values[i]);
-    if (result) {
-      return result;
-    }
-  }
-  return result;
-};
-
-
-/**
- * @return {boolean} Is empty.
- */
-ol.structs.RBush.prototype.isEmpty = function() {
-  return ol.obj.isEmpty(this.items_);
-};
-
-
-/**
- * Remove all values from the RBush.
- */
-ol.structs.RBush.prototype.clear = function() {
-  this.rbush_.clear();
-  this.items_ = {};
-};
-
-
-/**
- * @param {ol.Extent=} opt_extent Extent.
- * @return {!ol.Extent} Extent.
- */
-ol.structs.RBush.prototype.getExtent = function(opt_extent) {
-  // FIXME add getExtent() to rbush
-  var data = this.rbush_.data;
-  return [data.minX, data.minY, data.maxX, data.maxY];
-};
-
-// FIXME bulk feature upload - suppress events
-// FIXME make change-detection more refined (notably, geometry hint)
-
-goog.provide('ol.source.Vector');
-
-goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.ObjectEventType');
-goog.require('ol.array');
-goog.require('ol.asserts');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.featureloader');
-goog.require('ol.functions');
-goog.require('ol.loadingstrategy');
-goog.require('ol.obj');
-goog.require('ol.source.Source');
-goog.require('ol.source.State');
-goog.require('ol.structs.RBush');
-
-
-/**
- * @classdesc
- * Provides a source of features for vector layers. Vector features provided
- * by this source are suitable for editing. See {@link ol.source.VectorTile} for
- * vector data that is optimized for rendering.
- *
- * @constructor
- * @extends {ol.source.Source}
- * @fires ol.source.Vector.Event
- * @param {olx.source.VectorOptions=} opt_options Vector source options.
- * @api stable
- */
-ol.source.Vector = function(opt_options) {
-
-  var options = opt_options || {};
-
-  ol.source.Source.call(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: undefined,
-    state: ol.source.State.READY,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-  /**
-   * @private
-   * @type {ol.FeatureLoader}
-   */
-  this.loader_ = ol.nullFunction;
-
-  /**
-   * @private
-   * @type {ol.format.Feature|undefined}
-   */
-  this.format_ = options.format;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;
-
-  /**
-   * @private
-   * @type {string|ol.FeatureUrlFunction|undefined}
-   */
-  this.url_ = options.url;
-
-  if (options.loader !== undefined) {
-    this.loader_ = options.loader;
-  } else if (this.url_ !== undefined) {
-    ol.asserts.assert(this.format_, 7); // `format` must be set when `url` is set
-    // create a XHR feature loader for "url" and "format"
-    this.loader_ = ol.featureloader.xhr(this.url_, /** @type {ol.format.Feature} */ (this.format_));
-  }
-
-  /**
-   * @private
-   * @type {ol.LoadingStrategy}
-   */
-  this.strategy_ = options.strategy !== undefined ? options.strategy :
-      ol.loadingstrategy.all;
-
-  var useSpatialIndex =
-      options.useSpatialIndex !== undefined ? options.useSpatialIndex : true;
-
-  /**
-   * @private
-   * @type {ol.structs.RBush.<ol.Feature>}
-   */
-  this.featuresRtree_ = useSpatialIndex ? new ol.structs.RBush() : null;
-
-  /**
-   * @private
-   * @type {ol.structs.RBush.<{extent: ol.Extent}>}
-   */
-  this.loadedExtentsRtree_ = new ol.structs.RBush();
-
-  /**
-   * @private
-   * @type {Object.<string, ol.Feature>}
-   */
-  this.nullGeometryFeatures_ = {};
-
-  /**
-   * A lookup of features by id (the return from feature.getId()).
-   * @private
-   * @type {Object.<string, ol.Feature>}
-   */
-  this.idIndex_ = {};
-
-  /**
-   * A lookup of features without id (keyed by ol.getUid(feature)).
-   * @private
-   * @type {Object.<string, ol.Feature>}
-   */
-  this.undefIdIndex_ = {};
-
-  /**
-   * @private
-   * @type {Object.<string, Array.<ol.EventsKey>>}
-   */
-  this.featureChangeKeys_ = {};
-
-  /**
-   * @private
-   * @type {ol.Collection.<ol.Feature>}
-   */
-  this.featuresCollection_ = null;
-
-  var collection, features;
-  if (options.features instanceof ol.Collection) {
-    collection = options.features;
-    features = collection.getArray();
-  } else if (Array.isArray(options.features)) {
-    features = options.features;
-  }
-  if (!useSpatialIndex && collection === undefined) {
-    collection = new ol.Collection(features);
-  }
-  if (features !== undefined) {
-    this.addFeaturesInternal(features);
-  }
-  if (collection !== undefined) {
-    this.bindFeaturesCollection_(collection);
-  }
-
-};
-ol.inherits(ol.source.Vector, ol.source.Source);
-
-
-/**
- * Add a single feature to the source.  If you want to add a batch of features
- * at once, call {@link ol.source.Vector#addFeatures source.addFeatures()}
- * instead.
- * @param {ol.Feature} feature Feature to add.
- * @api stable
- */
-ol.source.Vector.prototype.addFeature = function(feature) {
-  this.addFeatureInternal(feature);
-  this.changed();
-};
-
-
-/**
- * Add a feature without firing a `change` event.
- * @param {ol.Feature} feature Feature.
- * @protected
- */
-ol.source.Vector.prototype.addFeatureInternal = function(feature) {
-  var featureKey = ol.getUid(feature).toString();
-
-  if (!this.addToIndex_(featureKey, feature)) {
-    return;
-  }
-
-  this.setupChangeEvents_(featureKey, feature);
-
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    var extent = geometry.getExtent();
-    if (this.featuresRtree_) {
-      this.featuresRtree_.insert(extent, feature);
-    }
-  } else {
-    this.nullGeometryFeatures_[featureKey] = feature;
-  }
-
-  this.dispatchEvent(
-      new ol.source.Vector.Event(ol.source.Vector.EventType.ADDFEATURE, feature));
-};
-
-
-/**
- * @param {string} featureKey Unique identifier for the feature.
- * @param {ol.Feature} feature The feature.
- * @private
- */
-ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) {
-  ol.DEBUG && console.assert(!(featureKey in this.featureChangeKeys_),
-      'key (%s) not yet registered in featureChangeKey', featureKey);
-  this.featureChangeKeys_[featureKey] = [
-    ol.events.listen(feature, ol.events.EventType.CHANGE,
-        this.handleFeatureChange_, this),
-    ol.events.listen(feature, ol.ObjectEventType.PROPERTYCHANGE,
-        this.handleFeatureChange_, this)
-  ];
-};
-
-
-/**
- * @param {string} featureKey Unique identifier for the feature.
- * @param {ol.Feature} feature The feature.
- * @return {boolean} The feature is "valid", in the sense that it is also a
- *     candidate for insertion into the Rtree.
- * @private
- */
-ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) {
-  var valid = true;
-  var id = feature.getId();
-  if (id !== undefined) {
-    if (!(id.toString() in this.idIndex_)) {
-      this.idIndex_[id.toString()] = feature;
-    } else {
-      valid = false;
-    }
-  } else {
-    ol.asserts.assert(!(featureKey in this.undefIdIndex_),
-        30); // The passed `feature` was already added to the source
-    this.undefIdIndex_[featureKey] = feature;
-  }
-  return valid;
-};
-
-
-/**
- * Add a batch of features to the source.
- * @param {Array.<ol.Feature>} features Features to add.
- * @api stable
- */
-ol.source.Vector.prototype.addFeatures = function(features) {
-  this.addFeaturesInternal(features);
-  this.changed();
-};
-
-
-/**
- * Add features without firing a `change` event.
- * @param {Array.<ol.Feature>} features Features.
- * @protected
- */
-ol.source.Vector.prototype.addFeaturesInternal = function(features) {
-  var featureKey, i, length, feature;
-
-  var extents = [];
-  var newFeatures = [];
-  var geometryFeatures = [];
-
-  for (i = 0, length = features.length; i < length; i++) {
-    feature = features[i];
-    featureKey = ol.getUid(feature).toString();
-    if (this.addToIndex_(featureKey, feature)) {
-      newFeatures.push(feature);
-    }
-  }
-
-  for (i = 0, length = newFeatures.length; i < length; i++) {
-    feature = newFeatures[i];
-    featureKey = ol.getUid(feature).toString();
-    this.setupChangeEvents_(featureKey, feature);
-
-    var geometry = feature.getGeometry();
-    if (geometry) {
-      var extent = geometry.getExtent();
-      extents.push(extent);
-      geometryFeatures.push(feature);
-    } else {
-      this.nullGeometryFeatures_[featureKey] = feature;
-    }
-  }
-  if (this.featuresRtree_) {
-    this.featuresRtree_.load(extents, geometryFeatures);
-  }
-
-  for (i = 0, length = newFeatures.length; i < length; i++) {
-    this.dispatchEvent(new ol.source.Vector.Event(
-        ol.source.Vector.EventType.ADDFEATURE, newFeatures[i]));
-  }
-};
-
-
-/**
- * @param {!ol.Collection.<ol.Feature>} collection Collection.
- * @private
- */
-ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) {
-  ol.DEBUG && console.assert(!this.featuresCollection_,
-      'bindFeaturesCollection can only be called once');
-  var modifyingCollection = false;
-  ol.events.listen(this, ol.source.Vector.EventType.ADDFEATURE,
-      function(evt) {
-        if (!modifyingCollection) {
-          modifyingCollection = true;
-          collection.push(evt.feature);
-          modifyingCollection = false;
-        }
-      });
-  ol.events.listen(this, ol.source.Vector.EventType.REMOVEFEATURE,
-      function(evt) {
-        if (!modifyingCollection) {
-          modifyingCollection = true;
-          collection.remove(evt.feature);
-          modifyingCollection = false;
-        }
-      });
-  ol.events.listen(collection, ol.Collection.EventType.ADD,
-      function(evt) {
-        if (!modifyingCollection) {
-          modifyingCollection = true;
-          this.addFeature(/** @type {ol.Feature} */ (evt.element));
-          modifyingCollection = false;
-        }
-      }, this);
-  ol.events.listen(collection, ol.Collection.EventType.REMOVE,
-      function(evt) {
-        if (!modifyingCollection) {
-          modifyingCollection = true;
-          this.removeFeature(/** @type {ol.Feature} */ (evt.element));
-          modifyingCollection = false;
-        }
-      }, this);
-  this.featuresCollection_ = collection;
-};
-
-
-/**
- * Remove all features from the source.
- * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events.
- * @api stable
- */
-ol.source.Vector.prototype.clear = function(opt_fast) {
-  if (opt_fast) {
-    for (var featureId in this.featureChangeKeys_) {
-      var keys = this.featureChangeKeys_[featureId];
-      keys.forEach(ol.events.unlistenByKey);
-    }
-    if (!this.featuresCollection_) {
-      this.featureChangeKeys_ = {};
-      this.idIndex_ = {};
-      this.undefIdIndex_ = {};
-    }
-  } else {
-    if (this.featuresRtree_) {
-      this.featuresRtree_.forEach(this.removeFeatureInternal, this);
-      for (var id in this.nullGeometryFeatures_) {
-        this.removeFeatureInternal(this.nullGeometryFeatures_[id]);
-      }
-    }
-  }
-  if (this.featuresCollection_) {
-    this.featuresCollection_.clear();
-  }
-  ol.DEBUG && console.assert(ol.obj.isEmpty(this.featureChangeKeys_),
-      'featureChangeKeys is an empty object now');
-  ol.DEBUG && console.assert(ol.obj.isEmpty(this.idIndex_),
-      'idIndex is an empty object now');
-  ol.DEBUG && console.assert(ol.obj.isEmpty(this.undefIdIndex_),
-      'undefIdIndex is an empty object now');
-
-  if (this.featuresRtree_) {
-    this.featuresRtree_.clear();
-  }
-  this.loadedExtentsRtree_.clear();
-  this.nullGeometryFeatures_ = {};
-
-  var clearEvent = new ol.source.Vector.Event(ol.source.Vector.EventType.CLEAR);
-  this.dispatchEvent(clearEvent);
-  this.changed();
-};
-
-
-/**
- * Iterate through all features on the source, calling the provided callback
- * with each one.  If the callback returns any "truthy" value, iteration will
- * stop and the function will return the same value.
- *
- * @param {function(this: T, ol.Feature): S} callback Called with each feature
- *     on the source.  Return a truthy value to stop iteration.
- * @param {T=} opt_this The object to use as `this` in the callback.
- * @return {S|undefined} The return value from the last call to the callback.
- * @template T,S
- * @api stable
- */
-ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) {
-  if (this.featuresRtree_) {
-    return this.featuresRtree_.forEach(callback, opt_this);
-  } else if (this.featuresCollection_) {
-    return this.featuresCollection_.forEach(callback, opt_this);
-  }
-};
-
-
-/**
- * Iterate through all features whose geometries contain the provided
- * coordinate, calling the callback with each feature.  If the callback returns
- * a "truthy" value, iteration will stop and the function will return the same
- * value.
- *
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {function(this: T, ol.Feature): S} callback Called with each feature
- *     whose goemetry contains the provided coordinate.
- * @param {T=} opt_this The object to use as `this` in the callback.
- * @return {S|undefined} The return value from the last call to the callback.
- * @template T,S
- */
-ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = function(coordinate, callback, opt_this) {
-  var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]];
-  return this.forEachFeatureInExtent(extent, function(feature) {
-    var geometry = feature.getGeometry();
-    ol.DEBUG && console.assert(geometry, 'feature geometry is defined and not null');
-    if (geometry.intersectsCoordinate(coordinate)) {
-      return callback.call(opt_this, feature);
-    } else {
-      return undefined;
-    }
-  });
-};
-
-
-/**
- * Iterate through all features whose bounding box intersects the provided
- * extent (note that the feature's geometry may not intersect the extent),
- * calling the callback with each feature.  If the callback returns a "truthy"
- * value, iteration will stop and the function will return the same value.
- *
- * If you are interested in features whose geometry intersects an extent, call
- * the {@link ol.source.Vector#forEachFeatureIntersectingExtent
- * source.forEachFeatureIntersectingExtent()} method instead.
- *
- * When `useSpatialIndex` is set to false, this method will loop through all
- * features, equivalent to {@link ol.source.Vector#forEachFeature}.
- *
- * @param {ol.Extent} extent Extent.
- * @param {function(this: T, ol.Feature): S} callback Called with each feature
- *     whose bounding box intersects the provided extent.
- * @param {T=} opt_this The object to use as `this` in the callback.
- * @return {S|undefined} The return value from the last call to the callback.
- * @template T,S
- * @api
- */
-ol.source.Vector.prototype.forEachFeatureInExtent = function(extent, callback, opt_this) {
-  if (this.featuresRtree_) {
-    return this.featuresRtree_.forEachInExtent(extent, callback, opt_this);
-  } else if (this.featuresCollection_) {
-    return this.featuresCollection_.forEach(callback, opt_this);
-  }
-};
-
-
-/**
- * Iterate through all features whose geometry intersects the provided extent,
- * calling the callback with each feature.  If the callback returns a "truthy"
- * value, iteration will stop and the function will return the same value.
- *
- * If you only want to test for bounding box intersection, call the
- * {@link ol.source.Vector#forEachFeatureInExtent
- * source.forEachFeatureInExtent()} method instead.
- *
- * @param {ol.Extent} extent Extent.
- * @param {function(this: T, ol.Feature): S} callback Called with each feature
- *     whose geometry intersects the provided extent.
- * @param {T=} opt_this The object to use as `this` in the callback.
- * @return {S|undefined} The return value from the last call to the callback.
- * @template T,S
- * @api
- */
-ol.source.Vector.prototype.forEachFeatureIntersectingExtent = function(extent, callback, opt_this) {
-  return this.forEachFeatureInExtent(extent,
-      /**
-       * @param {ol.Feature} feature Feature.
-       * @return {S|undefined} The return value from the last call to the callback.
-       * @template S
-       */
-      function(feature) {
-        var geometry = feature.getGeometry();
-        ol.DEBUG && console.assert(geometry,
-            'feature geometry is defined and not null');
-        if (geometry.intersectsExtent(extent)) {
-          var result = callback.call(opt_this, feature);
-          if (result) {
-            return result;
-          }
-        }
-      });
-};
-
-
-/**
- * Get the features collection associated with this source. Will be `null`
- * unless the source was configured with `useSpatialIndex` set to `false`, or
- * with an {@link ol.Collection} as `features`.
- * @return {ol.Collection.<ol.Feature>} The collection of features.
- * @api
- */
-ol.source.Vector.prototype.getFeaturesCollection = function() {
-  return this.featuresCollection_;
-};
-
-
-/**
- * Get all features on the source in random order.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.source.Vector.prototype.getFeatures = function() {
-  var features;
-  if (this.featuresCollection_) {
-    features = this.featuresCollection_.getArray();
-  } else if (this.featuresRtree_) {
-    features = this.featuresRtree_.getAll();
-    if (!ol.obj.isEmpty(this.nullGeometryFeatures_)) {
-      ol.array.extend(
-          features, ol.obj.getValues(this.nullGeometryFeatures_));
-    }
-  }
-  return /** @type {Array.<ol.Feature>} */ (features);
-};
-
-
-/**
- * Get all features whose geometry intersects the provided coordinate.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {Array.<ol.Feature>} Features.
- * @api stable
- */
-ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
-  var features = [];
-  this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) {
-    features.push(feature);
-  });
-  return features;
-};
-
-
-/**
- * Get all features in the provided extent.  Note that this returns an array of
- * all features intersecting the given extent in random order (so it may include
- * features whose geometries do not intersect the extent).
- *
- * This method is not available when the source is configured with
- * `useSpatialIndex` set to `false`.
- * @param {ol.Extent} extent Extent.
- * @return {Array.<ol.Feature>} Features.
- * @api
- */
-ol.source.Vector.prototype.getFeaturesInExtent = function(extent) {
-  ol.DEBUG && console.assert(this.featuresRtree_,
-      'getFeaturesInExtent does not work when useSpatialIndex is set to false');
-  return this.featuresRtree_.getInExtent(extent);
-};
-
-
-/**
- * Get the closest feature to the provided coordinate.
- *
- * This method is not available when the source is configured with
- * `useSpatialIndex` set to `false`.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {function(ol.Feature):boolean=} opt_filter Feature filter function.
- *     The filter function will receive one argument, the {@link ol.Feature feature}
- *     and it should return a boolean value. By default, no filtering is made.
- * @return {ol.Feature} Closest feature.
- * @api stable
- */
-ol.source.Vector.prototype.getClosestFeatureToCoordinate = function(coordinate, opt_filter) {
-  // Find the closest feature using branch and bound.  We start searching an
-  // infinite extent, and find the distance from the first feature found.  This
-  // becomes the closest feature.  We then compute a smaller extent which any
-  // closer feature must intersect.  We continue searching with this smaller
-  // extent, trying to find a closer feature.  Every time we find a closer
-  // feature, we update the extent being searched so that any even closer
-  // feature must intersect it.  We continue until we run out of features.
-  var x = coordinate[0];
-  var y = coordinate[1];
-  var closestFeature = null;
-  var closestPoint = [NaN, NaN];
-  var minSquaredDistance = Infinity;
-  var extent = [-Infinity, -Infinity, Infinity, Infinity];
-  ol.DEBUG && console.assert(this.featuresRtree_,
-      'getClosestFeatureToCoordinate does not work with useSpatialIndex set ' +
-      'to false');
-  var filter = opt_filter ? opt_filter : ol.functions.TRUE;
-  this.featuresRtree_.forEachInExtent(extent,
-      /**
-       * @param {ol.Feature} feature Feature.
-       */
-      function(feature) {
-        if (filter(feature)) {
-          var geometry = feature.getGeometry();
-          ol.DEBUG && console.assert(geometry,
-              'feature geometry is defined and not null');
-          var previousMinSquaredDistance = minSquaredDistance;
-          minSquaredDistance = geometry.closestPointXY(
-              x, y, closestPoint, minSquaredDistance);
-          if (minSquaredDistance < previousMinSquaredDistance) {
-            closestFeature = feature;
-            // This is sneaky.  Reduce the extent that it is currently being
-            // searched while the R-Tree traversal using this same extent object
-            // is still in progress.  This is safe because the new extent is
-            // strictly contained by the old extent.
-            var minDistance = Math.sqrt(minSquaredDistance);
-            extent[0] = x - minDistance;
-            extent[1] = y - minDistance;
-            extent[2] = x + minDistance;
-            extent[3] = y + minDistance;
-          }
-        }
-      });
-  return closestFeature;
-};
-
-
-/**
- * Get the extent of the features currently in the source.
- *
- * This method is not available when the source is configured with
- * `useSpatialIndex` set to `false`.
- * @return {!ol.Extent} Extent.
- * @api stable
- */
-ol.source.Vector.prototype.getExtent = function() {
-  ol.DEBUG && console.assert(this.featuresRtree_,
-      'getExtent does not work when useSpatialIndex is set to false');
-  return this.featuresRtree_.getExtent();
-};
-
-
-/**
- * Get a feature by its identifier (the value returned by feature.getId()).
- * Note that the index treats string and numeric identifiers as the same.  So
- * `source.getFeatureById(2)` will return a feature with id `'2'` or `2`.
- *
- * @param {string|number} id Feature identifier.
- * @return {ol.Feature} The feature (or `null` if not found).
- * @api stable
- */
-ol.source.Vector.prototype.getFeatureById = function(id) {
-  var feature = this.idIndex_[id.toString()];
-  return feature !== undefined ? feature : null;
-};
-
-
-/**
- * Get the format associated with this source.
- *
- * @return {ol.format.Feature|undefined} The feature format.
- * @api
- */
-ol.source.Vector.prototype.getFormat = function() {
-  return this.format_;
-};
-
-
-/**
- * @return {boolean} The source can have overlapping geometries.
- */
-ol.source.Vector.prototype.getOverlaps = function() {
-  return this.overlaps_;
-};
-
-
-/**
- * Get the url associated with this source.
- *
- * @return {string|ol.FeatureUrlFunction|undefined} The url.
- * @api
- */
-ol.source.Vector.prototype.getUrl = function() {
-  return this.url_;
-};
-
-
-/**
- * @param {ol.events.Event} event Event.
- * @private
- */
-ol.source.Vector.prototype.handleFeatureChange_ = function(event) {
-  var feature = /** @type {ol.Feature} */ (event.target);
-  var featureKey = ol.getUid(feature).toString();
-  var geometry = feature.getGeometry();
-  if (!geometry) {
-    if (!(featureKey in this.nullGeometryFeatures_)) {
-      if (this.featuresRtree_) {
-        this.featuresRtree_.remove(feature);
-      }
-      this.nullGeometryFeatures_[featureKey] = feature;
-    }
-  } else {
-    var extent = geometry.getExtent();
-    if (featureKey in this.nullGeometryFeatures_) {
-      delete this.nullGeometryFeatures_[featureKey];
-      if (this.featuresRtree_) {
-        this.featuresRtree_.insert(extent, feature);
-      }
-    } else {
-      if (this.featuresRtree_) {
-        this.featuresRtree_.update(extent, feature);
-      }
-    }
-  }
-  var id = feature.getId();
-  var removed;
-  if (id !== undefined) {
-    var sid = id.toString();
-    if (featureKey in this.undefIdIndex_) {
-      delete this.undefIdIndex_[featureKey];
-      this.idIndex_[sid] = feature;
-    } else {
-      if (this.idIndex_[sid] !== feature) {
-        removed = this.removeFromIdIndex_(feature);
-        ol.DEBUG && console.assert(removed,
-            'Expected feature to be removed from index');
-        this.idIndex_[sid] = feature;
-      }
-    }
-  } else {
-    if (!(featureKey in this.undefIdIndex_)) {
-      removed = this.removeFromIdIndex_(feature);
-      ol.DEBUG && console.assert(removed,
-          'Expected feature to be removed from index');
-      this.undefIdIndex_[featureKey] = feature;
-    } else {
-      ol.DEBUG && console.assert(this.undefIdIndex_[featureKey] === feature,
-          'feature keyed under %s in undefIdKeys', featureKey);
-    }
-  }
-  this.changed();
-  this.dispatchEvent(new ol.source.Vector.Event(
-      ol.source.Vector.EventType.CHANGEFEATURE, feature));
-};
-
-
-/**
- * @return {boolean} Is empty.
- */
-ol.source.Vector.prototype.isEmpty = function() {
-  return this.featuresRtree_.isEmpty() &&
-      ol.obj.isEmpty(this.nullGeometryFeatures_);
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {number} resolution Resolution.
- * @param {ol.proj.Projection} projection Projection.
- */
-ol.source.Vector.prototype.loadFeatures = function(
-    extent, resolution, projection) {
-  var loadedExtentsRtree = this.loadedExtentsRtree_;
-  var extentsToLoad = this.strategy_(extent, resolution);
-  var i, ii;
-  for (i = 0, ii = extentsToLoad.length; i < ii; ++i) {
-    var extentToLoad = extentsToLoad[i];
-    var alreadyLoaded = loadedExtentsRtree.forEachInExtent(extentToLoad,
-        /**
-         * @param {{extent: ol.Extent}} object Object.
-         * @return {boolean} Contains.
-         */
-        function(object) {
-          return ol.extent.containsExtent(object.extent, extentToLoad);
-        });
-    if (!alreadyLoaded) {
-      this.loader_.call(this, extentToLoad, resolution, projection);
-      loadedExtentsRtree.insert(extentToLoad, {extent: extentToLoad.slice()});
-    }
-  }
-};
-
-
-/**
- * Remove a single feature from the source.  If you want to remove all features
- * at once, use the {@link ol.source.Vector#clear source.clear()} method
- * instead.
- * @param {ol.Feature} feature Feature to remove.
- * @api stable
- */
-ol.source.Vector.prototype.removeFeature = function(feature) {
-  var featureKey = ol.getUid(feature).toString();
-  if (featureKey in this.nullGeometryFeatures_) {
-    delete this.nullGeometryFeatures_[featureKey];
-  } else {
-    if (this.featuresRtree_) {
-      this.featuresRtree_.remove(feature);
-    }
-  }
-  this.removeFeatureInternal(feature);
-  this.changed();
-};
-
-
-/**
- * Remove feature without firing a `change` event.
- * @param {ol.Feature} feature Feature.
- * @protected
- */
-ol.source.Vector.prototype.removeFeatureInternal = function(feature) {
-  var featureKey = ol.getUid(feature).toString();
-  ol.DEBUG && console.assert(featureKey in this.featureChangeKeys_,
-      'featureKey exists in featureChangeKeys');
-  this.featureChangeKeys_[featureKey].forEach(ol.events.unlistenByKey);
-  delete this.featureChangeKeys_[featureKey];
-  var id = feature.getId();
-  if (id !== undefined) {
-    delete this.idIndex_[id.toString()];
-  } else {
-    delete this.undefIdIndex_[featureKey];
-  }
-  this.dispatchEvent(new ol.source.Vector.Event(
-      ol.source.Vector.EventType.REMOVEFEATURE, feature));
-};
-
-
-/**
- * Remove a feature from the id index.  Called internally when the feature id
- * may have changed.
- * @param {ol.Feature} feature The feature.
- * @return {boolean} Removed the feature from the index.
- * @private
- */
-ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) {
-  var removed = false;
-  for (var id in this.idIndex_) {
-    if (this.idIndex_[id] === feature) {
-      delete this.idIndex_[id];
-      removed = true;
-      break;
-    }
-  }
-  return removed;
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.source.Vector} instances are instances of this
- * type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.source.Vector.Event}
- * @param {string} type Type.
- * @param {ol.Feature=} opt_feature Feature.
- */
-ol.source.Vector.Event = function(type, opt_feature) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The feature being added or removed.
-   * @type {ol.Feature|undefined}
-   * @api stable
-   */
-  this.feature = opt_feature;
-
-};
-ol.inherits(ol.source.Vector.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.source.Vector.EventType = {
-  /**
-   * Triggered when a feature is added to the source.
-   * @event ol.source.Vector.Event#addfeature
-   * @api stable
-   */
-  ADDFEATURE: 'addfeature',
-
-  /**
-   * Triggered when a feature is updated.
-   * @event ol.source.Vector.Event#changefeature
-   * @api
-   */
-  CHANGEFEATURE: 'changefeature',
-
-  /**
-   * Triggered when the clear method is called on the source.
-   * @event ol.source.Vector.Event#clear
-   * @api
-   */
-  CLEAR: 'clear',
-
-  /**
-   * Triggered when a feature is removed from the source.
-   * See {@link ol.source.Vector#clear source.clear()} for exceptions.
-   * @event ol.source.Vector.Event#removefeature
-   * @api stable
-   */
-  REMOVEFEATURE: 'removefeature'
-};
-
-goog.provide('ol.interaction.Draw');
-
-goog.require('ol');
-goog.require('ol.events');
-goog.require('ol.extent');
-goog.require('ol.events.Event');
-goog.require('ol.Feature');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.Object');
-goog.require('ol.coordinate');
-goog.require('ol.functions');
-goog.require('ol.events.condition');
-goog.require('ol.geom.Circle');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.layer.Vector');
-goog.require('ol.source.Vector');
-goog.require('ol.style.Style');
-
-
-/**
- * @classdesc
- * Interaction for drawing feature geometries.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.interaction.Draw.Event
- * @param {olx.interaction.DrawOptions} options Options.
- * @api stable
- */
-ol.interaction.Draw = function(options) {
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.Draw.handleDownEvent_,
-    handleEvent: ol.interaction.Draw.handleEvent,
-    handleUpEvent: ol.interaction.Draw.handleUpEvent_
-  });
-
-  /**
-   * @type {ol.Pixel}
-   * @private
-   */
-  this.downPx_ = null;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.freehand_ = false;
-
-  /**
-   * Target source for drawn features.
-   * @type {ol.source.Vector}
-   * @private
-   */
-  this.source_ = options.source ? options.source : null;
-
-  /**
-   * Target collection for drawn features.
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
-   */
-  this.features_ = options.features ? options.features : null;
-
-  /**
-   * Pixel distance for snapping.
-   * @type {number}
-   * @private
-   */
-  this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12;
-
-  /**
-   * Geometry type.
-   * @type {ol.geom.GeometryType}
-   * @private
-   */
-  this.type_ = options.type;
-
-  /**
-   * Drawing mode (derived from geometry type.
-   * @type {ol.interaction.Draw.Mode}
-   * @private
-   */
-  this.mode_ = ol.interaction.Draw.getMode_(this.type_);
-
-  /**
-   * The number of points that must be drawn before a polygon ring or line
-   * string can be finished.  The default is 3 for polygon rings and 2 for
-   * line strings.
-   * @type {number}
-   * @private
-   */
-  this.minPoints_ = options.minPoints ?
-      options.minPoints :
-      (this.mode_ === ol.interaction.Draw.Mode.POLYGON ? 3 : 2);
-
-  /**
-   * The number of points that can be drawn before a polygon ring or line string
-   * is finished. The default is no restriction.
-   * @type {number}
-   * @private
-   */
-  this.maxPoints_ = options.maxPoints ? options.maxPoints : Infinity;
-
-  /**
-   * A function to decide if a potential finish coordinate is permissable
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.finishCondition_ = options.finishCondition ? options.finishCondition : ol.functions.TRUE;
-
-  var geometryFunction = options.geometryFunction;
-  if (!geometryFunction) {
-    if (this.type_ === ol.geom.GeometryType.CIRCLE) {
-      /**
-       * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
-       *     The coordinates.
-       * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry.
-       * @return {ol.geom.SimpleGeometry} A geometry.
-       */
-      geometryFunction = function(coordinates, opt_geometry) {
-        var circle = opt_geometry ? /** @type {ol.geom.Circle} */ (opt_geometry) :
-            new ol.geom.Circle([NaN, NaN]);
-        var squaredLength = ol.coordinate.squaredDistance(
-            coordinates[0], coordinates[1]);
-        circle.setCenterAndRadius(coordinates[0], Math.sqrt(squaredLength));
-        return circle;
-      };
-    } else {
-      var Constructor;
-      var mode = this.mode_;
-      if (mode === ol.interaction.Draw.Mode.POINT) {
-        Constructor = ol.geom.Point;
-      } else if (mode === ol.interaction.Draw.Mode.LINE_STRING) {
-        Constructor = ol.geom.LineString;
-      } else if (mode === ol.interaction.Draw.Mode.POLYGON) {
-        Constructor = ol.geom.Polygon;
-      }
-      /**
-       * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
-       *     The coordinates.
-       * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry.
-       * @return {ol.geom.SimpleGeometry} A geometry.
-       */
-      geometryFunction = function(coordinates, opt_geometry) {
-        var geometry = opt_geometry;
-        if (geometry) {
-          geometry.setCoordinates(coordinates);
-        } else {
-          geometry = new Constructor(coordinates);
-        }
-        return geometry;
-      };
-    }
-  }
-
-  /**
-   * @type {ol.DrawGeometryFunctionType}
-   * @private
-   */
-  this.geometryFunction_ = geometryFunction;
-
-  /**
-   * Finish coordinate for the feature (first point for polygons, last point for
-   * linestrings).
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.finishCoordinate_ = null;
-
-  /**
-   * Sketch feature.
-   * @type {ol.Feature}
-   * @private
-   */
-  this.sketchFeature_ = null;
-
-  /**
-   * Sketch point.
-   * @type {ol.Feature}
-   * @private
-   */
-  this.sketchPoint_ = null;
-
-  /**
-   * Sketch coordinates. Used when drawing a line or polygon.
-   * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>}
-   * @private
-   */
-  this.sketchCoords_ = null;
-
-  /**
-   * Sketch line. Used when drawing polygon.
-   * @type {ol.Feature}
-   * @private
-   */
-  this.sketchLine_ = null;
-
-  /**
-   * Sketch line coordinates. Used when drawing a polygon or circle.
-   * @type {Array.<ol.Coordinate>}
-   * @private
-   */
-  this.sketchLineCoords_ = null;
-
-  /**
-   * Squared tolerance for handling up events.  If the squared distance
-   * between a down and up event is greater than this tolerance, up events
-   * will not be handled.
-   * @type {number}
-   * @private
-   */
-  this.squaredClickTolerance_ = options.clickTolerance ?
-      options.clickTolerance * options.clickTolerance : 36;
-
-  /**
-   * Draw overlay where our sketch features are drawn.
-   * @type {ol.layer.Vector}
-   * @private
-   */
-  this.overlay_ = new ol.layer.Vector({
-    source: new ol.source.Vector({
-      useSpatialIndex: false,
-      wrapX: options.wrapX ? options.wrapX : false
-    }),
-    style: options.style ? options.style :
-        ol.interaction.Draw.getDefaultStyleFunction()
-  });
-
-  /**
-   * Name of the geometry attribute for newly created features.
-   * @type {string|undefined}
-   * @private
-   */
-  this.geometryName_ = options.geometryName;
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.noModifierKeys;
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.freehandCondition_;
-  if (options.freehand) {
-    this.freehandCondition_ = ol.events.condition.always;
-  } else {
-    this.freehandCondition_ = options.freehandCondition ?
-        options.freehandCondition : ol.events.condition.shiftKeyOnly;
-  }
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.interaction.Interaction.Property.ACTIVE),
-      this.updateState_, this);
-
-};
-ol.inherits(ol.interaction.Draw, ol.interaction.Pointer);
-
-
-/**
- * @return {ol.StyleFunction} Styles.
- */
-ol.interaction.Draw.getDefaultStyleFunction = function() {
-  var styles = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return styles[feature.getGeometry().getType()];
-  };
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.Draw.prototype.setMap = function(map) {
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
-  this.updateState_();
-};
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} and may actually
- * draw or finish the drawing.
- * @param {ol.MapBrowserEvent} event Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.Draw}
- * @api
- */
-ol.interaction.Draw.handleEvent = function(event) {
-  this.freehand_ = this.mode_ !== ol.interaction.Draw.Mode.POINT && this.freehandCondition_(event);
-  var pass = !this.freehand_;
-  if (this.freehand_ &&
-      event.type === ol.MapBrowserEvent.EventType.POINTERDRAG && this.sketchFeature_ !== null) {
-    this.addToDrawing_(event);
-    pass = false;
-  } else if (event.type ===
-      ol.MapBrowserEvent.EventType.POINTERMOVE) {
-    pass = this.handlePointerMove_(event);
-  } else if (event.type === ol.MapBrowserEvent.EventType.DBLCLICK) {
-    pass = false;
-  }
-  return ol.interaction.Pointer.handleEvent.call(this, event) && pass;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Draw}
- * @private
- */
-ol.interaction.Draw.handleDownEvent_ = function(event) {
-  if (this.freehand_) {
-    this.downPx_ = event.pixel;
-    if (!this.finishCoordinate_) {
-      this.startDrawing_(event);
-    }
-    return true;
-  } else if (this.condition_(event)) {
-    this.downPx_ = event.pixel;
-    return true;
-  } else {
-    return false;
-  }
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Draw}
- * @private
- */
-ol.interaction.Draw.handleUpEvent_ = function(event) {
-  var downPx = this.downPx_;
-  var clickPx = event.pixel;
-  var dx = downPx[0] - clickPx[0];
-  var dy = downPx[1] - clickPx[1];
-  var squaredDistance = dx * dx + dy * dy;
-  var pass = true;
-  var shouldHandle = this.freehand_ ?
-      squaredDistance > this.squaredClickTolerance_ :
-      squaredDistance <= this.squaredClickTolerance_;
-  if (shouldHandle) {
-    this.handlePointerMove_(event);
-    if (!this.finishCoordinate_) {
-      this.startDrawing_(event);
-      if (this.mode_ === ol.interaction.Draw.Mode.POINT) {
-        this.finishDrawing();
-      }
-    } else if (this.freehand_ || this.mode_ === ol.interaction.Draw.Mode.CIRCLE) {
-      this.finishDrawing();
-    } else if (this.atFinish_(event)) {
-      if (this.finishCondition_(event)) {
-        this.finishDrawing();
-      }
-    } else {
-      this.addToDrawing_(event);
-    }
-    pass = false;
-  }
-  return pass;
-};
-
-
-/**
- * Handle move events.
- * @param {ol.MapBrowserEvent} event A move event.
- * @return {boolean} Pass the event to other interactions.
- * @private
- */
-ol.interaction.Draw.prototype.handlePointerMove_ = function(event) {
-  if (this.finishCoordinate_) {
-    this.modifyDrawing_(event);
-  } else {
-    this.createOrUpdateSketchPoint_(event);
-  }
-  return true;
-};
-
-
-/**
- * Determine if an event is within the snapping tolerance of the start coord.
- * @param {ol.MapBrowserEvent} event Event.
- * @return {boolean} The event is within the snapping tolerance of the start.
- * @private
- */
-ol.interaction.Draw.prototype.atFinish_ = function(event) {
-  var at = false;
-  if (this.sketchFeature_) {
-    var potentiallyDone = false;
-    var potentiallyFinishCoordinates = [this.finishCoordinate_];
-    if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) {
-      potentiallyDone = this.sketchCoords_.length > this.minPoints_;
-    } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) {
-      potentiallyDone = this.sketchCoords_[0].length >
-          this.minPoints_;
-      potentiallyFinishCoordinates = [this.sketchCoords_[0][0],
-        this.sketchCoords_[0][this.sketchCoords_[0].length - 2]];
-    }
-    if (potentiallyDone) {
-      var map = event.map;
-      for (var i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++) {
-        var finishCoordinate = potentiallyFinishCoordinates[i];
-        var finishPixel = map.getPixelFromCoordinate(finishCoordinate);
-        var pixel = event.pixel;
-        var dx = pixel[0] - finishPixel[0];
-        var dy = pixel[1] - finishPixel[1];
-        var snapTolerance = this.freehand_ ? 1 : this.snapTolerance_;
-        at = Math.sqrt(dx * dx + dy * dy) <= snapTolerance;
-        if (at) {
-          this.finishCoordinate_ = finishCoordinate;
-          break;
-        }
-      }
-    }
-  }
-  return at;
-};
-
-
-/**
- * @param {ol.MapBrowserEvent} event Event.
- * @private
- */
-ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) {
-  var coordinates = event.coordinate.slice();
-  if (!this.sketchPoint_) {
-    this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates));
-    this.updateSketchFeatures_();
-  } else {
-    var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry());
-    sketchPointGeom.setCoordinates(coordinates);
-  }
-};
-
-
-/**
- * Start the drawing.
- * @param {ol.MapBrowserEvent} event Event.
- * @private
- */
-ol.interaction.Draw.prototype.startDrawing_ = function(event) {
-  var start = event.coordinate;
-  this.finishCoordinate_ = start;
-  if (this.mode_ === ol.interaction.Draw.Mode.POINT) {
-    this.sketchCoords_ = start.slice();
-  } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) {
-    this.sketchCoords_ = [[start.slice(), start.slice()]];
-    this.sketchLineCoords_ = this.sketchCoords_[0];
-  } else {
-    this.sketchCoords_ = [start.slice(), start.slice()];
-    if (this.mode_ === ol.interaction.Draw.Mode.CIRCLE) {
-      this.sketchLineCoords_ = this.sketchCoords_;
-    }
-  }
-  if (this.sketchLineCoords_) {
-    this.sketchLine_ = new ol.Feature(
-        new ol.geom.LineString(this.sketchLineCoords_));
-  }
-  var geometry = this.geometryFunction_(this.sketchCoords_);
-  ol.DEBUG && console.assert(geometry !== undefined, 'geometry should be defined');
-  this.sketchFeature_ = new ol.Feature();
-  if (this.geometryName_) {
-    this.sketchFeature_.setGeometryName(this.geometryName_);
-  }
-  this.sketchFeature_.setGeometry(geometry);
-  this.updateSketchFeatures_();
-  this.dispatchEvent(new ol.interaction.Draw.Event(
-      ol.interaction.Draw.EventType.DRAWSTART, this.sketchFeature_));
-};
-
-
-/**
- * Modify the drawing.
- * @param {ol.MapBrowserEvent} event Event.
- * @private
- */
-ol.interaction.Draw.prototype.modifyDrawing_ = function(event) {
-  var coordinate = event.coordinate;
-  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
-  var coordinates, last;
-  if (this.mode_ === ol.interaction.Draw.Mode.POINT) {
-    last = this.sketchCoords_;
-  } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) {
-    coordinates = this.sketchCoords_[0];
-    last = coordinates[coordinates.length - 1];
-    if (this.atFinish_(event)) {
-      // snap to finish
-      coordinate = this.finishCoordinate_.slice();
-    }
-  } else {
-    coordinates = this.sketchCoords_;
-    last = coordinates[coordinates.length - 1];
-  }
-  last[0] = coordinate[0];
-  last[1] = coordinate[1];
-  ol.DEBUG && console.assert(this.sketchCoords_, 'sketchCoords_ expected');
-  this.geometryFunction_(
-      /** @type {!ol.Coordinate|!Array.<ol.Coordinate>|!Array.<Array.<ol.Coordinate>>} */ (this.sketchCoords_),
-      geometry);
-  if (this.sketchPoint_) {
-    var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry());
-    sketchPointGeom.setCoordinates(coordinate);
-  }
-  var sketchLineGeom;
-  if (geometry instanceof ol.geom.Polygon &&
-      this.mode_ !== ol.interaction.Draw.Mode.POLYGON) {
-    if (!this.sketchLine_) {
-      this.sketchLine_ = new ol.Feature(new ol.geom.LineString(null));
-    }
-    var ring = geometry.getLinearRing(0);
-    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
-    sketchLineGeom.setFlatCoordinates(
-        ring.getLayout(), ring.getFlatCoordinates());
-  } else if (this.sketchLineCoords_) {
-    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
-    sketchLineGeom.setCoordinates(this.sketchLineCoords_);
-  }
-  this.updateSketchFeatures_();
-};
-
-
-/**
- * Add a new coordinate to the drawing.
- * @param {ol.MapBrowserEvent} event Event.
- * @private
- */
-ol.interaction.Draw.prototype.addToDrawing_ = function(event) {
-  var coordinate = event.coordinate;
-  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
-  var done;
-  var coordinates;
-  if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) {
-    this.finishCoordinate_ = coordinate.slice();
-    coordinates = this.sketchCoords_;
-    if (coordinates.length >= this.maxPoints_) {
-      if (this.freehand_) {
-        coordinates.pop();
-      } else {
-        done = true;
-      }
-    }
-    coordinates.push(coordinate.slice());
-    this.geometryFunction_(coordinates, geometry);
-  } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) {
-    coordinates = this.sketchCoords_[0];
-    if (coordinates.length >= this.maxPoints_) {
-      if (this.freehand_) {
-        coordinates.pop();
-      } else {
-        done = true;
-      }
-    }
-    coordinates.push(coordinate.slice());
-    if (done) {
-      this.finishCoordinate_ = coordinates[0];
-    }
-    this.geometryFunction_(this.sketchCoords_, geometry);
-  }
-  this.updateSketchFeatures_();
-  if (done) {
-    this.finishDrawing();
-  }
-};
-
-
-/**
- * Remove last point of the feature currently being drawn.
- * @api
- */
-ol.interaction.Draw.prototype.removeLastPoint = function() {
-  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
-  var coordinates, sketchLineGeom;
-  if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) {
-    coordinates = this.sketchCoords_;
-    coordinates.splice(-2, 1);
-    this.geometryFunction_(coordinates, geometry);
-  } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) {
-    coordinates = this.sketchCoords_[0];
-    coordinates.splice(-2, 1);
-    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
-    sketchLineGeom.setCoordinates(coordinates);
-    this.geometryFunction_(this.sketchCoords_, geometry);
-  }
-
-  if (coordinates.length === 0) {
-    this.finishCoordinate_ = null;
-  }
-
-  this.updateSketchFeatures_();
-};
-
-
-/**
- * Stop drawing and add the sketch feature to the target layer.
- * The {@link ol.interaction.Draw.EventType.DRAWEND} event is dispatched before
- * inserting the feature.
- * @api
- */
-ol.interaction.Draw.prototype.finishDrawing = function() {
-  var sketchFeature = this.abortDrawing_();
-  var coordinates = this.sketchCoords_;
-  var geometry = /** @type {ol.geom.SimpleGeometry} */ (sketchFeature.getGeometry());
-  if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) {
-    // remove the redundant last point
-    coordinates.pop();
-    this.geometryFunction_(coordinates, geometry);
-  } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) {
-    // When we finish drawing a polygon on the last point,
-    // the last coordinate is duplicated as for LineString
-    // we force the replacement by the first point
-    coordinates[0].pop();
-    coordinates[0].push(coordinates[0][0]);
-    this.geometryFunction_(coordinates, geometry);
-  }
-
-  // cast multi-part geometries
-  if (this.type_ === ol.geom.GeometryType.MULTI_POINT) {
-    sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates]));
-  } else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING) {
-    sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates]));
-  } else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON) {
-    sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates]));
-  }
-
-  // First dispatch event to allow full set up of feature
-  this.dispatchEvent(new ol.interaction.Draw.Event(
-      ol.interaction.Draw.EventType.DRAWEND, sketchFeature));
-
-  // Then insert feature
-  if (this.features_) {
-    this.features_.push(sketchFeature);
-  }
-  if (this.source_) {
-    this.source_.addFeature(sketchFeature);
-  }
-};
-
-
-/**
- * Stop drawing without adding the sketch feature to the target layer.
- * @return {ol.Feature} The sketch feature (or null if none).
- * @private
- */
-ol.interaction.Draw.prototype.abortDrawing_ = function() {
-  this.finishCoordinate_ = null;
-  var sketchFeature = this.sketchFeature_;
-  if (sketchFeature) {
-    this.sketchFeature_ = null;
-    this.sketchPoint_ = null;
-    this.sketchLine_ = null;
-    this.overlay_.getSource().clear(true);
-  }
-  return sketchFeature;
-};
-
-
-/**
- * Extend an existing geometry by adding additional points. This only works
- * on features with `LineString` geometries, where the interaction will
- * extend lines by adding points to the end of the coordinates array.
- * @param {!ol.Feature} feature Feature to be extended.
- * @api
- */
-ol.interaction.Draw.prototype.extend = function(feature) {
-  var geometry = feature.getGeometry();
-  ol.DEBUG && console.assert(this.mode_ == ol.interaction.Draw.Mode.LINE_STRING,
-      'interaction mode must be "line"');
-  ol.DEBUG && console.assert(geometry.getType() == ol.geom.GeometryType.LINE_STRING,
-      'feature geometry must be a line string');
-  var lineString = /** @type {ol.geom.LineString} */ (geometry);
-  this.sketchFeature_ = feature;
-  this.sketchCoords_ = lineString.getCoordinates();
-  var last = this.sketchCoords_[this.sketchCoords_.length - 1];
-  this.finishCoordinate_ = last.slice();
-  this.sketchCoords_.push(last.slice());
-  this.updateSketchFeatures_();
-  this.dispatchEvent(new ol.interaction.Draw.Event(
-      ol.interaction.Draw.EventType.DRAWSTART, this.sketchFeature_));
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.Draw.prototype.shouldStopEvent = ol.functions.FALSE;
-
-
-/**
- * Redraw the sketch features.
- * @private
- */
-ol.interaction.Draw.prototype.updateSketchFeatures_ = function() {
-  var sketchFeatures = [];
-  if (this.sketchFeature_) {
-    sketchFeatures.push(this.sketchFeature_);
-  }
-  if (this.sketchLine_) {
-    sketchFeatures.push(this.sketchLine_);
-  }
-  if (this.sketchPoint_) {
-    sketchFeatures.push(this.sketchPoint_);
-  }
-  var overlaySource = this.overlay_.getSource();
-  overlaySource.clear(true);
-  overlaySource.addFeatures(sketchFeatures);
-};
-
-
-/**
- * @private
- */
-ol.interaction.Draw.prototype.updateState_ = function() {
-  var map = this.getMap();
-  var active = this.getActive();
-  if (!map || !active) {
-    this.abortDrawing_();
-  }
-  this.overlay_.setMap(active ? map : null);
-};
-
-
-/**
- * Create a `geometryFunction` for `type: 'Circle'` that will create a regular
- * polygon with a user specified number of sides and start angle instead of an
- * `ol.geom.Circle` geometry.
- * @param {number=} opt_sides Number of sides of the regular polygon. Default is
- *     32.
- * @param {number=} opt_angle Angle of the first point in radians. 0 means East.
- *     Default is the angle defined by the heading from the center of the
- *     regular polygon to the current pointer position.
- * @return {ol.DrawGeometryFunctionType} Function that draws a
- *     polygon.
- * @api
- */
-ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) {
-  return (
-      /**
-       * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
-       * @param {ol.geom.SimpleGeometry=} opt_geometry
-       * @return {ol.geom.SimpleGeometry}
-       */
-      function(coordinates, opt_geometry) {
-        var center = coordinates[0];
-        var end = coordinates[1];
-        var radius = Math.sqrt(
-            ol.coordinate.squaredDistance(center, end));
-        var geometry = opt_geometry ? /** @type {ol.geom.Polygon} */ (opt_geometry) :
-            ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), opt_sides);
-        var angle = opt_angle ? opt_angle :
-            Math.atan((end[1] - center[1]) / (end[0] - center[0]));
-        ol.geom.Polygon.makeRegular(geometry, center, radius, angle);
-        return geometry;
-      }
-  );
-};
-
-
-/**
- * Create a `geometryFunction` that will create a box-shaped polygon (aligned
- * with the coordinate system axes).  Use this with the draw interaction and
- * `type: 'Circle'` to return a box instead of a circle geometry.
- * @return {ol.DrawGeometryFunctionType} Function that draws a box-shaped polygon.
- * @api
- */
-ol.interaction.Draw.createBox = function() {
-  return (
-    /**
-     * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
-     * @param {ol.geom.SimpleGeometry=} opt_geometry
-     * @return {ol.geom.SimpleGeometry}
-     */
-    function(coordinates, opt_geometry) {
-      var extent = ol.extent.boundingExtent(coordinates);
-      var geometry = opt_geometry || new ol.geom.Polygon(null);
-      geometry.setCoordinates([[
-        ol.extent.getBottomLeft(extent),
-        ol.extent.getBottomRight(extent),
-        ol.extent.getTopRight(extent),
-        ol.extent.getTopLeft(extent),
-        ol.extent.getBottomLeft(extent)
-      ]]);
-      return geometry;
-    }
-  );
-};
-
-
-/**
- * Get the drawing mode.  The mode for mult-part geometries is the same as for
- * their single-part cousins.
- * @param {ol.geom.GeometryType} type Geometry type.
- * @return {ol.interaction.Draw.Mode} Drawing mode.
- * @private
- */
-ol.interaction.Draw.getMode_ = function(type) {
-  var mode;
-  if (type === ol.geom.GeometryType.POINT ||
-      type === ol.geom.GeometryType.MULTI_POINT) {
-    mode = ol.interaction.Draw.Mode.POINT;
-  } else if (type === ol.geom.GeometryType.LINE_STRING ||
-      type === ol.geom.GeometryType.MULTI_LINE_STRING) {
-    mode = ol.interaction.Draw.Mode.LINE_STRING;
-  } else if (type === ol.geom.GeometryType.POLYGON ||
-      type === ol.geom.GeometryType.MULTI_POLYGON) {
-    mode = ol.interaction.Draw.Mode.POLYGON;
-  } else if (type === ol.geom.GeometryType.CIRCLE) {
-    mode = ol.interaction.Draw.Mode.CIRCLE;
-  }
-  return /** @type {!ol.interaction.Draw.Mode} */ (mode);
-};
-
-
-/**
- * Draw mode.  This collapses multi-part geometry types with their single-part
- * cousins.
- * @enum {string}
- */
-ol.interaction.Draw.Mode = {
-  POINT: 'Point',
-  LINE_STRING: 'LineString',
-  POLYGON: 'Polygon',
-  CIRCLE: 'Circle'
-};
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.Draw} instances are instances of
- * this type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.DrawEvent}
- * @param {ol.interaction.Draw.EventType} type Type.
- * @param {ol.Feature} feature The feature drawn.
- */
-ol.interaction.Draw.Event = function(type, feature) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The feature being drawn.
-   * @type {ol.Feature}
-   * @api stable
-   */
-  this.feature = feature;
-
-};
-ol.inherits(ol.interaction.Draw.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.interaction.Draw.EventType = {
-  /**
-   * Triggered upon feature draw start
-   * @event ol.interaction.Draw.Event#drawstart
-   * @api stable
-   */
-  DRAWSTART: 'drawstart',
-  /**
-   * Triggered upon feature draw end
-   * @event ol.interaction.Draw.Event#drawend
-   * @api stable
-   */
-  DRAWEND: 'drawend'
-};
-
-goog.provide('ol.interaction.Extent');
-
-goog.require('ol');
-goog.require('ol.Feature');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserPointerEvent');
-goog.require('ol.coordinate');
-goog.require('ol.events.Event');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.layer.Vector');
-goog.require('ol.source.Vector');
-goog.require('ol.style.Style');
-
-
-/**
- * @classdesc
- * Allows the user to draw a vector box by clicking and dragging on the map.
- * Once drawn, the vector box can be modified by dragging its vertices or edges.
- * This interaction is only supported for mouse devices.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.interaction.Extent.Event
- * @param {olx.interaction.ExtentOptions=} opt_options Options.
- * @api
- */
-ol.interaction.Extent = function(opt_options) {
-
-  /**
-   * Extent of the drawn box
-   * @type {ol.Extent}
-   * @private
-   */
-  this.extent_ = null;
-
-  /**
-   * Handler for pointer move events
-   * @type {function (ol.Coordinate): ol.Extent|null}
-   * @private
-   */
-  this.pointerHandler_ = null;
-
-  /**
-   * Pixel threshold to snap to extent
-   * @type {number}
-   * @private
-   */
-  this.pixelTolerance_ = 10;
-
-  /**
-   * Is the pointer snapped to an extent vertex
-   * @type {boolean}
-   * @private
-   */
-  this.snappedToVertex_ = false;
-
-  /**
-   * Feature for displaying the visible extent
-   * @type {ol.Feature}
-   * @private
-   */
-  this.extentFeature_ = null;
-
-  /**
-   * Feature for displaying the visible pointer
-   * @type {ol.Feature}
-   * @private
-   */
-  this.vertexFeature_ = null;
-
-  if (!opt_options) {
-    opt_options = {};
-  }
-
-  if (opt_options.extent) {
-    this.setExtent(opt_options.extent);
-  }
-
-  /* Inherit ol.interaction.Pointer */
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.Extent.handleDownEvent_,
-    handleDragEvent: ol.interaction.Extent.handleDragEvent_,
-    handleEvent: ol.interaction.Extent.handleEvent_,
-    handleUpEvent: ol.interaction.Extent.handleUpEvent_
-  });
-
-  /**
-   * Layer for the extentFeature
-   * @type {ol.layer.Vector}
-   * @private
-   */
-  this.extentOverlay_ = new ol.layer.Vector({
-    source: new ol.source.Vector({
-      useSpatialIndex: false,
-      wrapX: !!opt_options.wrapX
-    }),
-    style: opt_options.boxStyle ? opt_options.boxStyle : ol.interaction.Extent.getDefaultExtentStyleFunction_(),
-    updateWhileAnimating: true,
-    updateWhileInteracting: true
-  });
-
-  /**
-   * Layer for the vertexFeature
-   * @type {ol.layer.Vector}
-   * @private
-   */
-  this.vertexOverlay_ = new ol.layer.Vector({
-    source: new ol.source.Vector({
-      useSpatialIndex: false,
-      wrapX: !!opt_options.wrapX
-    }),
-    style: opt_options.pointerStyle ? opt_options.pointerStyle : ol.interaction.Extent.getDefaultPointerStyleFunction_(),
-    updateWhileAnimating: true,
-    updateWhileInteracting: true
-  });
-};
-
-ol.inherits(ol.interaction.Extent, ol.interaction.Pointer);
-
-/**
- * @param {ol.MapBrowserEvent} mapBrowserEvent Event.
- * @return {boolean} Propagate event?
- * @this {ol.interaction.Extent}
- * @private
- */
-ol.interaction.Extent.handleEvent_ = function(mapBrowserEvent) {
-  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
-    return true;
-  }
-  //display pointer (if not dragging)
-  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE && !this.handlingDownUpSequence) {
-    this.handlePointerMove_(mapBrowserEvent);
-  }
-  //call pointer to determine up/down/drag
-  ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent);
-  //return false to stop propagation
-  return false;
-};
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Event handled?
- * @this {ol.interaction.Extent}
- * @private
- */
-ol.interaction.Extent.handleDownEvent_ = function(mapBrowserEvent) {
-  var pixel = mapBrowserEvent.pixel;
-  var map = mapBrowserEvent.map;
-
-  var extent = this.getExtent();
-  var vertex = this.snapToVertex_(pixel, map);
-
-  //find the extent corner opposite the passed corner
-  var getOpposingPoint = function(point) {
-    var x_ = null;
-    var y_ = null;
-    if (point[0] == extent[0]) {
-      x_ = extent[2];
-    } else if (point[0] == extent[2]) {
-      x_ = extent[0];
-    }
-    if (point[1] == extent[1]) {
-      y_ = extent[3];
-    } else if (point[1] == extent[3]) {
-      y_ = extent[1];
-    }
-    if (x_ !== null && y_ !== null) {
-      return [x_, y_];
-    }
-    return null;
-  };
-  if (vertex && extent) {
-    var x = (vertex[0] == extent[0] || vertex[0] == extent[2]) ? vertex[0] : null;
-    var y = (vertex[1] == extent[1] || vertex[1] == extent[3]) ? vertex[1] : null;
-
-    //snap to point
-    if (x !== null && y !== null) {
-      this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(getOpposingPoint(vertex));
-    //snap to edge
-    } else if (x !== null) {
-      this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_(
-        getOpposingPoint([x, extent[1]]),
-        getOpposingPoint([x, extent[3]])
-      );
-    } else if (y !== null) {
-      this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_(
-        getOpposingPoint([extent[0], y]),
-        getOpposingPoint([extent[2], y])
-      );
-    }
-  //no snap - new bbox
-  } else {
-    vertex = map.getCoordinateFromPixel(pixel);
-    this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]);
-    this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(vertex);
-  }
-  return true; //event handled; start downup sequence
-};
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Event handled?
- * @this {ol.interaction.Extent}
- * @private
- */
-ol.interaction.Extent.handleDragEvent_ = function(mapBrowserEvent) {
-  if (this.pointerHandler_) {
-    var pixelCoordinate = mapBrowserEvent.coordinate;
-    this.setExtent(this.pointerHandler_(pixelCoordinate));
-    this.createOrUpdatePointerFeature_(pixelCoordinate);
-  }
-  return true;
-};
-
-/**
- * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Extent}
- * @private
- */
-ol.interaction.Extent.handleUpEvent_ = function(mapBrowserEvent) {
-  this.pointerHandler_ = null;
-  //If bbox is zero area, set to null;
-  var extent = this.getExtent();
-  if (!extent || ol.extent.getArea(extent) === 0) {
-    this.setExtent(null);
-  }
-  return false; //Stop handling downup sequence
-};
-
-/**
- * Returns the default style for the drawn bbox
- *
- * @return {ol.StyleFunction} Default Extent style
- * @private
- */
-ol.interaction.Extent.getDefaultExtentStyleFunction_ = function() {
-  var style = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POLYGON];
-  };
-};
-
-/**
- * Returns the default style for the pointer
- *
- * @return {ol.StyleFunction} Default pointer style
- * @private
- */
-ol.interaction.Extent.getDefaultPointerStyleFunction_ = function() {
-  var style = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POINT];
-  };
-};
-
-/**
- * @param {ol.Coordinate} fixedPoint corner that will be unchanged in the new extent
- * @returns {function (ol.Coordinate): ol.Extent} event handler
- * @private
- */
-ol.interaction.Extent.getPointHandler_ = function(fixedPoint) {
-  return function(point) {
-    return ol.extent.boundingExtent([fixedPoint, point]);
-  };
-};
-
-/**
- * @param {ol.Coordinate} fixedP1 first corner that will be unchanged in the new extent
- * @param {ol.Coordinate} fixedP2 second corner that will be unchanged in the new extent
- * @returns {function (ol.Coordinate): ol.Extent|null} event handler
- * @private
- */
-ol.interaction.Extent.getEdgeHandler_ = function(fixedP1, fixedP2) {
-  if (fixedP1[0] == fixedP2[0]) {
-    return function(point) {
-      return ol.extent.boundingExtent([fixedP1, [point[0], fixedP2[1]]]);
-    };
-  } else if (fixedP1[1] == fixedP2[1]) {
-    return function(point) {
-      return ol.extent.boundingExtent([fixedP1, [fixedP2[0], point[1]]]);
-    };
-  } else {
-    return null;
-  }
-};
-
-/**
- * @param {ol.Extent} extent extent
- * @returns {Array<Array<ol.Coordinate>>} extent line segments
- * @private
- */
-ol.interaction.Extent.getSegments_ = function(extent) {
-  return [
-    [[extent[0], extent[1]], [extent[0], extent[3]]],
-    [[extent[0], extent[3]], [extent[2], extent[3]]],
-    [[extent[2], extent[3]], [extent[2], extent[1]]],
-    [[extent[2], extent[1]], [extent[0], extent[1]]]
-  ];
-};
-
-/**
- * @param {ol.Pixel} pixel cursor location
- * @param {ol.Map} map map
- * @returns {ol.Coordinate|null} snapped vertex on extent
- * @private
- */
-ol.interaction.Extent.prototype.snapToVertex_ = function(pixel, map) {
-  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
-  var sortByDistance = function(a, b) {
-    return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a) -
-        ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b);
-  };
-  var extent = this.getExtent();
-  if (extent) {
-    //convert extents to line segments and find the segment closest to pixelCoordinate
-    var segments = ol.interaction.Extent.getSegments_(extent);
-    segments.sort(sortByDistance);
-    var closestSegment = segments[0];
-
-    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
-        closestSegment));
-    var vertexPixel = map.getPixelFromCoordinate(vertex);
-
-    //if the distance is within tolerance, snap to the segment
-    if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
-        this.pixelTolerance_) {
-
-      //test if we should further snap to a vertex
-      var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
-      var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
-      var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
-      var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
-      var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
-      this.snappedToVertex_ = dist <= this.pixelTolerance_;
-      if (this.snappedToVertex_) {
-        vertex = squaredDist1 > squaredDist2 ?
-            closestSegment[1] : closestSegment[0];
-      }
-      return vertex;
-    }
-  }
-  return null;
-};
-
-/**
- * @param {ol.MapBrowserEvent} mapBrowserEvent pointer move event
- * @private
- */
-ol.interaction.Extent.prototype.handlePointerMove_ = function(mapBrowserEvent) {
-  var pixel = mapBrowserEvent.pixel;
-  var map = mapBrowserEvent.map;
-
-  var vertex = this.snapToVertex_(pixel, map);
-  if (!vertex) {
-    vertex = map.getCoordinateFromPixel(pixel);
-  }
-  this.createOrUpdatePointerFeature_(vertex);
-};
-
-/**
- * @param {ol.Extent} extent extent
- * @returns {ol.Feature} extent as featrue
- * @private
- */
-ol.interaction.Extent.prototype.createOrUpdateExtentFeature_ = function(extent) {
-  var extentFeature = this.extentFeature_;
-
-  if (!extentFeature) {
-    if (!extent) {
-      extentFeature = new ol.Feature({});
-    } else {
-      extentFeature = new ol.Feature(ol.geom.Polygon.fromExtent(extent));
-    }
-    this.extentFeature_ = extentFeature;
-    this.extentOverlay_.getSource().addFeature(extentFeature);
-  } else {
-    if (!extent) {
-      extentFeature.setGeometry(undefined);
-    } else {
-      extentFeature.setGeometry(ol.geom.Polygon.fromExtent(extent));
-    }
-  }
-  return extentFeature;
-};
-
-
-/**
- * @param {ol.Coordinate} vertex location of feature
- * @returns {ol.Feature} vertex as feature
- * @private
- */
-ol.interaction.Extent.prototype.createOrUpdatePointerFeature_ = function(vertex) {
-  var vertexFeature = this.vertexFeature_;
-  if (!vertexFeature) {
-    vertexFeature = new ol.Feature(new ol.geom.Point(vertex));
-    this.vertexFeature_ = vertexFeature;
-    this.vertexOverlay_.getSource().addFeature(vertexFeature);
-  } else {
-    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
-    geometry.setCoordinates(vertex);
-  }
-  return vertexFeature;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.Extent.prototype.setMap = function(map) {
-  this.extentOverlay_.setMap(map);
-  this.vertexOverlay_.setMap(map);
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
-};
-
-/**
- * Returns the current drawn extent in the view projection
- *
- * @return {ol.Extent} Drawn extent in the view projection.
- * @api
- */
-ol.interaction.Extent.prototype.getExtent = function() {
-  return this.extent_;
-};
-
-/**
- * Manually sets the drawn extent, using the view projection.
- *
- * @param {ol.Extent} extent Extent
- * @api
- */
-ol.interaction.Extent.prototype.setExtent = function(extent) {
-  //Null extent means no bbox
-  this.extent_ = extent ? extent : null;
-  this.createOrUpdateExtentFeature_(extent);
-  this.dispatchEvent(new ol.interaction.Extent.Event(this.extent_));
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.Extent} instances are instances of
- * this type.
- *
- * @constructor
- * @param {ol.Extent} extent the new extent
- * @extends {ol.events.Event}
- */
-ol.interaction.Extent.Event = function(extent) {
-  ol.events.Event.call(this, ol.interaction.Extent.EventType.EXTENTCHANGED);
-
-  /**
-   * The current extent.
-   * @type {ol.Extent}
-   * @api
-   */
-  this.extent_ = extent;
-};
-ol.inherits(ol.interaction.Extent.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.interaction.Extent.EventType = {
-  /**
-   * Triggered after the extent is changed
-   * @event ol.interaction.Extent.Event
-   * @api
-   */
-  EXTENTCHANGED: 'extentchanged'
-};
-
-goog.provide('ol.interaction.Modify');
-
-goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.Feature');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserPointerEvent');
-goog.require('ol.View');
-goog.require('ol.array');
-goog.require('ol.coordinate');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.EventType');
-goog.require('ol.events.condition');
-goog.require('ol.extent');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.Point');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.layer.Vector');
-goog.require('ol.source.Vector');
-goog.require('ol.structs.RBush');
-goog.require('ol.style.Style');
-
-
-/**
- * @classdesc
- * Interaction for modifying feature geometries.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.ModifyOptions} options Options.
- * @fires ol.interaction.Modify.Event
- * @api
- */
-ol.interaction.Modify = function(options) {
-
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.Modify.handleDownEvent_,
-    handleDragEvent: ol.interaction.Modify.handleDragEvent_,
-    handleEvent: ol.interaction.Modify.handleEvent,
-    handleUpEvent: ol.interaction.Modify.handleUpEvent_
-  });
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.primaryAction;
-
-
-  /**
-   * @private
-   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
-   * @return {boolean} Combined condition result.
-   */
-  this.defaultDeleteCondition_ = function(mapBrowserEvent) {
-    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
-      ol.events.condition.singleClick(mapBrowserEvent);
-  };
-
-  /**
-   * @type {ol.EventsConditionType}
-   * @private
-   */
-  this.deleteCondition_ = options.deleteCondition ?
-      options.deleteCondition : this.defaultDeleteCondition_;
-
-  /**
-   * Editing vertex.
-   * @type {ol.Feature}
-   * @private
-   */
-  this.vertexFeature_ = null;
-
-  /**
-   * Segments intersecting {@link this.vertexFeature_} by segment uid.
-   * @type {Object.<string, boolean>}
-   * @private
-   */
-  this.vertexSegments_ = null;
-
-  /**
-   * @type {ol.Pixel}
-   * @private
-   */
-  this.lastPixel_ = [0, 0];
-
-  /**
-   * Tracks if the next `singleclick` event should be ignored to prevent
-   * accidental deletion right after vertex creation.
-   * @type {boolean}
-   * @private
-   */
-  this.ignoreNextSingleClick_ = false;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.modified_ = false;
-
-  /**
-   * Segment RTree for each layer
-   * @type {ol.structs.RBush.<ol.ModifySegmentDataType>}
-   * @private
-   */
-  this.rBush_ = new ol.structs.RBush();
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
-      options.pixelTolerance : 10;
-
-  /**
-   * @type {boolean}
-   * @private
-   */
-  this.snappedToVertex_ = false;
-
-  /**
-   * Indicate whether the interaction is currently changing a feature's
-   * coordinates.
-   * @type {boolean}
-   * @private
-   */
-  this.changingFeature_ = false;
-
-  /**
-   * @type {Array}
-   * @private
-   */
-  this.dragSegments_ = [];
-
-  /**
-   * Draw overlay where sketch features are drawn.
-   * @type {ol.layer.Vector}
-   * @private
-   */
-  this.overlay_ = new ol.layer.Vector({
-    source: new ol.source.Vector({
-      useSpatialIndex: false,
-      wrapX: !!options.wrapX
-    }),
-    style: options.style ? options.style :
-        ol.interaction.Modify.getDefaultStyleFunction(),
-    updateWhileAnimating: true,
-    updateWhileInteracting: true
-  });
-
-  /**
-  * @const
-  * @private
-  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>}
-  */
-  this.SEGMENT_WRITERS_ = {
-    'Point': this.writePointGeometry_,
-    'LineString': this.writeLineStringGeometry_,
-    'LinearRing': this.writeLineStringGeometry_,
-    'Polygon': this.writePolygonGeometry_,
-    'MultiPoint': this.writeMultiPointGeometry_,
-    'MultiLineString': this.writeMultiLineStringGeometry_,
-    'MultiPolygon': this.writeMultiPolygonGeometry_,
-    'GeometryCollection': this.writeGeometryCollectionGeometry_
-  };
-
-  /**
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
-   */
-  this.features_ = options.features;
-
-  this.features_.forEach(this.addFeature_, this);
-  ol.events.listen(this.features_, ol.Collection.EventType.ADD,
-      this.handleFeatureAdd_, this);
-  ol.events.listen(this.features_, ol.Collection.EventType.REMOVE,
-      this.handleFeatureRemove_, this);
-
-  /**
-   * @type {ol.MapBrowserPointerEvent}
-   * @private
-   */
-  this.lastPointerEvent_ = null;
-
-};
-ol.inherits(ol.interaction.Modify, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.interaction.Modify.prototype.addFeature_ = function(feature) {
-  var geometry = feature.getGeometry();
-  if (geometry && geometry.getType() in this.SEGMENT_WRITERS_) {
-    this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry);
-  }
-  var map = this.getMap();
-  if (map) {
-    this.handlePointerAtPixel_(this.lastPixel_, map);
-  }
-  ol.events.listen(feature, ol.events.EventType.CHANGE,
-      this.handleFeatureChange_, this);
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} evt Map browser event
- * @private
- */
-ol.interaction.Modify.prototype.willModifyFeatures_ = function(evt) {
-  if (!this.modified_) {
-    this.modified_ = true;
-    this.dispatchEvent(new ol.interaction.Modify.Event(
-        ol.interaction.Modify.EventType.MODIFYSTART, this.features_, evt));
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.interaction.Modify.prototype.removeFeature_ = function(feature) {
-  this.removeFeatureSegmentData_(feature);
-  // Remove the vertex feature if the collection of canditate features
-  // is empty.
-  if (this.vertexFeature_ && this.features_.getLength() === 0) {
-    this.overlay_.getSource().removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
-  }
-  ol.events.unlisten(feature, ol.events.EventType.CHANGE,
-      this.handleFeatureChange_, this);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.interaction.Modify.prototype.removeFeatureSegmentData_ = function(feature) {
-  var rBush = this.rBush_;
-  var /** @type {Array.<ol.ModifySegmentDataType>} */ nodesToRemove = [];
-  rBush.forEach(
-      /**
-       * @param {ol.ModifySegmentDataType} node RTree node.
-       */
-      function(node) {
-        if (feature === node.feature) {
-          nodesToRemove.push(node);
-        }
-      });
-  for (var i = nodesToRemove.length - 1; i >= 0; --i) {
-    rBush.remove(nodesToRemove[i]);
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.Modify.prototype.setActive = function(active) {
-  if (this.vertexFeature_ && !active) {
-    this.overlay_.getSource().removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
-  }
-  ol.interaction.Pointer.prototype.setActive.call(this, active);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.Modify.prototype.setMap = function(map) {
-  this.overlay_.setMap(map);
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
-};
-
-
-/**
- * @param {ol.Collection.Event} evt Event.
- * @private
- */
-ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) {
-  this.addFeature_(/** @type {ol.Feature} */ (evt.element));
-};
-
-
-/**
- * @param {ol.events.Event} evt Event.
- * @private
- */
-ol.interaction.Modify.prototype.handleFeatureChange_ = function(evt) {
-  if (!this.changingFeature_) {
-    var feature = /** @type {ol.Feature} */ (evt.target);
-    this.removeFeature_(feature);
-    this.addFeature_(feature);
-  }
-};
-
-
-/**
- * @param {ol.Collection.Event} evt Event.
- * @private
- */
-ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
-  var feature = /** @type {ol.Feature} */ (evt.element);
-  this.removeFeature_(feature);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Point} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writePointGeometry_ = function(feature, geometry) {
-  var coordinates = geometry.getCoordinates();
-  var segmentData = /** @type {ol.ModifySegmentDataType} */ ({
-    feature: feature,
-    geometry: geometry,
-    segment: [coordinates, coordinates]
-  });
-  this.rBush_.insert(geometry.getExtent(), segmentData);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPoint} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writeMultiPointGeometry_ = function(feature, geometry) {
-  var points = geometry.getCoordinates();
-  var coordinates, i, ii, segmentData;
-  for (i = 0, ii = points.length; i < ii; ++i) {
-    coordinates = points[i];
-    segmentData = /** @type {ol.ModifySegmentDataType} */ ({
-      feature: feature,
-      geometry: geometry,
-      depth: [i],
-      index: i,
-      segment: [coordinates, coordinates]
-    });
-    this.rBush_.insert(geometry.getExtent(), segmentData);
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.LineString} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writeLineStringGeometry_ = function(feature, geometry) {
-  var coordinates = geometry.getCoordinates();
-  var i, ii, segment, segmentData;
-  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-    segment = coordinates.slice(i, i + 2);
-    segmentData = /** @type {ol.ModifySegmentDataType} */ ({
-      feature: feature,
-      geometry: geometry,
-      index: i,
-      segment: segment
-    });
-    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiLineString} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) {
-  var lines = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, segment, segmentData;
-  for (j = 0, jj = lines.length; j < jj; ++j) {
-    coordinates = lines[j];
-    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-      segment = coordinates.slice(i, i + 2);
-      segmentData = /** @type {ol.ModifySegmentDataType} */ ({
-        feature: feature,
-        geometry: geometry,
-        depth: [j],
-        index: i,
-        segment: segment
-      });
-      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-    }
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Polygon} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writePolygonGeometry_ = function(feature, geometry) {
-  var rings = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, segment, segmentData;
-  for (j = 0, jj = rings.length; j < jj; ++j) {
-    coordinates = rings[j];
-    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-      segment = coordinates.slice(i, i + 2);
-      segmentData = /** @type {ol.ModifySegmentDataType} */ ({
-        feature: feature,
-        geometry: geometry,
-        depth: [j],
-        index: i,
-        segment: segment
-      });
-      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-    }
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) {
-  var polygons = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
-  for (k = 0, kk = polygons.length; k < kk; ++k) {
-    rings = polygons[k];
-    for (j = 0, jj = rings.length; j < jj; ++j) {
-      coordinates = rings[j];
-      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-        segment = coordinates.slice(i, i + 2);
-        segmentData = /** @type {ol.ModifySegmentDataType} */ ({
-          feature: feature,
-          geometry: geometry,
-          depth: [j, k],
-          index: i,
-          segment: segment
-        });
-        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-      }
-    }
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @private
- */
-ol.interaction.Modify.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) {
-  var i, geometries = geometry.getGeometriesArray();
-  for (i = 0; i < geometries.length; ++i) {
-    this.SEGMENT_WRITERS_[geometries[i].getType()].call(
-        this, feature, geometries[i]);
-  }
-};
-
-
-/**
- * @param {ol.Coordinate} coordinates Coordinates.
- * @return {ol.Feature} Vertex feature.
- * @private
- */
-ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = function(coordinates) {
-  var vertexFeature = this.vertexFeature_;
-  if (!vertexFeature) {
-    vertexFeature = new ol.Feature(new ol.geom.Point(coordinates));
-    this.vertexFeature_ = vertexFeature;
-    this.overlay_.getSource().addFeature(vertexFeature);
-  } else {
-    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
-    geometry.setCoordinates(coordinates);
-  }
-  return vertexFeature;
-};
-
-
-/**
- * @param {ol.ModifySegmentDataType} a The first segment data.
- * @param {ol.ModifySegmentDataType} b The second segment data.
- * @return {number} The difference in indexes.
- * @private
- */
-ol.interaction.Modify.compareIndexes_ = function(a, b) {
-  return a.index - b.index;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Modify}
- * @private
- */
-ol.interaction.Modify.handleDownEvent_ = function(evt) {
-  if (!this.condition_(evt)) {
-    return false;
-  }
-  this.handlePointerAtPixel_(evt.pixel, evt.map);
-  this.dragSegments_.length = 0;
-  this.modified_ = false;
-  var vertexFeature = this.vertexFeature_;
-  if (vertexFeature) {
-    var insertVertices = [];
-    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
-    var vertex = geometry.getCoordinates();
-    var vertexExtent = ol.extent.boundingExtent([vertex]);
-    var segmentDataMatches = this.rBush_.getInExtent(vertexExtent);
-    var componentSegments = {};
-    segmentDataMatches.sort(ol.interaction.Modify.compareIndexes_);
-    for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) {
-      var segmentDataMatch = segmentDataMatches[i];
-      var segment = segmentDataMatch.segment;
-      var uid = ol.getUid(segmentDataMatch.feature);
-      var depth = segmentDataMatch.depth;
-      if (depth) {
-        uid += '-' + depth.join('-'); // separate feature components
-      }
-      if (!componentSegments[uid]) {
-        componentSegments[uid] = new Array(2);
-      }
-      if (ol.coordinate.equals(segment[0], vertex) &&
-          !componentSegments[uid][0]) {
-        this.dragSegments_.push([segmentDataMatch, 0]);
-        componentSegments[uid][0] = segmentDataMatch;
-      } else if (ol.coordinate.equals(segment[1], vertex) &&
-          !componentSegments[uid][1]) {
-
-        // prevent dragging closed linestrings by the connecting node
-        if ((segmentDataMatch.geometry.getType() ===
-            ol.geom.GeometryType.LINE_STRING ||
-            segmentDataMatch.geometry.getType() ===
-            ol.geom.GeometryType.MULTI_LINE_STRING) &&
-            componentSegments[uid][0] &&
-            componentSegments[uid][0].index === 0) {
-          continue;
-        }
-
-        this.dragSegments_.push([segmentDataMatch, 1]);
-        componentSegments[uid][1] = segmentDataMatch;
-      } else if (ol.getUid(segment) in this.vertexSegments_ &&
-          (!componentSegments[uid][0] && !componentSegments[uid][1])) {
-        insertVertices.push([segmentDataMatch, vertex]);
-      }
-    }
-    if (insertVertices.length) {
-      this.willModifyFeatures_(evt);
-    }
-    for (var j = insertVertices.length - 1; j >= 0; --j) {
-      this.insertVertex_.apply(this, insertVertices[j]);
-    }
-  }
-  return !!this.vertexFeature_;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @this {ol.interaction.Modify}
- * @private
- */
-ol.interaction.Modify.handleDragEvent_ = function(evt) {
-  this.ignoreNextSingleClick_ = false;
-  this.willModifyFeatures_(evt);
-
-  var vertex = evt.coordinate;
-  for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) {
-    var dragSegment = this.dragSegments_[i];
-    var segmentData = dragSegment[0];
-    var depth = segmentData.depth;
-    var geometry = segmentData.geometry;
-    var coordinates = geometry.getCoordinates();
-    var segment = segmentData.segment;
-    var index = dragSegment[1];
-
-    while (vertex.length < geometry.getStride()) {
-      vertex.push(0);
-    }
-
-    switch (geometry.getType()) {
-      case ol.geom.GeometryType.POINT:
-        coordinates = vertex;
-        segment[0] = segment[1] = vertex;
-        break;
-      case ol.geom.GeometryType.MULTI_POINT:
-        coordinates[segmentData.index] = vertex;
-        segment[0] = segment[1] = vertex;
-        break;
-      case ol.geom.GeometryType.LINE_STRING:
-        coordinates[segmentData.index + index] = vertex;
-        segment[index] = vertex;
-        break;
-      case ol.geom.GeometryType.MULTI_LINE_STRING:
-        coordinates[depth[0]][segmentData.index + index] = vertex;
-        segment[index] = vertex;
-        break;
-      case ol.geom.GeometryType.POLYGON:
-        coordinates[depth[0]][segmentData.index + index] = vertex;
-        segment[index] = vertex;
-        break;
-      case ol.geom.GeometryType.MULTI_POLYGON:
-        coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex;
-        segment[index] = vertex;
-        break;
-      default:
-        // pass
-    }
-
-    this.setGeometryCoordinates_(geometry, coordinates);
-  }
-  this.createOrUpdateVertexFeature_(vertex);
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Modify}
- * @private
- */
-ol.interaction.Modify.handleUpEvent_ = function(evt) {
-  var segmentData;
-  for (var i = this.dragSegments_.length - 1; i >= 0; --i) {
-    segmentData = this.dragSegments_[i][0];
-    this.rBush_.update(ol.extent.boundingExtent(segmentData.segment),
-        segmentData);
-  }
-  if (this.modified_) {
-    this.dispatchEvent(new ol.interaction.Modify.Event(
-        ol.interaction.Modify.EventType.MODIFYEND, this.features_, evt));
-    this.modified_ = false;
-  }
-  return false;
-};
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the
- * geometry.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.Modify}
- * @api
- */
-ol.interaction.Modify.handleEvent = function(mapBrowserEvent) {
-  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
-    return true;
-  }
-  this.lastPointerEvent_ = mapBrowserEvent;
-
-  var handled;
-  if (!mapBrowserEvent.map.getView().getHints()[ol.View.Hint.INTERACTING] &&
-      mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE &&
-      !this.handlingDownUpSequence) {
-    this.handlePointerMove_(mapBrowserEvent);
-  }
-  if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) {
-    if (mapBrowserEvent.type != ol.MapBrowserEvent.EventType.SINGLECLICK ||
-        !this.ignoreNextSingleClick_) {
-      handled = this.removePoint();
-    } else {
-      handled = true;
-    }
-  }
-
-  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK) {
-    this.ignoreNextSingleClick_ = false;
-  }
-
-  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
-      !handled;
-};
-
-
-/**
- * @param {ol.MapBrowserEvent} evt Event.
- * @private
- */
-ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
-  this.lastPixel_ = evt.pixel;
-  this.handlePointerAtPixel_(evt.pixel, evt.map);
-};
-
-
-/**
- * @param {ol.Pixel} pixel Pixel
- * @param {ol.Map} map Map.
- * @private
- */
-ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) {
-  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
-  var sortByDistance = function(a, b) {
-    return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) -
-        ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment);
-  };
-
-  var lowerLeft = map.getCoordinateFromPixel(
-      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
-  var upperRight = map.getCoordinateFromPixel(
-      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
-  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
-
-  var rBush = this.rBush_;
-  var nodes = rBush.getInExtent(box);
-  if (nodes.length > 0) {
-    nodes.sort(sortByDistance);
-    var node = nodes[0];
-    var closestSegment = node.segment;
-    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
-        closestSegment));
-    var vertexPixel = map.getPixelFromCoordinate(vertex);
-    if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
-        this.pixelTolerance_) {
-      var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
-      var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
-      var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
-      var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
-      var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
-      this.snappedToVertex_ = dist <= this.pixelTolerance_;
-      if (this.snappedToVertex_) {
-        vertex = squaredDist1 > squaredDist2 ?
-            closestSegment[1] : closestSegment[0];
-      }
-      this.createOrUpdateVertexFeature_(vertex);
-      var vertexSegments = {};
-      vertexSegments[ol.getUid(closestSegment)] = true;
-      var segment;
-      for (var i = 1, ii = nodes.length; i < ii; ++i) {
-        segment = nodes[i].segment;
-        if ((ol.coordinate.equals(closestSegment[0], segment[0]) &&
-            ol.coordinate.equals(closestSegment[1], segment[1]) ||
-            (ol.coordinate.equals(closestSegment[0], segment[1]) &&
-            ol.coordinate.equals(closestSegment[1], segment[0])))) {
-          vertexSegments[ol.getUid(segment)] = true;
-        } else {
-          break;
-        }
-      }
-      this.vertexSegments_ = vertexSegments;
-      return;
-    }
-  }
-  if (this.vertexFeature_) {
-    this.overlay_.getSource().removeFeature(this.vertexFeature_);
-    this.vertexFeature_ = null;
-  }
-};
-
-
-/**
- * @param {ol.ModifySegmentDataType} segmentData Segment data.
- * @param {ol.Coordinate} vertex Vertex.
- * @private
- */
-ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) {
-  var segment = segmentData.segment;
-  var feature = segmentData.feature;
-  var geometry = segmentData.geometry;
-  var depth = segmentData.depth;
-  var index = /** @type {number} */ (segmentData.index);
-  var coordinates;
-
-  while (vertex.length < geometry.getStride()) {
-    vertex.push(0);
-  }
-
-  switch (geometry.getType()) {
-    case ol.geom.GeometryType.MULTI_LINE_STRING:
-      coordinates = geometry.getCoordinates();
-      coordinates[depth[0]].splice(index + 1, 0, vertex);
-      break;
-    case ol.geom.GeometryType.POLYGON:
-      coordinates = geometry.getCoordinates();
-      coordinates[depth[0]].splice(index + 1, 0, vertex);
-      break;
-    case ol.geom.GeometryType.MULTI_POLYGON:
-      coordinates = geometry.getCoordinates();
-      coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex);
-      break;
-    case ol.geom.GeometryType.LINE_STRING:
-      coordinates = geometry.getCoordinates();
-      coordinates.splice(index + 1, 0, vertex);
-      break;
-    default:
-      return;
-  }
-
-  this.setGeometryCoordinates_(geometry, coordinates);
-  var rTree = this.rBush_;
-  rTree.remove(segmentData);
-  this.updateSegmentIndices_(geometry, index, depth, 1);
-  var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
-    segment: [segment[0], vertex],
-    feature: feature,
-    geometry: geometry,
-    depth: depth,
-    index: index
-  });
-  rTree.insert(ol.extent.boundingExtent(newSegmentData.segment),
-      newSegmentData);
-  this.dragSegments_.push([newSegmentData, 1]);
-
-  var newSegmentData2 = /** @type {ol.ModifySegmentDataType} */ ({
-    segment: [vertex, segment[1]],
-    feature: feature,
-    geometry: geometry,
-    depth: depth,
-    index: index + 1
-  });
-  rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment),
-      newSegmentData2);
-  this.dragSegments_.push([newSegmentData2, 0]);
-  this.ignoreNextSingleClick_ = true;
-};
-
-/**
- * Removes the vertex currently being pointed.
- * @return {boolean} True when a vertex was removed.
- * @api
- */
-ol.interaction.Modify.prototype.removePoint = function() {
-  var handled = false;
-  if (this.lastPointerEvent_ && this.lastPointerEvent_.type != ol.MapBrowserEvent.EventType.POINTERDRAG) {
-    var evt = this.lastPointerEvent_;
-    this.willModifyFeatures_(evt);
-    handled = this.removeVertex_();
-    this.dispatchEvent(new ol.interaction.Modify.Event(
-        ol.interaction.Modify.EventType.MODIFYEND, this.features_, evt));
-    this.modified_ = false;
-  }
-  return handled;
-};
-
-/**
- * Removes a vertex from all matching features.
- * @return {boolean} True when a vertex was removed.
- * @private
- */
-ol.interaction.Modify.prototype.removeVertex_ = function() {
-  var dragSegments = this.dragSegments_;
-  var segmentsByFeature = {};
-  var deleted = false;
-  var component, coordinates, dragSegment, geometry, i, index, left;
-  var newIndex, right, segmentData, uid;
-  for (i = dragSegments.length - 1; i >= 0; --i) {
-    dragSegment = dragSegments[i];
-    segmentData = dragSegment[0];
-    uid = ol.getUid(segmentData.feature);
-    if (segmentData.depth) {
-      // separate feature components
-      uid += '-' + segmentData.depth.join('-');
-    }
-    if (!(uid in segmentsByFeature)) {
-      segmentsByFeature[uid] = {};
-    }
-    if (dragSegment[1] === 0) {
-      segmentsByFeature[uid].right = segmentData;
-      segmentsByFeature[uid].index = segmentData.index;
-    } else if (dragSegment[1] == 1) {
-      segmentsByFeature[uid].left = segmentData;
-      segmentsByFeature[uid].index = segmentData.index + 1;
-    }
-
-  }
-  for (uid in segmentsByFeature) {
-    right = segmentsByFeature[uid].right;
-    left = segmentsByFeature[uid].left;
-    index = segmentsByFeature[uid].index;
-    newIndex = index - 1;
-    if (left !== undefined) {
-      segmentData = left;
-    } else {
-      segmentData = right;
-    }
-    if (newIndex < 0) {
-      newIndex = 0;
-    }
-    geometry = segmentData.geometry;
-    coordinates = geometry.getCoordinates();
-    component = coordinates;
-    deleted = false;
-    switch (geometry.getType()) {
-      case ol.geom.GeometryType.MULTI_LINE_STRING:
-        if (coordinates[segmentData.depth[0]].length > 2) {
-          coordinates[segmentData.depth[0]].splice(index, 1);
-          deleted = true;
-        }
-        break;
-      case ol.geom.GeometryType.LINE_STRING:
-        if (coordinates.length > 2) {
-          coordinates.splice(index, 1);
-          deleted = true;
-        }
-        break;
-      case ol.geom.GeometryType.MULTI_POLYGON:
-        component = component[segmentData.depth[1]];
-        /* falls through */
-      case ol.geom.GeometryType.POLYGON:
-        component = component[segmentData.depth[0]];
-        if (component.length > 4) {
-          if (index == component.length - 1) {
-            index = 0;
-          }
-          component.splice(index, 1);
-          deleted = true;
-          if (index === 0) {
-            // close the ring again
-            component.pop();
-            component.push(component[0]);
-            newIndex = component.length - 1;
-          }
-        }
-        break;
-      default:
-        // pass
-    }
-
-    if (deleted) {
-      this.setGeometryCoordinates_(geometry, coordinates);
-      var segments = [];
-      if (left !== undefined) {
-        this.rBush_.remove(left);
-        segments.push(left.segment[0]);
-      }
-      if (right !== undefined) {
-        this.rBush_.remove(right);
-        segments.push(right.segment[1]);
-      }
-      if (left !== undefined && right !== undefined) {
-        ol.DEBUG && console.assert(newIndex >= 0, 'newIndex should be larger than 0');
-
-        var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
-          depth: segmentData.depth,
-          feature: segmentData.feature,
-          geometry: segmentData.geometry,
-          index: newIndex,
-          segment: segments
-        });
-        this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment),
-            newSegmentData);
-      }
-      this.updateSegmentIndices_(geometry, index, segmentData.depth, -1);
-      if (this.vertexFeature_) {
-        this.overlay_.getSource().removeFeature(this.vertexFeature_);
-        this.vertexFeature_ = null;
-      }
-    }
-
-  }
-  return deleted;
-};
-
-
-/**
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {Array} coordinates Coordinates.
- * @private
- */
-ol.interaction.Modify.prototype.setGeometryCoordinates_ = function(geometry, coordinates) {
-  this.changingFeature_ = true;
-  geometry.setCoordinates(coordinates);
-  this.changingFeature_ = false;
-};
-
-
-/**
- * @param {ol.geom.SimpleGeometry} geometry Geometry.
- * @param {number} index Index.
- * @param {Array.<number>|undefined} depth Depth.
- * @param {number} delta Delta (1 or -1).
- * @private
- */
-ol.interaction.Modify.prototype.updateSegmentIndices_ = function(
-    geometry, index, depth, delta) {
-  this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) {
-    if (segmentDataMatch.geometry === geometry &&
-        (depth === undefined || segmentDataMatch.depth === undefined ||
-        ol.array.equals(segmentDataMatch.depth, depth)) &&
-        segmentDataMatch.index > index) {
-      segmentDataMatch.index += delta;
-    }
-  });
-};
-
-
-/**
- * @return {ol.StyleFunction} Styles.
- */
-ol.interaction.Modify.getDefaultStyleFunction = function() {
-  var style = ol.style.Style.createDefaultEditing();
-  return function(feature, resolution) {
-    return style[ol.geom.GeometryType.POINT];
-  };
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.Modify} instances are instances of
- * this type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.ModifyEvent}
- * @param {ol.interaction.Modify.EventType} type Type.
- * @param {ol.Collection.<ol.Feature>} features The features modified.
- * @param {ol.MapBrowserPointerEvent} mapBrowserPointerEvent Associated
- *     {@link ol.MapBrowserPointerEvent}.
- */
-ol.interaction.Modify.Event = function(type, features, mapBrowserPointerEvent) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The features being modified.
-   * @type {ol.Collection.<ol.Feature>}
-   * @api
-   */
-  this.features = features;
-
-  /**
-   * Associated {@link ol.MapBrowserEvent}.
-   * @type {ol.MapBrowserEvent}
-   * @api
-   */
-  this.mapBrowserEvent = mapBrowserPointerEvent;
-};
-ol.inherits(ol.interaction.Modify.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.interaction.Modify.EventType = {
-  /**
-   * Triggered upon feature modification start
-   * @event ol.interaction.Modify.Event#modifystart
-   * @api
-   */
-  MODIFYSTART: 'modifystart',
-  /**
-   * Triggered upon feature modification end
-   * @event ol.interaction.Modify.Event#modifyend
-   * @api
-   */
-  MODIFYEND: 'modifyend'
-};
-
-goog.provide('ol.interaction.Select');
-
-goog.require('ol');
-goog.require('ol.functions');
-goog.require('ol.Collection');
-goog.require('ol.array');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.condition');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.layer.Vector');
-goog.require('ol.obj');
-goog.require('ol.source.Vector');
-goog.require('ol.style.Style');
-
-
-/**
- * @classdesc
- * Interaction for selecting vector features. By default, selected features are
- * styled differently, so this interaction can be used for visual highlighting,
- * as well as selecting features for other actions, such as modification or
- * output. There are three ways of controlling which features are selected:
- * using the browser event as defined by the `condition` and optionally the
- * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a
- * further feature filter using the `filter` option.
- *
- * Selected features are added to an internal unmanaged layer.
- *
- * @constructor
- * @extends {ol.interaction.Interaction}
- * @param {olx.interaction.SelectOptions=} opt_options Options.
- * @fires ol.interaction.Select.Event
- * @api stable
- */
-ol.interaction.Select = function(opt_options) {
-
-  ol.interaction.Interaction.call(this, {
-    handleEvent: ol.interaction.Select.handleEvent
-  });
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.condition_ = options.condition ?
-      options.condition : ol.events.condition.singleClick;
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.addCondition_ = options.addCondition ?
-      options.addCondition : ol.events.condition.never;
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.removeCondition_ = options.removeCondition ?
-      options.removeCondition : ol.events.condition.never;
-
-  /**
-   * @private
-   * @type {ol.EventsConditionType}
-   */
-  this.toggleCondition_ = options.toggleCondition ?
-      options.toggleCondition : ol.events.condition.shiftKeyOnly;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.multi_ = options.multi ? options.multi : false;
-
-  /**
-   * @private
-   * @type {ol.SelectFilterFunction}
-   */
-  this.filter_ = options.filter ? options.filter :
-      ol.functions.TRUE;
-
-  var featureOverlay = new ol.layer.Vector({
-    source: new ol.source.Vector({
-      useSpatialIndex: false,
-      features: options.features,
-      wrapX: options.wrapX
-    }),
-    style: options.style ? options.style :
-        ol.interaction.Select.getDefaultStyleFunction(),
-    updateWhileAnimating: true,
-    updateWhileInteracting: true
-  });
-
-  /**
-   * @private
-   * @type {ol.layer.Vector}
-   */
-  this.featureOverlay_ = featureOverlay;
-
-  /** @type {function(ol.layer.Layer): boolean} */
-  var layerFilter;
-  if (options.layers) {
-    if (typeof options.layers === 'function') {
-      layerFilter = options.layers;
-    } else {
-      var layers = options.layers;
-      layerFilter = function(layer) {
-        return ol.array.includes(layers, layer);
-      };
-    }
-  } else {
-    layerFilter = ol.functions.TRUE;
-  }
-
-  /**
-   * @private
-   * @type {function(ol.layer.Layer): boolean}
-   */
-  this.layerFilter_ = layerFilter;
-
-  /**
-   * An association between selected feature (key)
-   * and layer (value)
-   * @private
-   * @type {Object.<number, ol.layer.Layer>}
-   */
-  this.featureLayerAssociation_ = {};
-
-  var features = this.featureOverlay_.getSource().getFeaturesCollection();
-  ol.events.listen(features, ol.Collection.EventType.ADD,
-      this.addFeature_, this);
-  ol.events.listen(features, ol.Collection.EventType.REMOVE,
-      this.removeFeature_, this);
-
-};
-ol.inherits(ol.interaction.Select, ol.interaction.Interaction);
-
-
-/**
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @param {ol.layer.Layer} layer Layer.
- * @private
- */
-ol.interaction.Select.prototype.addFeatureLayerAssociation_ = function(feature, layer) {
-  var key = ol.getUid(feature);
-  this.featureLayerAssociation_[key] = layer;
-};
-
-
-/**
- * Get the selected features.
- * @return {ol.Collection.<ol.Feature>} Features collection.
- * @api stable
- */
-ol.interaction.Select.prototype.getFeatures = function() {
-  return this.featureOverlay_.getSource().getFeaturesCollection();
-};
-
-
-/**
- * Returns the associated {@link ol.layer.Vector vectorlayer} of
- * the (last) selected feature. Note that this will not work with any
- * programmatic method like pushing features to
- * {@link ol.interaction.Select#getFeatures collection}.
- * @param {ol.Feature|ol.render.Feature} feature Feature
- * @return {ol.layer.Vector} Layer.
- * @api
- */
-ol.interaction.Select.prototype.getLayer = function(feature) {
-  var key = ol.getUid(feature);
-  return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]);
-};
-
-
-/**
- * Handles the {@link ol.MapBrowserEvent map browser event} and may change the
- * selected state of features.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
- * @return {boolean} `false` to stop event propagation.
- * @this {ol.interaction.Select}
- * @api
- */
-ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
-  if (!this.condition_(mapBrowserEvent)) {
-    return true;
-  }
-  var add = this.addCondition_(mapBrowserEvent);
-  var remove = this.removeCondition_(mapBrowserEvent);
-  var toggle = this.toggleCondition_(mapBrowserEvent);
-  var set = !add && !remove && !toggle;
-  var map = mapBrowserEvent.map;
-  var features = this.featureOverlay_.getSource().getFeaturesCollection();
-  var deselected = [];
-  var selected = [];
-  if (set) {
-    // Replace the currently selected feature(s) with the feature(s) at the
-    // pixel, or clear the selected feature(s) if there is no feature at
-    // the pixel.
-    ol.obj.clear(this.featureLayerAssociation_);
-    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
-        /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @param {ol.layer.Layer} layer Layer.
-         * @return {boolean|undefined} Continue to iterate over the features.
-         */
-        function(feature, layer) {
-          if (this.filter_(feature, layer)) {
-            selected.push(feature);
-            this.addFeatureLayerAssociation_(feature, layer);
-            return !this.multi_;
-          }
-        }, this, this.layerFilter_);
-    var i;
-    for (i = features.getLength() - 1; i >= 0; --i) {
-      var feature = features.item(i);
-      var index = selected.indexOf(feature);
-      if (index > -1) {
-        // feature is already selected
-        selected.splice(index, 1);
-      } else {
-        features.remove(feature);
-        deselected.push(feature);
-      }
-    }
-    if (selected.length !== 0) {
-      features.extend(selected);
-    }
-  } else {
-    // Modify the currently selected feature(s).
-    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
-        /**
-         * @param {ol.Feature|ol.render.Feature} feature Feature.
-         * @param {ol.layer.Layer} layer Layer.
-         * @return {boolean|undefined} Continue to iterate over the features.
-         */
-        function(feature, layer) {
-          if (this.filter_(feature, layer)) {
-            if ((add || toggle) &&
-                !ol.array.includes(features.getArray(), feature)) {
-              selected.push(feature);
-              this.addFeatureLayerAssociation_(feature, layer);
-            } else if ((remove || toggle) &&
-                ol.array.includes(features.getArray(), feature)) {
-              deselected.push(feature);
-              this.removeFeatureLayerAssociation_(feature);
-            }
-            return !this.multi_;
-          }
-        }, this, this.layerFilter_);
-    var j;
-    for (j = deselected.length - 1; j >= 0; --j) {
-      features.remove(deselected[j]);
-    }
-    features.extend(selected);
-  }
-  if (selected.length > 0 || deselected.length > 0) {
-    this.dispatchEvent(
-        new ol.interaction.Select.Event(ol.interaction.Select.EventType.SELECT,
-            selected, deselected, mapBrowserEvent));
-  }
-  return ol.events.condition.pointerMove(mapBrowserEvent);
-};
-
-
-/**
- * Remove the interaction from its current map, if any,  and attach it to a new
- * map, if any. Pass `null` to just remove the interaction from the current map.
- * @param {ol.Map} map Map.
- * @api stable
- */
-ol.interaction.Select.prototype.setMap = function(map) {
-  var currentMap = this.getMap();
-  var selectedFeatures =
-      this.featureOverlay_.getSource().getFeaturesCollection();
-  if (currentMap) {
-    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
-  }
-  ol.interaction.Interaction.prototype.setMap.call(this, map);
-  this.featureOverlay_.setMap(map);
-  if (map) {
-    selectedFeatures.forEach(map.skipFeature, map);
-  }
-};
-
-
-/**
- * @return {ol.StyleFunction} Styles.
- */
-ol.interaction.Select.getDefaultStyleFunction = function() {
-  var styles = ol.style.Style.createDefaultEditing();
-  ol.array.extend(styles[ol.geom.GeometryType.POLYGON],
-      styles[ol.geom.GeometryType.LINE_STRING]);
-  ol.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION],
-      styles[ol.geom.GeometryType.LINE_STRING]);
-
-  return function(feature, resolution) {
-    if (!feature.getGeometry()) {
-      return null;
-    }
-    return styles[feature.getGeometry().getType()];
-  };
-};
-
-
-/**
- * @param {ol.Collection.Event} evt Event.
- * @private
- */
-ol.interaction.Select.prototype.addFeature_ = function(evt) {
-  var map = this.getMap();
-  if (map) {
-    map.skipFeature(/** @type {ol.Feature} */ (evt.element));
-  }
-};
-
-
-/**
- * @param {ol.Collection.Event} evt Event.
- * @private
- */
-ol.interaction.Select.prototype.removeFeature_ = function(evt) {
-  var map = this.getMap();
-  if (map) {
-    map.unskipFeature(/** @type {ol.Feature} */ (evt.element));
-  }
-};
-
-
-/**
- * @param {ol.Feature|ol.render.Feature} feature Feature.
- * @private
- */
-ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = function(feature) {
-  var key = ol.getUid(feature);
-  delete this.featureLayerAssociation_[key];
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.Select} instances are instances of
- * this type.
- *
- * @param {ol.interaction.Select.EventType} type The event type.
- * @param {Array.<ol.Feature>} selected Selected features.
- * @param {Array.<ol.Feature>} deselected Deselected features.
- * @param {ol.MapBrowserEvent} mapBrowserEvent Associated
- *     {@link ol.MapBrowserEvent}.
- * @implements {oli.SelectEvent}
- * @extends {ol.events.Event}
- * @constructor
- */
-ol.interaction.Select.Event = function(type, selected, deselected, mapBrowserEvent) {
-  ol.events.Event.call(this, type);
-
-  /**
-   * Selected features array.
-   * @type {Array.<ol.Feature>}
-   * @api
-   */
-  this.selected = selected;
-
-  /**
-   * Deselected features array.
-   * @type {Array.<ol.Feature>}
-   * @api
-   */
-  this.deselected = deselected;
-
-  /**
-   * Associated {@link ol.MapBrowserEvent}.
-   * @type {ol.MapBrowserEvent}
-   * @api
-   */
-  this.mapBrowserEvent = mapBrowserEvent;
-};
-ol.inherits(ol.interaction.Select.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.interaction.Select.EventType = {
-  /**
-   * Triggered when feature(s) has been (de)selected.
-   * @event ol.interaction.Select.Event#select
-   * @api
-   */
-  SELECT: 'select'
-};
-
-goog.provide('ol.interaction.Snap');
-
-goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.Object');
-goog.require('ol.Observable');
-goog.require('ol.coordinate');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.functions');
-goog.require('ol.obj');
-goog.require('ol.source.Vector');
-goog.require('ol.structs.RBush');
-
-
-/**
- * @classdesc
- * Handles snapping of vector features while modifying or drawing them.  The
- * features can come from a {@link ol.source.Vector} or {@link ol.Collection}
- * Any interaction object that allows the user to interact
- * with the features using the mouse can benefit from the snapping, as long
- * as it is added before.
- *
- * The snap interaction modifies map browser event `coordinate` and `pixel`
- * properties to force the snap to occur to any interaction that them.
- *
- * Example:
- *
- *     var snap = new ol.interaction.Snap({
- *       source: source
- *     });
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @param {olx.interaction.SnapOptions=} opt_options Options.
- * @api
- */
-ol.interaction.Snap = function(opt_options) {
-
-  ol.interaction.Pointer.call(this, {
-    handleEvent: ol.interaction.Snap.handleEvent_,
-    handleDownEvent: ol.functions.TRUE,
-    handleUpEvent: ol.interaction.Snap.handleUpEvent_
-  });
-
-  var options = opt_options ? opt_options : {};
-
-  /**
-   * @type {ol.source.Vector}
-   * @private
-   */
-  this.source_ = options.source ? options.source : null;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.vertex_ = options.vertex !== undefined ? options.vertex : true;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.edge_ = options.edge !== undefined ? options.edge : true;
-
-  /**
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
-   */
-  this.features_ = options.features ? options.features : null;
-
-  /**
-   * @type {Array.<ol.EventsKey>}
-   * @private
-   */
-  this.featuresListenerKeys_ = [];
-
-  /**
-   * @type {Object.<number, ol.EventsKey>}
-   * @private
-   */
-  this.geometryChangeListenerKeys_ = {};
-
-  /**
-   * @type {Object.<number, ol.EventsKey>}
-   * @private
-   */
-  this.geometryModifyListenerKeys_ = {};
-
-  /**
-   * Extents are preserved so indexed segment can be quickly removed
-   * when its feature geometry changes
-   * @type {Object.<number, ol.Extent>}
-   * @private
-   */
-  this.indexedFeaturesExtents_ = {};
-
-  /**
-   * If a feature geometry changes while a pointer drag|move event occurs, the
-   * feature doesn't get updated right away.  It will be at the next 'pointerup'
-   * event fired.
-   * @type {Object.<number, ol.Feature>}
-   * @private
-   */
-  this.pendingFeatures_ = {};
-
-  /**
-   * Used for distance sorting in sortByDistance_
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.pixelCoordinate_ = null;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
-      options.pixelTolerance : 10;
-
-  /**
-   * @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number}
-   * @private
-   */
-  this.sortByDistance_ = ol.interaction.Snap.sortByDistance.bind(this);
-
-
-  /**
-  * Segment RTree for each layer
-  * @type {ol.structs.RBush.<ol.SnapSegmentDataType>}
-  * @private
-  */
-  this.rBush_ = new ol.structs.RBush();
-
-
-  /**
-  * @const
-  * @private
-  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>}
-  */
-  this.SEGMENT_WRITERS_ = {
-    'Point': this.writePointGeometry_,
-    'LineString': this.writeLineStringGeometry_,
-    'LinearRing': this.writeLineStringGeometry_,
-    'Polygon': this.writePolygonGeometry_,
-    'MultiPoint': this.writeMultiPointGeometry_,
-    'MultiLineString': this.writeMultiLineStringGeometry_,
-    'MultiPolygon': this.writeMultiPolygonGeometry_,
-    'GeometryCollection': this.writeGeometryCollectionGeometry_
-  };
-};
-ol.inherits(ol.interaction.Snap, ol.interaction.Pointer);
-
-
-/**
- * Add a feature to the collection of features that we may snap to.
- * @param {ol.Feature} feature Feature.
- * @param {boolean=} opt_listen Whether to listen to the geometry change or not
- *     Defaults to `true`.
- * @api
- */
-ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) {
-  var listen = opt_listen !== undefined ? opt_listen : true;
-  var feature_uid = ol.getUid(feature);
-  var geometry = feature.getGeometry();
-  if (geometry) {
-    var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];
-    if (segmentWriter) {
-      this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent(
-          ol.extent.createEmpty());
-      segmentWriter.call(this, feature, geometry);
-
-      if (listen) {
-        this.geometryModifyListenerKeys_[feature_uid] = ol.events.listen(
-            geometry,
-            ol.events.EventType.CHANGE,
-            this.handleGeometryModify_.bind(this, feature),
-            this);
-      }
-    }
-  }
-
-  if (listen) {
-    this.geometryChangeListenerKeys_[feature_uid] = ol.events.listen(
-        feature,
-        ol.Object.getChangeEventType(feature.getGeometryName()),
-        this.handleGeometryChange_, this);
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
-  this.addFeature(feature);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- * @private
- */
-ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
-  this.removeFeature(feature);
-};
-
-
-/**
- * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} Features.
- * @private
- */
-ol.interaction.Snap.prototype.getFeatures_ = function() {
-  var features;
-  if (this.features_) {
-    features = this.features_;
-  } else if (this.source_) {
-    features = this.source_.getFeatures();
-  }
-  return /** @type {!Array.<ol.Feature>|!ol.Collection.<ol.Feature>} */ (features);
-};
-
-
-/**
- * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
- * @private
- */
-ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) {
-  var feature;
-  if (evt instanceof ol.source.Vector.Event) {
-    feature = evt.feature;
-  } else if (evt instanceof ol.Collection.Event) {
-    feature = evt.element;
-  }
-  this.addFeature(/** @type {ol.Feature} */ (feature));
-};
-
-
-/**
- * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
- * @private
- */
-ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) {
-  var feature;
-  if (evt instanceof ol.source.Vector.Event) {
-    feature = evt.feature;
-  } else if (evt instanceof ol.Collection.Event) {
-    feature = evt.element;
-  }
-  this.removeFeature(/** @type {ol.Feature} */ (feature));
-};
-
-
-/**
- * @param {ol.events.Event} evt Event.
- * @private
- */
-ol.interaction.Snap.prototype.handleGeometryChange_ = function(evt) {
-  var feature = /** @type {ol.Feature} */ (evt.target);
-  this.removeFeature(feature, true);
-  this.addFeature(feature, true);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature which geometry was modified.
- * @param {ol.events.Event} evt Event.
- * @private
- */
-ol.interaction.Snap.prototype.handleGeometryModify_ = function(feature, evt) {
-  if (this.handlingDownUpSequence) {
-    var uid = ol.getUid(feature);
-    if (!(uid in this.pendingFeatures_)) {
-      this.pendingFeatures_[uid] = feature;
-    }
-  } else {
-    this.updateFeature_(feature);
-  }
-};
-
-
-/**
- * Remove a feature from the collection of features that we may snap to.
- * @param {ol.Feature} feature Feature
- * @param {boolean=} opt_unlisten Whether to unlisten to the geometry change
- *     or not. Defaults to `true`.
- * @api
- */
-ol.interaction.Snap.prototype.removeFeature = function(feature, opt_unlisten) {
-  var unlisten = opt_unlisten !== undefined ? opt_unlisten : true;
-  var feature_uid = ol.getUid(feature);
-  var extent = this.indexedFeaturesExtents_[feature_uid];
-  if (extent) {
-    var rBush = this.rBush_;
-    var i, nodesToRemove = [];
-    rBush.forEachInExtent(extent, function(node) {
-      if (feature === node.feature) {
-        nodesToRemove.push(node);
-      }
-    });
-    for (i = nodesToRemove.length - 1; i >= 0; --i) {
-      rBush.remove(nodesToRemove[i]);
-    }
-    if (unlisten) {
-      ol.Observable.unByKey(this.geometryModifyListenerKeys_[feature_uid]);
-      delete this.geometryModifyListenerKeys_[feature_uid];
-    }
-  }
-
-  if (unlisten) {
-    ol.Observable.unByKey(this.geometryChangeListenerKeys_[feature_uid]);
-    delete this.geometryChangeListenerKeys_[feature_uid];
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.Snap.prototype.setMap = function(map) {
-  var currentMap = this.getMap();
-  var keys = this.featuresListenerKeys_;
-  var features = this.getFeatures_();
-
-  if (currentMap) {
-    keys.forEach(ol.Observable.unByKey);
-    keys.length = 0;
-    features.forEach(this.forEachFeatureRemove_, this);
-  }
-  ol.interaction.Pointer.prototype.setMap.call(this, map);
-
-  if (map) {
-    if (this.features_) {
-      keys.push(
-        ol.events.listen(this.features_, ol.Collection.EventType.ADD,
-            this.handleFeatureAdd_, this),
-        ol.events.listen(this.features_, ol.Collection.EventType.REMOVE,
-            this.handleFeatureRemove_, this)
-      );
-    } else if (this.source_) {
-      keys.push(
-        ol.events.listen(this.source_, ol.source.Vector.EventType.ADDFEATURE,
-            this.handleFeatureAdd_, this),
-        ol.events.listen(this.source_, ol.source.Vector.EventType.REMOVEFEATURE,
-            this.handleFeatureRemove_, this)
-      );
-    }
-    features.forEach(this.forEachFeatureAdd_, this);
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE;
-
-
-/**
- * @param {ol.Pixel} pixel Pixel
- * @param {ol.Coordinate} pixelCoordinate Coordinate
- * @param {ol.Map} map Map.
- * @return {ol.SnapResultType} Snap result
- */
-ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) {
-
-  var lowerLeft = map.getCoordinateFromPixel(
-      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
-  var upperRight = map.getCoordinateFromPixel(
-      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
-  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
-
-  var segments = this.rBush_.getInExtent(box);
-  var snappedToVertex = false;
-  var snapped = false;
-  var vertex = null;
-  var vertexPixel = null;
-  var dist, pixel1, pixel2, squaredDist1, squaredDist2;
-  if (segments.length > 0) {
-    this.pixelCoordinate_ = pixelCoordinate;
-    segments.sort(this.sortByDistance_);
-    var closestSegment = segments[0].segment;
-    if (this.vertex_ && !this.edge_) {
-      pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
-      pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
-      squaredDist1 = ol.coordinate.squaredDistance(pixel, pixel1);
-      squaredDist2 = ol.coordinate.squaredDistance(pixel, pixel2);
-      dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
-      snappedToVertex = dist <= this.pixelTolerance_;
-      if (snappedToVertex) {
-        snapped = true;
-        vertex = squaredDist1 > squaredDist2 ?
-            closestSegment[1] : closestSegment[0];
-        vertexPixel = map.getPixelFromCoordinate(vertex);
-      }
-    } else if (this.edge_) {
-      vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
-          closestSegment));
-      vertexPixel = map.getPixelFromCoordinate(vertex);
-      if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
-          this.pixelTolerance_) {
-        snapped = true;
-        if (this.vertex_) {
-          pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
-          pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
-          squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
-          squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
-          dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
-          snappedToVertex = dist <= this.pixelTolerance_;
-          if (snappedToVertex) {
-            vertex = squaredDist1 > squaredDist2 ?
-                closestSegment[1] : closestSegment[0];
-            vertexPixel = map.getPixelFromCoordinate(vertex);
-          }
-        }
-      }
-    }
-    if (snapped) {
-      vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
-    }
-  }
-  return /** @type {ol.SnapResultType} */ ({
-    snapped: snapped,
-    vertex: vertex,
-    vertexPixel: vertexPixel
-  });
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @private
- */
-ol.interaction.Snap.prototype.updateFeature_ = function(feature) {
-  this.removeFeature(feature, false);
-  this.addFeature(feature, false);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.GeometryCollection} geometry Geometry.
- * @private
- */
-ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) {
-  var i, geometries = geometry.getGeometriesArray();
-  for (i = 0; i < geometries.length; ++i) {
-    this.SEGMENT_WRITERS_[geometries[i].getType()].call(
-        this, feature, geometries[i]);
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.LineString} geometry Geometry.
- * @private
- */
-ol.interaction.Snap.prototype.writeLineStringGeometry_ = function(feature, geometry) {
-  var coordinates = geometry.getCoordinates();
-  var i, ii, segment, segmentData;
-  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-    segment = coordinates.slice(i, i + 2);
-    segmentData = /** @type {ol.SnapSegmentDataType} */ ({
-      feature: feature,
-      segment: segment
-    });
-    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiLineString} geometry Geometry.
- * @private
- */
-ol.interaction.Snap.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) {
-  var lines = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, segment, segmentData;
-  for (j = 0, jj = lines.length; j < jj; ++j) {
-    coordinates = lines[j];
-    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-      segment = coordinates.slice(i, i + 2);
-      segmentData = /** @type {ol.SnapSegmentDataType} */ ({
-        feature: feature,
-        segment: segment
-      });
-      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-    }
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPoint} geometry Geometry.
- * @private
- */
-ol.interaction.Snap.prototype.writeMultiPointGeometry_ = function(feature, geometry) {
-  var points = geometry.getCoordinates();
-  var coordinates, i, ii, segmentData;
-  for (i = 0, ii = points.length; i < ii; ++i) {
-    coordinates = points[i];
-    segmentData = /** @type {ol.SnapSegmentDataType} */ ({
-      feature: feature,
-      segment: [coordinates, coordinates]
-    });
-    this.rBush_.insert(geometry.getExtent(), segmentData);
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.MultiPolygon} geometry Geometry.
- * @private
- */
-ol.interaction.Snap.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) {
-  var polygons = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
-  for (k = 0, kk = polygons.length; k < kk; ++k) {
-    rings = polygons[k];
-    for (j = 0, jj = rings.length; j < jj; ++j) {
-      coordinates = rings[j];
-      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-        segment = coordinates.slice(i, i + 2);
-        segmentData = /** @type {ol.SnapSegmentDataType} */ ({
-          feature: feature,
-          segment: segment
-        });
-        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-      }
-    }
-  }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Point} geometry Geometry.
- * @private
- */
-ol.interaction.Snap.prototype.writePointGeometry_ = function(feature, geometry) {
-  var coordinates = geometry.getCoordinates();
-  var segmentData = /** @type {ol.SnapSegmentDataType} */ ({
-    feature: feature,
-    segment: [coordinates, coordinates]
-  });
-  this.rBush_.insert(geometry.getExtent(), segmentData);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature
- * @param {ol.geom.Polygon} geometry Geometry.
- * @private
- */
-ol.interaction.Snap.prototype.writePolygonGeometry_ = function(feature, geometry) {
-  var rings = geometry.getCoordinates();
-  var coordinates, i, ii, j, jj, segment, segmentData;
-  for (j = 0, jj = rings.length; j < jj; ++j) {
-    coordinates = rings[j];
-    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
-      segment = coordinates.slice(i, i + 2);
-      segmentData = /** @type {ol.SnapSegmentDataType} */ ({
-        feature: feature,
-        segment: segment
-      });
-      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
-    }
-  }
-};
-
-
-/**
- * Handle all pointer events events.
- * @param {ol.MapBrowserEvent} evt A move event.
- * @return {boolean} Pass the event to other interactions.
- * @this {ol.interaction.Snap}
- * @private
- */
-ol.interaction.Snap.handleEvent_ = function(evt) {
-  var result = this.snapTo(evt.pixel, evt.coordinate, evt.map);
-  if (result.snapped) {
-    evt.coordinate = result.vertex.slice(0, 2);
-    evt.pixel = result.vertexPixel;
-  }
-  return ol.interaction.Pointer.handleEvent.call(this, evt);
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} evt Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Snap}
- * @private
- */
-ol.interaction.Snap.handleUpEvent_ = function(evt) {
-  var featuresToUpdate = ol.obj.getValues(this.pendingFeatures_);
-  if (featuresToUpdate.length) {
-    featuresToUpdate.forEach(this.updateFeature_, this);
-    this.pendingFeatures_ = {};
-  }
-  return false;
-};
-
-
-/**
- * Sort segments by distance, helper function
- * @param {ol.SnapSegmentDataType} a The first segment data.
- * @param {ol.SnapSegmentDataType} b The second segment data.
- * @return {number} The difference in distance.
- * @this {ol.interaction.Snap}
- */
-ol.interaction.Snap.sortByDistance = function(a, b) {
-  return ol.coordinate.squaredDistanceToSegment(
-      this.pixelCoordinate_, a.segment) -
-      ol.coordinate.squaredDistanceToSegment(
-      this.pixelCoordinate_, b.segment);
-};
-
-goog.provide('ol.interaction.Translate');
-
-goog.require('ol');
-goog.require('ol.events.Event');
-goog.require('ol.functions');
-goog.require('ol.array');
-goog.require('ol.interaction.Pointer');
-
-
-/**
- * @classdesc
- * Interaction for translating (moving) features.
- *
- * @constructor
- * @extends {ol.interaction.Pointer}
- * @fires ol.interaction.Translate.Event
- * @param {olx.interaction.TranslateOptions} options Options.
- * @api
- */
-ol.interaction.Translate = function(options) {
-  ol.interaction.Pointer.call(this, {
-    handleDownEvent: ol.interaction.Translate.handleDownEvent_,
-    handleDragEvent: ol.interaction.Translate.handleDragEvent_,
-    handleMoveEvent: ol.interaction.Translate.handleMoveEvent_,
-    handleUpEvent: ol.interaction.Translate.handleUpEvent_
-  });
-
-
-  /**
-   * @type {string|undefined}
-   * @private
-   */
-  this.previousCursor_ = undefined;
-
-
-  /**
-   * The last position we translated to.
-   * @type {ol.Coordinate}
-   * @private
-   */
-  this.lastCoordinate_ = null;
-
-
-  /**
-   * @type {ol.Collection.<ol.Feature>}
-   * @private
-   */
-  this.features_ = options.features !== undefined ? options.features : null;
-
-  /** @type {function(ol.layer.Layer): boolean} */
-  var layerFilter;
-  if (options.layers) {
-    if (typeof options.layers === 'function') {
-      layerFilter = options.layers;
-    } else {
-      var layers = options.layers;
-      layerFilter = function(layer) {
-        return ol.array.includes(layers, layer);
-      };
-    }
-  } else {
-    layerFilter = ol.functions.TRUE;
-  }
-
-  /**
-   * @private
-   * @type {function(ol.layer.Layer): boolean}
-   */
-  this.layerFilter_ = layerFilter;
-
-  /**
-   * @type {ol.Feature}
-   * @private
-   */
-  this.lastFeature_ = null;
-};
-ol.inherits(ol.interaction.Translate, ol.interaction.Pointer);
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Start drag sequence?
- * @this {ol.interaction.Translate}
- * @private
- */
-ol.interaction.Translate.handleDownEvent_ = function(event) {
-  this.lastFeature_ = this.featuresAtPixel_(event.pixel, event.map);
-  if (!this.lastCoordinate_ && this.lastFeature_) {
-    this.lastCoordinate_ = event.coordinate;
-    ol.interaction.Translate.handleMoveEvent_.call(this, event);
-    this.dispatchEvent(
-        new ol.interaction.Translate.Event(
-            ol.interaction.Translate.EventType.TRANSLATESTART, this.features_,
-            event.coordinate));
-    return true;
-  }
-  return false;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @return {boolean} Stop drag sequence?
- * @this {ol.interaction.Translate}
- * @private
- */
-ol.interaction.Translate.handleUpEvent_ = function(event) {
-  if (this.lastCoordinate_) {
-    this.lastCoordinate_ = null;
-    ol.interaction.Translate.handleMoveEvent_.call(this, event);
-    this.dispatchEvent(
-        new ol.interaction.Translate.Event(
-            ol.interaction.Translate.EventType.TRANSLATEEND, this.features_,
-            event.coordinate));
-    return true;
-  }
-  return false;
-};
-
-
-/**
- * @param {ol.MapBrowserPointerEvent} event Event.
- * @this {ol.interaction.Translate}
- * @private
- */
-ol.interaction.Translate.handleDragEvent_ = function(event) {
-  if (this.lastCoordinate_) {
-    var newCoordinate = event.coordinate;
-    var deltaX = newCoordinate[0] - this.lastCoordinate_[0];
-    var deltaY = newCoordinate[1] - this.lastCoordinate_[1];
-
-    if (this.features_) {
-      this.features_.forEach(function(feature) {
-        var geom = feature.getGeometry();
-        geom.translate(deltaX, deltaY);
-        feature.setGeometry(geom);
-      });
-    } else if (this.lastFeature_) {
-      var geom = this.lastFeature_.getGeometry();
-      geom.translate(deltaX, deltaY);
-      this.lastFeature_.setGeometry(geom);
-    }
-
-    this.lastCoordinate_ = newCoordinate;
-    this.dispatchEvent(
-        new ol.interaction.Translate.Event(
-            ol.interaction.Translate.EventType.TRANSLATING, this.features_,
-            newCoordinate));
-  }
-};
-
-
-/**
- * @param {ol.MapBrowserEvent} event Event.
- * @this {ol.interaction.Translate}
- * @private
- */
-ol.interaction.Translate.handleMoveEvent_ = function(event) {
-  var elem = event.map.getTargetElement();
-
-  // Change the cursor to grab/grabbing if hovering any of the features managed
-  // by the interaction
-  if (this.featuresAtPixel_(event.pixel, event.map)) {
-    this.previousCursor_ = elem.style.cursor;
-    // WebKit browsers don't support the grab icons without a prefix
-    elem.style.cursor = this.lastCoordinate_ ?
-        '-webkit-grabbing' : '-webkit-grab';
-
-    // Thankfully, attempting to set the standard ones will silently fail,
-    // keeping the prefixed icons
-    elem.style.cursor = this.lastCoordinate_ ?  'grabbing' : 'grab';
-  } else {
-    elem.style.cursor = this.previousCursor_ !== undefined ?
-        this.previousCursor_ : '';
-    this.previousCursor_ = undefined;
-  }
-};
-
-
-/**
- * Tests to see if the given coordinates intersects any of our selected
- * features.
- * @param {ol.Pixel} pixel Pixel coordinate to test for intersection.
- * @param {ol.Map} map Map to test the intersection on.
- * @return {ol.Feature} Returns the feature found at the specified pixel
- * coordinates.
- * @private
- */
-ol.interaction.Translate.prototype.featuresAtPixel_ = function(pixel, map) {
-  var found = null;
-
-  var intersectingFeature = map.forEachFeatureAtPixel(pixel,
-      function(feature) {
-        return feature;
-      }, this, this.layerFilter_);
-
-  if (this.features_ &&
-      ol.array.includes(this.features_.getArray(), intersectingFeature)) {
-    found = intersectingFeature;
-  }
-
-  return found;
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.interaction.Translate} instances are instances of
- * this type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.interaction.TranslateEvent}
- * @param {ol.interaction.Translate.EventType} type Type.
- * @param {ol.Collection.<ol.Feature>} features The features translated.
- * @param {ol.Coordinate} coordinate The event coordinate.
- */
-ol.interaction.Translate.Event = function(type, features, coordinate) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The features being translated.
-   * @type {ol.Collection.<ol.Feature>}
-   * @api
-   */
-  this.features = features;
-
-  /**
-   * The coordinate of the drag event.
-   * @const
-   * @type {ol.Coordinate}
-   * @api
-   */
-  this.coordinate = coordinate;
-};
-ol.inherits(ol.interaction.Translate.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.interaction.Translate.EventType = {
-  /**
-   * Triggered upon feature translation start.
-   * @event ol.interaction.Translate.Event#translatestart
-   * @api
-   */
-  TRANSLATESTART: 'translatestart',
-  /**
-   * Triggered upon feature translation.
-   * @event ol.interaction.Translate.Event#translating
-   * @api
-   */
-  TRANSLATING: 'translating',
-  /**
-   * Triggered upon feature translation end.
-   * @event ol.interaction.Translate.Event#translateend
-   * @api
-   */
-  TRANSLATEEND: 'translateend'
-};
-
-goog.provide('ol.layer.Heatmap');
-
-goog.require('ol.events');
-goog.require('ol');
-goog.require('ol.Object');
-goog.require('ol.dom');
-goog.require('ol.layer.Vector');
-goog.require('ol.math');
-goog.require('ol.obj');
-goog.require('ol.render.Event');
-goog.require('ol.style.Icon');
-goog.require('ol.style.Style');
-
-
-/**
- * @classdesc
- * Layer for rendering vector data as a heatmap.
- * Note that any property set in the options is set as a {@link ol.Object}
- * property on the layer object; for example, setting `title: 'My Title'` in the
- * options means that `title` is observable, and has get/set accessors.
- *
- * @constructor
- * @extends {ol.layer.Vector}
- * @fires ol.render.Event
- * @param {olx.layer.HeatmapOptions=} opt_options Options.
- * @api
- */
-ol.layer.Heatmap = function(opt_options) {
-  var options = opt_options ? opt_options : {};
-
-  var baseOptions = ol.obj.assign({}, options);
-
-  delete baseOptions.gradient;
-  delete baseOptions.radius;
-  delete baseOptions.blur;
-  delete baseOptions.shadow;
-  delete baseOptions.weight;
-  ol.layer.Vector.call(this, /** @type {olx.layer.VectorOptions} */ (baseOptions));
-
-  /**
-   * @private
-   * @type {Uint8ClampedArray}
-   */
-  this.gradient_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.shadow_ = options.shadow !== undefined ? options.shadow : 250;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.circleImage_ = undefined;
-
-  /**
-   * @private
-   * @type {Array.<Array.<ol.style.Style>>}
-   */
-  this.styleCache_ = null;
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.Heatmap.Property.GRADIENT),
-      this.handleGradientChanged_, this);
-
-  this.setGradient(options.gradient ?
-      options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);
-
-  this.setBlur(options.blur !== undefined ? options.blur : 15);
-
-  this.setRadius(options.radius !== undefined ? options.radius : 8);
-
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.Heatmap.Property.BLUR),
-      this.handleStyleChanged_, this);
-  ol.events.listen(this,
-      ol.Object.getChangeEventType(ol.layer.Heatmap.Property.RADIUS),
-      this.handleStyleChanged_, this);
-
-  this.handleStyleChanged_();
-
-  var weight = options.weight ? options.weight : 'weight';
-  var weightFunction;
-  if (typeof weight === 'string') {
-    weightFunction = function(feature) {
-      return feature.get(weight);
-    };
-  } else {
-    weightFunction = weight;
-  }
-  ol.DEBUG && console.assert(typeof weightFunction === 'function',
-      'weightFunction should be a function');
-
-  this.setStyle(function(feature, resolution) {
-    ol.DEBUG && console.assert(this.styleCache_, 'this.styleCache_ expected');
-    ol.DEBUG && console.assert(this.circleImage_ !== undefined,
-        'this.circleImage_ should be defined');
-    var weight = weightFunction(feature);
-    var opacity = weight !== undefined ? ol.math.clamp(weight, 0, 1) : 1;
-    // cast to 8 bits
-    var index = (255 * opacity) | 0;
-    var style = this.styleCache_[index];
-    if (!style) {
-      style = [
-        new ol.style.Style({
-          image: new ol.style.Icon({
-            opacity: opacity,
-            src: this.circleImage_
-          })
-        })
-      ];
-      this.styleCache_[index] = style;
-    }
-    return style;
-  }.bind(this));
-
-  // For performance reasons, don't sort the features before rendering.
-  // The render order is not relevant for a heatmap representation.
-  this.setRenderOrder(null);
-
-  ol.events.listen(this, ol.render.Event.Type.RENDER, this.handleRender_, this);
-
-};
-ol.inherits(ol.layer.Heatmap, ol.layer.Vector);
-
-
-/**
- * @const
- * @type {Array.<string>}
- */
-ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
-
-
-/**
- * @param {Array.<string>} colors A list of colored.
- * @return {Uint8ClampedArray} An array.
- * @private
- */
-ol.layer.Heatmap.createGradient_ = function(colors) {
-  var width = 1;
-  var height = 256;
-  var context = ol.dom.createCanvasContext2D(width, height);
-
-  var gradient = context.createLinearGradient(0, 0, width, height);
-  var step = 1 / (colors.length - 1);
-  for (var i = 0, ii = colors.length; i < ii; ++i) {
-    gradient.addColorStop(i * step, colors[i]);
-  }
-
-  context.fillStyle = gradient;
-  context.fillRect(0, 0, width, height);
-
-  return context.getImageData(0, 0, width, height).data;
-};
-
-
-/**
- * @return {string} Data URL for a circle.
- * @private
- */
-ol.layer.Heatmap.prototype.createCircle_ = function() {
-  var radius = this.getRadius();
-  var blur = this.getBlur();
-  ol.DEBUG && console.assert(radius !== undefined && blur !== undefined,
-      'radius and blur should be defined');
-  var halfSize = radius + blur + 1;
-  var size = 2 * halfSize;
-  var context = ol.dom.createCanvasContext2D(size, size);
-  context.shadowOffsetX = context.shadowOffsetY = this.shadow_;
-  context.shadowBlur = blur;
-  context.shadowColor = '#000';
-  context.beginPath();
-  var center = halfSize - this.shadow_;
-  context.arc(center, center, radius, 0, Math.PI * 2, true);
-  context.fill();
-  return context.canvas.toDataURL();
-};
-
-
-/**
- * Return the blur size in pixels.
- * @return {number} Blur size in pixels.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.getBlur = function() {
-  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property.BLUR));
-};
-
-
-/**
- * Return the gradient colors as array of strings.
- * @return {Array.<string>} Colors.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.getGradient = function() {
-  return /** @type {Array.<string>} */ (
-      this.get(ol.layer.Heatmap.Property.GRADIENT));
-};
-
-
-/**
- * Return the size of the radius in pixels.
- * @return {number} Radius size in pixel.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.getRadius = function() {
-  return /** @type {number} */ (this.get(ol.layer.Heatmap.Property.RADIUS));
-};
-
-
-/**
- * @private
- */
-ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
-  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
-};
-
-
-/**
- * @private
- */
-ol.layer.Heatmap.prototype.handleStyleChanged_ = function() {
-  this.circleImage_ = this.createCircle_();
-  this.styleCache_ = new Array(256);
-  this.changed();
-};
-
-
-/**
- * @param {ol.render.Event} event Post compose event
- * @private
- */
-ol.layer.Heatmap.prototype.handleRender_ = function(event) {
-  ol.DEBUG && console.assert(event.type == ol.render.Event.Type.RENDER,
-      'event.type should be RENDER');
-  ol.DEBUG && console.assert(this.gradient_, 'this.gradient_ expected');
-  var context = event.context;
-  var canvas = context.canvas;
-  var image = context.getImageData(0, 0, canvas.width, canvas.height);
-  var view8 = image.data;
-  var i, ii, alpha;
-  for (i = 0, ii = view8.length; i < ii; i += 4) {
-    alpha = view8[i + 3] * 4;
-    if (alpha) {
-      view8[i] = this.gradient_[alpha];
-      view8[i + 1] = this.gradient_[alpha + 1];
-      view8[i + 2] = this.gradient_[alpha + 2];
-    }
-  }
-  context.putImageData(image, 0, 0);
-};
-
-
-/**
- * Set the blur size in pixels.
- * @param {number} blur Blur size in pixels.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.setBlur = function(blur) {
-  this.set(ol.layer.Heatmap.Property.BLUR, blur);
-};
-
-
-/**
- * Set the gradient colors as array of strings.
- * @param {Array.<string>} colors Gradient.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.setGradient = function(colors) {
-  this.set(ol.layer.Heatmap.Property.GRADIENT, colors);
-};
-
-
-/**
- * Set the size of the radius in pixels.
- * @param {number} radius Radius size in pixel.
- * @api
- * @observable
- */
-ol.layer.Heatmap.prototype.setRadius = function(radius) {
-  this.set(ol.layer.Heatmap.Property.RADIUS, radius);
-};
-
-
-/**
- * @enum {string}
- */
-ol.layer.Heatmap.Property = {
-  BLUR: 'blur',
-  GRADIENT: 'gradient',
-  RADIUS: 'radius'
-};
-
-goog.provide('ol.net');
-
-goog.require('ol');
-
-
-/**
- * Simple JSONP helper. Supports error callbacks and a custom callback param.
- * The error callback will be called when no JSONP is executed after 10 seconds.
- *
- * @param {string} url Request url. A 'callback' query parameter will be
- *     appended.
- * @param {Function} callback Callback on success.
- * @param {function()=} opt_errback Callback on error.
- * @param {string=} opt_callbackParam Custom query parameter for the JSONP
- *     callback. Default is 'callback'.
- */
-ol.net.jsonp = function(url, callback, opt_errback, opt_callbackParam) {
-  var script = document.createElement('script');
-  var key = 'olc_' + ol.getUid(callback);
-  function cleanup() {
-    delete window[key];
-    script.parentNode.removeChild(script);
-  }
-  script.async = true;
-  script.src = url + (url.indexOf('?') == -1 ? '?' : '&') +
-      (opt_callbackParam || 'callback') + '=' + key;
-  var timer = setTimeout(function() {
-    cleanup();
-    if (opt_errback) {
-      opt_errback();
-    }
-  }, 10000);
-  window[key] = function(data) {
-    clearTimeout(timer);
-    cleanup();
-    callback(data);
-  };
-  document.getElementsByTagName('head')[0].appendChild(script);
-};
-
-goog.provide('ol.render');
-
-goog.require('ol.has');
-goog.require('ol.transform');
-goog.require('ol.render.canvas.Immediate');
-
-
-/**
- * Binds a Canvas Immediate API to a canvas context, to allow drawing geometries
- * to the context's canvas.
- *
- * The units for geometry coordinates are css pixels relative to the top left
- * corner of the canvas element.
- * ```js
- * var canvas = document.createElement('canvas');
- * var render = ol.render.toContext(canvas.getContext('2d'),
- *     { size: [100, 100] });
- * render.setFillStrokeStyle(new ol.style.Fill({ color: blue }));
- * render.drawPolygon(
- *     new ol.geom.Polygon([[[0, 0], [100, 100], [100, 0], [0, 0]]]));
- * ```
- *
- * @param {CanvasRenderingContext2D} context Canvas context.
- * @param {olx.render.ToContextOptions=} opt_options Options.
- * @return {ol.render.canvas.Immediate} Canvas Immediate.
- * @api
- */
-ol.render.toContext = function(context, opt_options) {
-  var canvas = context.canvas;
-  var options = opt_options ? opt_options : {};
-  var pixelRatio = options.pixelRatio || ol.has.DEVICE_PIXEL_RATIO;
-  var size = options.size;
-  if (size) {
-    canvas.width = size[0] * pixelRatio;
-    canvas.height = size[1] * pixelRatio;
-    canvas.style.width = size[0] + 'px';
-    canvas.style.height = size[1] + 'px';
-  }
-  var extent = [0, 0, canvas.width, canvas.height];
-  var transform = ol.transform.scale(ol.transform.create(), pixelRatio, pixelRatio);
-  return new ol.render.canvas.Immediate(context, pixelRatio, extent, transform,
-      0);
-};
-
-goog.provide('ol.reproj.Tile');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.reproj');
-goog.require('ol.reproj.Triangulation');
-
-
-/**
- * @classdesc
- * Class encapsulating single reprojected tile.
- * See {@link ol.source.TileImage}.
- *
- * @constructor
- * @extends {ol.Tile}
- * @param {ol.proj.Projection} sourceProj Source projection.
- * @param {ol.tilegrid.TileGrid} sourceTileGrid Source tile grid.
- * @param {ol.proj.Projection} targetProj Target projection.
- * @param {ol.tilegrid.TileGrid} targetTileGrid Target tile grid.
- * @param {ol.TileCoord} tileCoord Coordinate of the tile.
- * @param {ol.TileCoord} wrappedTileCoord Coordinate of the tile wrapped in X.
- * @param {number} pixelRatio Pixel ratio.
- * @param {number} gutter Gutter of the source tiles.
- * @param {ol.ReprojTileFunctionType} getTileFunction
- *     Function returning source tiles (z, x, y, pixelRatio).
- * @param {number=} opt_errorThreshold Acceptable reprojection error (in px).
- * @param {boolean=} opt_renderEdges Render reprojection edges.
- */
-ol.reproj.Tile = function(sourceProj, sourceTileGrid,
-    targetProj, targetTileGrid, tileCoord, wrappedTileCoord,
-    pixelRatio, gutter, getTileFunction,
-    opt_errorThreshold,
-    opt_renderEdges) {
-  ol.Tile.call(this, tileCoord, ol.Tile.State.IDLE);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderEdges_ = opt_renderEdges !== undefined ? opt_renderEdges : false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.pixelRatio_ = pixelRatio;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.gutter_ = gutter;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = null;
-
-  /**
-   * @private
-   * @type {ol.tilegrid.TileGrid}
-   */
-  this.sourceTileGrid_ = sourceTileGrid;
-
-  /**
-   * @private
-   * @type {ol.tilegrid.TileGrid}
-   */
-  this.targetTileGrid_ = targetTileGrid;
-
-  /**
-   * @private
-   * @type {ol.TileCoord}
-   */
-  this.wrappedTileCoord_ = wrappedTileCoord ? wrappedTileCoord : tileCoord;
-
-  /**
-   * @private
-   * @type {!Array.<ol.Tile>}
-   */
-  this.sourceTiles_ = [];
-
-  /**
-   * @private
-   * @type {Array.<ol.EventsKey>}
-   */
-  this.sourcesListenerKeys_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.sourceZ_ = 0;
-
-  var targetExtent = targetTileGrid.getTileCoordExtent(this.wrappedTileCoord_);
-  var maxTargetExtent = this.targetTileGrid_.getExtent();
-  var maxSourceExtent = this.sourceTileGrid_.getExtent();
-
-  var limitedTargetExtent = maxTargetExtent ?
-      ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;
-
-  if (ol.extent.getArea(limitedTargetExtent) === 0) {
-    // Tile is completely outside range -> EMPTY
-    // TODO: is it actually correct that the source even creates the tile ?
-    this.state = ol.Tile.State.EMPTY;
-    return;
-  }
-
-  var sourceProjExtent = sourceProj.getExtent();
-  if (sourceProjExtent) {
-    if (!maxSourceExtent) {
-      maxSourceExtent = sourceProjExtent;
-    } else {
-      maxSourceExtent = ol.extent.getIntersection(
-          maxSourceExtent, sourceProjExtent);
-    }
-  }
-
-  var targetResolution = targetTileGrid.getResolution(
-      this.wrappedTileCoord_[0]);
-
-  var targetCenter = ol.extent.getCenter(limitedTargetExtent);
-  var sourceResolution = ol.reproj.calculateSourceResolution(
-      sourceProj, targetProj, targetCenter, targetResolution);
-
-  if (!isFinite(sourceResolution) || sourceResolution <= 0) {
-    // invalid sourceResolution -> EMPTY
-    // probably edges of the projections when no extent is defined
-    this.state = ol.Tile.State.EMPTY;
-    return;
-  }
-
-  var errorThresholdInPixels = opt_errorThreshold !== undefined ?
-      opt_errorThreshold : ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;
-
-  /**
-   * @private
-   * @type {!ol.reproj.Triangulation}
-   */
-  this.triangulation_ = new ol.reproj.Triangulation(
-      sourceProj, targetProj, limitedTargetExtent, maxSourceExtent,
-      sourceResolution * errorThresholdInPixels);
-
-  if (this.triangulation_.getTriangles().length === 0) {
-    // no valid triangles -> EMPTY
-    this.state = ol.Tile.State.EMPTY;
-    return;
-  }
-
-  this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution);
-  var sourceExtent = this.triangulation_.calculateSourceExtent();
-
-  if (maxSourceExtent) {
-    if (sourceProj.canWrapX()) {
-      sourceExtent[1] = ol.math.clamp(
-          sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]);
-      sourceExtent[3] = ol.math.clamp(
-          sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]);
-    } else {
-      sourceExtent = ol.extent.getIntersection(sourceExtent, maxSourceExtent);
-    }
-  }
-
-  if (!ol.extent.getArea(sourceExtent)) {
-    this.state = ol.Tile.State.EMPTY;
-  } else {
-    var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(
-        sourceExtent, this.sourceZ_);
-
-    var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight();
-    if (ol.DEBUG && !(tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES)) {
-      console.assert(false, 'reasonable number of tiles is required');
-      this.state = ol.Tile.State.ERROR;
-      return;
-    }
-    for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) {
-      for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) {
-        var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio);
-        if (tile) {
-          this.sourceTiles_.push(tile);
-        }
-      }
-    }
-
-    if (this.sourceTiles_.length === 0) {
-      this.state = ol.Tile.State.EMPTY;
-    }
-  }
-};
-ol.inherits(ol.reproj.Tile, ol.Tile);
-
-
-/**
- * @inheritDoc
- */
-ol.reproj.Tile.prototype.disposeInternal = function() {
-  if (this.state == ol.Tile.State.LOADING) {
-    this.unlistenSources_();
-  }
-  ol.Tile.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.reproj.Tile.prototype.getImage = function() {
-  return this.canvas_;
-};
-
-
-/**
- * @private
- */
-ol.reproj.Tile.prototype.reproject_ = function() {
-  var sources = [];
-  this.sourceTiles_.forEach(function(tile, i, arr) {
-    if (tile && tile.getState() == ol.Tile.State.LOADED) {
-      sources.push({
-        extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord),
-        image: tile.getImage()
-      });
-    }
-  }, this);
-  this.sourceTiles_.length = 0;
-
-  if (sources.length === 0) {
-    this.state = ol.Tile.State.ERROR;
-  } else {
-    var z = this.wrappedTileCoord_[0];
-    var size = this.targetTileGrid_.getTileSize(z);
-    var width = typeof size === 'number' ? size : size[0];
-    var height = typeof size === 'number' ? size : size[1];
-    var targetResolution = this.targetTileGrid_.getResolution(z);
-    var sourceResolution = this.sourceTileGrid_.getResolution(this.sourceZ_);
-
-    var targetExtent = this.targetTileGrid_.getTileCoordExtent(
-        this.wrappedTileCoord_);
-    this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_,
-        sourceResolution, this.sourceTileGrid_.getExtent(),
-        targetResolution, targetExtent, this.triangulation_, sources,
-        this.gutter_, this.renderEdges_);
-
-    this.state = ol.Tile.State.LOADED;
-  }
-  this.changed();
-};
-
-
-/**
- * @inheritDoc
- */
-ol.reproj.Tile.prototype.load = function() {
-  if (this.state == ol.Tile.State.IDLE) {
-    this.state = ol.Tile.State.LOADING;
-    this.changed();
-
-    var leftToLoad = 0;
-
-    ol.DEBUG && console.assert(!this.sourcesListenerKeys_,
-        'this.sourcesListenerKeys_ should be null');
-
-    this.sourcesListenerKeys_ = [];
-    this.sourceTiles_.forEach(function(tile, i, arr) {
-      var state = tile.getState();
-      if (state == ol.Tile.State.IDLE || state == ol.Tile.State.LOADING) {
-        leftToLoad++;
-
-        var sourceListenKey;
-        sourceListenKey = ol.events.listen(tile, ol.events.EventType.CHANGE,
-            function(e) {
-              var state = tile.getState();
-              if (state == ol.Tile.State.LOADED ||
-                  state == ol.Tile.State.ERROR ||
-                  state == ol.Tile.State.EMPTY) {
-                ol.events.unlistenByKey(sourceListenKey);
-                leftToLoad--;
-                ol.DEBUG && console.assert(leftToLoad >= 0,
-                    'leftToLoad should not be negative');
-                if (leftToLoad === 0) {
-                  this.unlistenSources_();
-                  this.reproject_();
-                }
-              }
-            }, this);
-        this.sourcesListenerKeys_.push(sourceListenKey);
-      }
-    }, this);
-
-    this.sourceTiles_.forEach(function(tile, i, arr) {
-      var state = tile.getState();
-      if (state == ol.Tile.State.IDLE) {
-        tile.load();
-      }
-    });
-
-    if (leftToLoad === 0) {
-      setTimeout(this.reproject_.bind(this), 0);
-    }
-  }
-};
-
-
-/**
- * @private
- */
-ol.reproj.Tile.prototype.unlistenSources_ = function() {
-  this.sourcesListenerKeys_.forEach(ol.events.unlistenByKey);
-  this.sourcesListenerKeys_ = null;
-};
-
-goog.provide('ol.TileUrlFunction');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.math');
-goog.require('ol.tilecoord');
-
-
-/**
- * @param {string} template Template.
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @return {ol.TileUrlFunctionType} Tile URL function.
- */
-ol.TileUrlFunction.createFromTemplate = function(template, tileGrid) {
-  var zRegEx = /\{z\}/g;
-  var xRegEx = /\{x\}/g;
-  var yRegEx = /\{y\}/g;
-  var dashYRegEx = /\{-y\}/g;
-  return (
-      /**
-       * @param {ol.TileCoord} tileCoord Tile Coordinate.
-       * @param {number} pixelRatio Pixel ratio.
-       * @param {ol.proj.Projection} projection Projection.
-       * @return {string|undefined} Tile URL.
-       */
-      function(tileCoord, pixelRatio, projection) {
-        if (!tileCoord) {
-          return undefined;
-        } else {
-          return template.replace(zRegEx, tileCoord[0].toString())
-              .replace(xRegEx, tileCoord[1].toString())
-              .replace(yRegEx, function() {
-                var y = -tileCoord[2] - 1;
-                return y.toString();
-              })
-              .replace(dashYRegEx, function() {
-                var z = tileCoord[0];
-                var range = tileGrid.getFullTileRange(z);
-                ol.asserts.assert(range, 55); // The {-y} placeholder requires a tile grid with extent
-                var y = range.getHeight() + tileCoord[2];
-                return y.toString();
-              });
-        }
-      });
-};
-
-
-/**
- * @param {Array.<string>} templates Templates.
- * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
- * @return {ol.TileUrlFunctionType} Tile URL function.
- */
-ol.TileUrlFunction.createFromTemplates = function(templates, tileGrid) {
-  var len = templates.length;
-  var tileUrlFunctions = new Array(len);
-  for (var i = 0; i < len; ++i) {
-    tileUrlFunctions[i] = ol.TileUrlFunction.createFromTemplate(
-        templates[i], tileGrid);
-  }
-  return ol.TileUrlFunction.createFromTileUrlFunctions(tileUrlFunctions);
-};
-
-
-/**
- * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
- * @return {ol.TileUrlFunctionType} Tile URL function.
- */
-ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
-  ol.DEBUG && console.assert(tileUrlFunctions.length > 0,
-      'Length of tile url functions should be greater than 0');
-  if (tileUrlFunctions.length === 1) {
-    return tileUrlFunctions[0];
-  }
-  return (
-      /**
-       * @param {ol.TileCoord} tileCoord Tile Coordinate.
-       * @param {number} pixelRatio Pixel ratio.
-       * @param {ol.proj.Projection} projection Projection.
-       * @return {string|undefined} Tile URL.
-       */
-      function(tileCoord, pixelRatio, projection) {
-        if (!tileCoord) {
-          return undefined;
-        } else {
-          var h = ol.tilecoord.hash(tileCoord);
-          var index = ol.math.modulo(h, tileUrlFunctions.length);
-          return tileUrlFunctions[index](tileCoord, pixelRatio, projection);
-        }
-      });
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {string|undefined} Tile URL.
- */
-ol.TileUrlFunction.nullTileUrlFunction = function(tileCoord, pixelRatio, projection) {
-  return undefined;
-};
-
-
-/**
- * @param {string} url URL.
- * @return {Array.<string>} Array of urls.
- */
-ol.TileUrlFunction.expandUrl = function(url) {
-  var urls = [];
-  var match = /\{([a-z])-([a-z])\}/.exec(url);
-  if (match) {
-    // char range
-    var startCharCode = match[1].charCodeAt(0);
-    var stopCharCode = match[2].charCodeAt(0);
-    var charCode;
-    for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) {
-      urls.push(url.replace(match[0], String.fromCharCode(charCode)));
-    }
-    return urls;
-  }
-  match = match = /\{(\d+)-(\d+)\}/.exec(url);
-  if (match) {
-    // number range
-    var stop = parseInt(match[2], 10);
-    for (var i = parseInt(match[1], 10); i <= stop; i++) {
-      urls.push(url.replace(match[0], i.toString()));
-    }
-    return urls;
-  }
-  urls.push(url);
-  return urls;
-};
-
-goog.provide('ol.TileCache');
-
-goog.require('ol');
-goog.require('ol.structs.LRUCache');
-
-
-/**
- * @constructor
- * @extends {ol.structs.LRUCache.<ol.Tile>}
- * @param {number=} opt_highWaterMark High water mark.
- * @struct
- */
-ol.TileCache = function(opt_highWaterMark) {
-
-  ol.structs.LRUCache.call(this);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.highWaterMark_ = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048;
-
-};
-ol.inherits(ol.TileCache, ol.structs.LRUCache);
-
-
-/**
- * @return {boolean} Can expire cache.
- */
-ol.TileCache.prototype.canExpireCache = function() {
-  return this.getCount() > this.highWaterMark_;
-};
-
-
-/**
- * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
- */
-ol.TileCache.prototype.expireCache = function(usedTiles) {
-  var tile, zKey;
-  while (this.canExpireCache()) {
-    tile = this.peekLast();
-    zKey = tile.tileCoord[0].toString();
-    if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) {
-      break;
-    } else {
-      this.pop().dispose();
-    }
-  }
-};
-
-goog.provide('ol.source.Tile');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.TileCache');
-goog.require('ol.events.Event');
-goog.require('ol.proj');
-goog.require('ol.size');
-goog.require('ol.source.Source');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid');
-
-
-/**
- * @classdesc
- * Abstract base class; normally only used for creating subclasses and not
- * instantiated in apps.
- * Base class for sources providing images divided into a tile grid.
- *
- * @constructor
- * @extends {ol.source.Source}
- * @param {ol.SourceTileOptions} options Tile source options.
- * @api
- */
-ol.source.Tile = function(options) {
-
-  ol.source.Source.call(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    projection: options.projection,
-    state: options.state,
-    wrapX: options.wrapX
-  });
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.opaque_ = options.opaque !== undefined ? options.opaque : false;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.tilePixelRatio_ = options.tilePixelRatio !== undefined ?
-      options.tilePixelRatio : 1;
-
-  /**
-   * @protected
-   * @type {ol.tilegrid.TileGrid}
-   */
-  this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null;
-
-  /**
-   * @protected
-   * @type {ol.TileCache}
-   */
-  this.tileCache = new ol.TileCache(options.cacheSize);
-
-  /**
-   * @protected
-   * @type {ol.Size}
-   */
-  this.tmpSize = [0, 0];
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.key_ = '';
-
-};
-ol.inherits(ol.source.Tile, ol.source.Source);
-
-
-/**
- * @return {boolean} Can expire cache.
- */
-ol.source.Tile.prototype.canExpireCache = function() {
-  return this.tileCache.canExpireCache();
-};
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
- */
-ol.source.Tile.prototype.expireCache = function(projection, usedTiles) {
-  var tileCache = this.getTileCacheForProjection(projection);
-  if (tileCache) {
-    tileCache.expireCache(usedTiles);
-  }
-};
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @param {number} z Zoom level.
- * @param {ol.TileRange} tileRange Tile range.
- * @param {function(ol.Tile):(boolean|undefined)} callback Called with each
- *     loaded tile.  If the callback returns `false`, the tile will not be
- *     considered loaded.
- * @return {boolean} The tile range is fully covered with loaded tiles.
- */
-ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange, callback) {
-  var tileCache = this.getTileCacheForProjection(projection);
-  if (!tileCache) {
-    return false;
-  }
-
-  var covered = true;
-  var tile, tileCoordKey, loaded;
-  for (var x = tileRange.minX; x <= tileRange.maxX; ++x) {
-    for (var y = tileRange.minY; y <= tileRange.maxY; ++y) {
-      tileCoordKey = this.getKeyZXY(z, x, y);
-      loaded = false;
-      if (tileCache.containsKey(tileCoordKey)) {
-        tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey));
-        loaded = tile.getState() === ol.Tile.State.LOADED;
-        if (loaded) {
-          loaded = (callback(tile) !== false);
-        }
-      }
-      if (!loaded) {
-        covered = false;
-      }
-    }
-  }
-  return covered;
-};
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @return {number} Gutter.
- */
-ol.source.Tile.prototype.getGutter = function(projection) {
-  return 0;
-};
-
-
-/**
- * Return the key to be used for all tiles in the source.
- * @return {string} The key for all tiles.
- * @protected
- */
-ol.source.Tile.prototype.getKey = function() {
-  return this.key_;
-};
-
-
-/**
- * Set the value to be used as the key for all tiles in the source.
- * @param {string} key The key for tiles.
- * @protected
- */
-ol.source.Tile.prototype.setKey = function(key) {
-  if (this.key_ !== key) {
-    this.key_ = key;
-    this.changed();
-  }
-};
-
-
-/**
- * @param {number} z Z.
- * @param {number} x X.
- * @param {number} y Y.
- * @return {string} Key.
- * @protected
- */
-ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @return {boolean} Opaque.
- */
-ol.source.Tile.prototype.getOpaque = function(projection) {
-  return this.opaque_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.Tile.prototype.getResolutions = function() {
-  return this.tileGrid.getResolutions();
-};
-
-
-/**
- * @abstract
- * @param {number} z Tile coordinate z.
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {!ol.Tile} Tile.
- */
-ol.source.Tile.prototype.getTile = function(z, x, y, pixelRatio, projection) {};
-
-
-/**
- * Return the tile grid of the tile source.
- * @return {ol.tilegrid.TileGrid} Tile grid.
- * @api stable
- */
-ol.source.Tile.prototype.getTileGrid = function() {
-  return this.tileGrid;
-};
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @return {!ol.tilegrid.TileGrid} Tile grid.
- */
-ol.source.Tile.prototype.getTileGridForProjection = function(projection) {
-  if (!this.tileGrid) {
-    return ol.tilegrid.getForProjection(projection);
-  } else {
-    return this.tileGrid;
-  }
-};
-
-
-/**
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.TileCache} Tile cache.
- * @protected
- */
-ol.source.Tile.prototype.getTileCacheForProjection = function(projection) {
-  var thisProj = this.getProjection();
-  if (thisProj && !ol.proj.equivalent(thisProj, projection)) {
-    return null;
-  } else {
-    return this.tileCache;
-  }
-};
-
-
-/**
- * Get the tile pixel ratio for this source. Subclasses may override this
- * method, which is meant to return a supported pixel ratio that matches the
- * provided `opt_pixelRatio` as close as possible. When no `opt_pixelRatio` is
- * provided, it is meant to return `this.tilePixelRatio_`.
- * @param {number=} opt_pixelRatio Pixel ratio.
- * @return {number} Tile pixel ratio.
- */
-ol.source.Tile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
-  return this.tilePixelRatio_;
-};
-
-
-/**
- * @param {number} z Z.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @return {ol.Size} Tile size.
- */
-ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) {
-  var tileGrid = this.getTileGridForProjection(projection);
-  var tilePixelRatio = this.getTilePixelRatio(pixelRatio);
-  var tileSize = ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize);
-  if (tilePixelRatio == 1) {
-    return tileSize;
-  } else {
-    return ol.size.scale(tileSize, tilePixelRatio, this.tmpSize);
-  }
-};
-
-
-/**
- * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate
- * is outside the resolution and extent range of the tile grid, `null` will be
- * returned.
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.proj.Projection=} opt_projection Projection.
- * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or
- *     null if no tile URL should be created for the passed `tileCoord`.
- */
-ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) {
-  var projection = opt_projection !== undefined ?
-      opt_projection : this.getProjection();
-  var tileGrid = this.getTileGridForProjection(projection);
-  if (this.getWrapX() && projection.isGlobal()) {
-    tileCoord = ol.tilegrid.wrapX(tileGrid, tileCoord, projection);
-  }
-  return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.Tile.prototype.refresh = function() {
-  this.tileCache.clear();
-  this.changed();
-};
-
-
-/**
- * Marks a tile coord as being used, without triggering a load.
- * @param {number} z Tile coordinate z.
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @param {ol.proj.Projection} projection Projection.
- */
-ol.source.Tile.prototype.useTile = ol.nullFunction;
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.source.Tile} instances are instances of this
- * type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.source.Tile.Event}
- * @param {string} type Type.
- * @param {ol.Tile} tile The tile.
- */
-ol.source.Tile.Event = function(type, tile) {
-
-  ol.events.Event.call(this, type);
-
-  /**
-   * The tile related to the event.
-   * @type {ol.Tile}
-   * @api
-   */
-  this.tile = tile;
-
-};
-ol.inherits(ol.source.Tile.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.source.Tile.EventType = {
-
-  /**
-   * Triggered when a tile starts loading.
-   * @event ol.source.Tile.Event#tileloadstart
-   * @api stable
-   */
-  TILELOADSTART: 'tileloadstart',
-
-  /**
-   * Triggered when a tile finishes loading.
-   * @event ol.source.Tile.Event#tileloadend
-   * @api stable
-   */
-  TILELOADEND: 'tileloadend',
-
-  /**
-   * Triggered if tile loading results in an error.
-   * @event ol.source.Tile.Event#tileloaderror
-   * @api stable
-   */
-  TILELOADERROR: 'tileloaderror'
-
-};
-
-goog.provide('ol.source.UrlTile');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.source.Tile');
-
-
-/**
- * @classdesc
- * Base class for sources providing tiles divided into a tile grid over http.
- *
- * @constructor
- * @fires ol.source.Tile.Event
- * @extends {ol.source.Tile}
- * @param {ol.SourceUrlTileOptions} options Image tile options.
- */
-ol.source.UrlTile = function(options) {
-
-  ol.source.Tile.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    extent: options.extent,
-    logo: options.logo,
-    opaque: options.opaque,
-    projection: options.projection,
-    state: options.state,
-    tileGrid: options.tileGrid,
-    tilePixelRatio: options.tilePixelRatio,
-    wrapX: options.wrapX
-  });
-
-  /**
-   * @protected
-   * @type {ol.TileLoadFunctionType}
-   */
-  this.tileLoadFunction = options.tileLoadFunction;
-
-  /**
-   * @protected
-   * @type {ol.TileUrlFunctionType}
-   */
-  this.tileUrlFunction = this.fixedTileUrlFunction ?
-      this.fixedTileUrlFunction.bind(this) :
-      ol.TileUrlFunction.nullTileUrlFunction;
-
-  /**
-   * @protected
-   * @type {!Array.<string>|null}
-   */
-  this.urls = null;
-
-  if (options.urls) {
-    this.setUrls(options.urls);
-  } else if (options.url) {
-    this.setUrl(options.url);
-  }
-  if (options.tileUrlFunction) {
-    this.setTileUrlFunction(options.tileUrlFunction);
-  }
-
-};
-ol.inherits(ol.source.UrlTile, ol.source.Tile);
-
-
-/**
- * @type {ol.TileUrlFunctionType|undefined}
- * @protected
- */
-ol.source.UrlTile.prototype.fixedTileUrlFunction;
-
-/**
- * Return the tile load function of the source.
- * @return {ol.TileLoadFunctionType} TileLoadFunction
- * @api
- */
-ol.source.UrlTile.prototype.getTileLoadFunction = function() {
-  return this.tileLoadFunction;
-};
-
-
-/**
- * Return the tile URL function of the source.
- * @return {ol.TileUrlFunctionType} TileUrlFunction
- * @api
- */
-ol.source.UrlTile.prototype.getTileUrlFunction = function() {
-  return this.tileUrlFunction;
-};
-
-
-/**
- * Return the URLs used for this source.
- * When a tileUrlFunction is used instead of url or urls,
- * null will be returned.
- * @return {!Array.<string>|null} URLs.
- * @api
- */
-ol.source.UrlTile.prototype.getUrls = function() {
-  return this.urls;
-};
-
-
-/**
- * Handle tile change events.
- * @param {ol.events.Event} event Event.
- * @protected
- */
-ol.source.UrlTile.prototype.handleTileChange = function(event) {
-  var tile = /** @type {ol.Tile} */ (event.target);
-  switch (tile.getState()) {
-    case ol.Tile.State.LOADING:
-      this.dispatchEvent(
-          new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADSTART, tile));
-      break;
-    case ol.Tile.State.LOADED:
-      this.dispatchEvent(
-          new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADEND, tile));
-      break;
-    case ol.Tile.State.ERROR:
-      this.dispatchEvent(
-          new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADERROR, tile));
-      break;
-    default:
-      // pass
-  }
-};
-
-
-/**
- * Set the tile load function of the source.
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
- * @api
- */
-ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) {
-  this.tileCache.clear();
-  this.tileLoadFunction = tileLoadFunction;
-  this.changed();
-};
-
-
-/**
- * Set the tile URL function of the source.
- * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
- * @param {string=} opt_key Optional new tile key for the source.
- * @api
- */
-ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) {
-  this.tileUrlFunction = tileUrlFunction;
-  if (typeof opt_key !== 'undefined') {
-    this.setKey(opt_key);
-  } else {
-    this.changed();
-  }
-};
-
-
-/**
- * Set the URL to use for requests.
- * @param {string} url URL.
- * @api stable
- */
-ol.source.UrlTile.prototype.setUrl = function(url) {
-  var urls = this.urls = ol.TileUrlFunction.expandUrl(url);
-  this.setTileUrlFunction(this.fixedTileUrlFunction ?
-      this.fixedTileUrlFunction.bind(this) :
-      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url);
-};
-
-
-/**
- * Set the URLs to use for requests.
- * @param {Array.<string>} urls URLs.
- * @api stable
- */
-ol.source.UrlTile.prototype.setUrls = function(urls) {
-  this.urls = urls;
-  var key = urls.join('\n');
-  this.setTileUrlFunction(this.fixedTileUrlFunction ?
-      this.fixedTileUrlFunction.bind(this) :
-      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), key);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.UrlTile.prototype.useTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    this.tileCache.get(tileCoordKey);
-  }
-};
-
-goog.provide('ol.source.TileImage');
-
-goog.require('ol');
-goog.require('ol.ImageTile');
-goog.require('ol.Tile');
-goog.require('ol.TileCache');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.proj');
-goog.require('ol.reproj.Tile');
-goog.require('ol.source.UrlTile');
-goog.require('ol.tilegrid');
-
-
-/**
- * @classdesc
- * Base class for sources providing images divided into a tile grid.
- *
- * @constructor
- * @fires ol.source.Tile.Event
- * @extends {ol.source.UrlTile}
- * @param {olx.source.TileImageOptions} options Image tile options.
- * @api
- */
-ol.source.TileImage = function(options) {
-
-  ol.source.UrlTile.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    extent: options.extent,
-    logo: options.logo,
-    opaque: options.opaque,
-    projection: options.projection,
-    state: options.state,
-    tileGrid: options.tileGrid,
-    tileLoadFunction: options.tileLoadFunction ?
-        options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction,
-    tilePixelRatio: options.tilePixelRatio,
-    tileUrlFunction: options.tileUrlFunction,
-    url: options.url,
-    urls: options.urls,
-    wrapX: options.wrapX
-  });
-
-  /**
-   * @protected
-   * @type {?string}
-   */
-  this.crossOrigin =
-      options.crossOrigin !== undefined ? options.crossOrigin : null;
-
-  /**
-   * @protected
-   * @type {function(new: ol.ImageTile, ol.TileCoord, ol.Tile.State, string,
-   *        ?string, ol.TileLoadFunctionType)}
-   */
-  this.tileClass = options.tileClass !== undefined ?
-      options.tileClass : ol.ImageTile;
-
-  /**
-   * @protected
-   * @type {Object.<string, ol.TileCache>}
-   */
-  this.tileCacheForProjection = {};
-
-  /**
-   * @protected
-   * @type {Object.<string, ol.tilegrid.TileGrid>}
-   */
-  this.tileGridForProjection = {};
-
-  /**
-   * @private
-   * @type {number|undefined}
-   */
-  this.reprojectionErrorThreshold_ = options.reprojectionErrorThreshold;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.renderReprojectionEdges_ = false;
-};
-ol.inherits(ol.source.TileImage, ol.source.UrlTile);
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileImage.prototype.canExpireCache = function() {
-  if (!ol.ENABLE_RASTER_REPROJECTION) {
-    return ol.source.UrlTile.prototype.canExpireCache.call(this);
-  }
-  if (this.tileCache.canExpireCache()) {
-    return true;
-  } else {
-    for (var key in this.tileCacheForProjection) {
-      if (this.tileCacheForProjection[key].canExpireCache()) {
-        return true;
-      }
-    }
-  }
-  return false;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileImage.prototype.expireCache = function(projection, usedTiles) {
-  if (!ol.ENABLE_RASTER_REPROJECTION) {
-    ol.source.UrlTile.prototype.expireCache.call(this, projection, usedTiles);
-    return;
-  }
-  var usedTileCache = this.getTileCacheForProjection(projection);
-
-  this.tileCache.expireCache(this.tileCache == usedTileCache ? usedTiles : {});
-  for (var id in this.tileCacheForProjection) {
-    var tileCache = this.tileCacheForProjection[id];
-    tileCache.expireCache(tileCache == usedTileCache ? usedTiles : {});
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileImage.prototype.getGutter = function(projection) {
-  if (ol.ENABLE_RASTER_REPROJECTION &&
-      this.getProjection() && projection &&
-      !ol.proj.equivalent(this.getProjection(), projection)) {
-    return 0;
-  } else {
-    return this.getGutterInternal();
-  }
-};
-
-
-/**
- * @protected
- * @return {number} Gutter.
- */
-ol.source.TileImage.prototype.getGutterInternal = function() {
-  return 0;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileImage.prototype.getOpaque = function(projection) {
-  if (ol.ENABLE_RASTER_REPROJECTION &&
-      this.getProjection() && projection &&
-      !ol.proj.equivalent(this.getProjection(), projection)) {
-    return false;
-  } else {
-    return ol.source.UrlTile.prototype.getOpaque.call(this, projection);
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileImage.prototype.getTileGridForProjection = function(projection) {
-  if (!ol.ENABLE_RASTER_REPROJECTION) {
-    return ol.source.UrlTile.prototype.getTileGridForProjection.call(this, projection);
-  }
-  var thisProj = this.getProjection();
-  if (this.tileGrid &&
-      (!thisProj || ol.proj.equivalent(thisProj, projection))) {
-    return this.tileGrid;
-  } else {
-    var projKey = ol.getUid(projection).toString();
-    if (!(projKey in this.tileGridForProjection)) {
-      this.tileGridForProjection[projKey] =
-          ol.tilegrid.getForProjection(projection);
-    }
-    return /** @type {!ol.tilegrid.TileGrid} */ (this.tileGridForProjection[projKey]);
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileImage.prototype.getTileCacheForProjection = function(projection) {
-  if (!ol.ENABLE_RASTER_REPROJECTION) {
-    return ol.source.UrlTile.prototype.getTileCacheForProjection.call(this, projection);
-  }
-  var thisProj = this.getProjection();
-  if (!thisProj || ol.proj.equivalent(thisProj, projection)) {
-    return this.tileCache;
-  } else {
-    var projKey = ol.getUid(projection).toString();
-    if (!(projKey in this.tileCacheForProjection)) {
-      this.tileCacheForProjection[projKey] = new ol.TileCache();
-    }
-    return this.tileCacheForProjection[projKey];
-  }
-};
-
-
-/**
- * @param {number} z Tile coordinate z.
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @param {string} key The key set on the tile.
- * @return {!ol.Tile} Tile.
- * @private
- */
-ol.source.TileImage.prototype.createTile_ = function(z, x, y, pixelRatio, projection, key) {
-  var tileCoord = [z, x, y];
-  var urlTileCoord = this.getTileCoordForTileUrlFunction(
-      tileCoord, projection);
-  var tileUrl = urlTileCoord ?
-      this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
-  var tile = new this.tileClass(
-      tileCoord,
-      tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY,
-      tileUrl !== undefined ? tileUrl : '',
-      this.crossOrigin,
-      this.tileLoadFunction);
-  tile.key = key;
-  ol.events.listen(tile, ol.events.EventType.CHANGE,
-      this.handleTileChange, this);
-  return tile;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection) {
-  if (!ol.ENABLE_RASTER_REPROJECTION ||
-      !this.getProjection() ||
-      !projection ||
-      ol.proj.equivalent(this.getProjection(), projection)) {
-    return this.getTileInternal(z, x, y, pixelRatio, /** @type {!ol.proj.Projection} */ (projection));
-  } else {
-    var cache = this.getTileCacheForProjection(projection);
-    var tileCoord = [z, x, y];
-    var tile;
-    var tileCoordKey = this.getKeyZXY.apply(this, tileCoord);
-    if (cache.containsKey(tileCoordKey)) {
-      tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey));
-    }
-    var key = this.getKey();
-    if (tile && tile.key == key) {
-      return tile;
-    } else {
-      var sourceProjection = /** @type {!ol.proj.Projection} */ (this.getProjection());
-      var sourceTileGrid = this.getTileGridForProjection(sourceProjection);
-      var targetTileGrid = this.getTileGridForProjection(projection);
-      var wrappedTileCoord =
-          this.getTileCoordForTileUrlFunction(tileCoord, projection);
-      var newTile = new ol.reproj.Tile(
-          sourceProjection, sourceTileGrid,
-          projection, targetTileGrid,
-          tileCoord, wrappedTileCoord, this.getTilePixelRatio(pixelRatio),
-          this.getGutterInternal(),
-          function(z, x, y, pixelRatio) {
-            return this.getTileInternal(z, x, y, pixelRatio, sourceProjection);
-          }.bind(this), this.reprojectionErrorThreshold_,
-          this.renderReprojectionEdges_);
-      newTile.key = key;
-
-      if (tile) {
-        newTile.interimTile = tile;
-        cache.replace(tileCoordKey, newTile);
-      } else {
-        cache.set(tileCoordKey, newTile);
-      }
-      return newTile;
-    }
-  }
-};
-
-
-/**
- * @param {number} z Tile coordinate z.
- * @param {number} x Tile coordinate x.
- * @param {number} y Tile coordinate y.
- * @param {number} pixelRatio Pixel ratio.
- * @param {!ol.proj.Projection} projection Projection.
- * @return {!ol.Tile} Tile.
- * @protected
- */
-ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) {
-  var tile = null;
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  var key = this.getKey();
-  if (!this.tileCache.containsKey(tileCoordKey)) {
-    tile = this.createTile_(z, x, y, pixelRatio, projection, key);
-    this.tileCache.set(tileCoordKey, tile);
-  } else {
-    tile = this.tileCache.get(tileCoordKey);
-    if (tile.key != key) {
-      // The source's params changed. If the tile has an interim tile and if we
-      // can use it then we use it. Otherwise we create a new tile.  In both
-      // cases we attempt to assign an interim tile to the new tile.
-      var interimTile = tile;
-      tile = this.createTile_(z, x, y, pixelRatio, projection, key);
-
-      //make the new tile the head of the list,
-      if (interimTile.getState() == ol.Tile.State.IDLE) {
-        //the old tile hasn't begun loading yet, and is now outdated, so we can simply discard it
-        tile.interimTile = interimTile.interimTile;
-      } else {
-        tile.interimTile = interimTile;
-      }
-      tile.refreshInterimChain();
-      this.tileCache.replace(tileCoordKey, tile);
-    }
-  }
-  return tile;
-};
-
-
-/**
- * Sets whether to render reprojection edges or not (usually for debugging).
- * @param {boolean} render Render the edges.
- * @api
- */
-ol.source.TileImage.prototype.setRenderReprojectionEdges = function(render) {
-  if (!ol.ENABLE_RASTER_REPROJECTION ||
-      this.renderReprojectionEdges_ == render) {
-    return;
-  }
-  this.renderReprojectionEdges_ = render;
-  for (var id in this.tileCacheForProjection) {
-    this.tileCacheForProjection[id].clear();
-  }
-  this.changed();
-};
-
-
-/**
- * Sets the tile grid to use when reprojecting the tiles to the given
- * projection instead of the default tile grid for the projection.
- *
- * This can be useful when the default tile grid cannot be created
- * (e.g. projection has no extent defined) or
- * for optimization reasons (custom tile size, resolutions, ...).
- *
- * @param {ol.ProjectionLike} projection Projection.
- * @param {ol.tilegrid.TileGrid} tilegrid Tile grid to use for the projection.
- * @api
- */
-ol.source.TileImage.prototype.setTileGridForProjection = function(projection, tilegrid) {
-  if (ol.ENABLE_RASTER_REPROJECTION) {
-    var proj = ol.proj.get(projection);
-    if (proj) {
-      var projKey = ol.getUid(proj).toString();
-      if (!(projKey in this.tileGridForProjection)) {
-        this.tileGridForProjection[projKey] = tilegrid;
-      }
-    }
-  }
-};
-
-
-/**
- * @param {ol.ImageTile} imageTile Image tile.
- * @param {string} src Source.
- */
-ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
-  imageTile.getImage().src = src;
-};
-
-goog.provide('ol.source.BingMaps');
-
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.extent');
-goog.require('ol.net');
-goog.require('ol.proj');
-goog.require('ol.source.State');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilecoord');
-goog.require('ol.tilegrid');
-
-
-/**
- * @classdesc
- * Layer source for Bing Maps tile data.
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.BingMapsOptions} options Bing Maps options.
- * @api stable
- */
-ol.source.BingMaps = function(options) {
-
-  ol.source.TileImage.call(this, {
-    cacheSize: options.cacheSize,
-    crossOrigin: 'anonymous',
-    opaque: true,
-    projection: ol.proj.get('EPSG:3857'),
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    state: ol.source.State.LOADING,
-    tileLoadFunction: options.tileLoadFunction,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.culture_ = options.culture !== undefined ? options.culture : 'en-us';
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.maxZoom_ = options.maxZoom !== undefined ? options.maxZoom : -1;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.apiKey_ = options.key;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.imagerySet_ = options.imagerySet;
-
-  var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
-      this.imagerySet_ +
-      '?uriScheme=https&include=ImageryProviders&key=' + this.apiKey_;
-
-  ol.net.jsonp(url, this.handleImageryMetadataResponse.bind(this), undefined,
-      'jsonp');
-
-};
-ol.inherits(ol.source.BingMaps, ol.source.TileImage);
-
-
-/**
- * The attribution containing a link to the Microsoft® Bing™ Maps Platform APIs’
- * Terms Of Use.
- * @const
- * @type {ol.Attribution}
- * @api
- */
-ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({
-  html: '<a class="ol-attribution-bing-tos" ' +
-      'href="http://www.microsoft.com/maps/product/terms.html">' +
-      'Terms of Use</a>'
-});
-
-
-/**
- * Get the api key used for this source.
- *
- * @return {string} The api key.
- * @api
- */
-ol.source.BingMaps.prototype.getApiKey = function() {
-  return this.apiKey_;
-};
-
-
-/**
- * Get the imagery set associated with this source.
- *
- * @return {string} The imagery set.
- * @api
- */
-ol.source.BingMaps.prototype.getImagerySet = function() {
-  return this.imagerySet_;
-};
-
-
-/**
- * @param {BingMapsImageryMetadataResponse} response Response.
- */
-ol.source.BingMaps.prototype.handleImageryMetadataResponse = function(response) {
-
-  if (response.statusCode != 200 ||
-      response.statusDescription != 'OK' ||
-      response.authenticationResultCode != 'ValidCredentials' ||
-      response.resourceSets.length != 1 ||
-      response.resourceSets[0].resources.length != 1) {
-    this.setState(ol.source.State.ERROR);
-    return;
-  }
-
-  var brandLogoUri = response.brandLogoUri;
-  if (brandLogoUri.indexOf('https') == -1) {
-    brandLogoUri = brandLogoUri.replace('http', 'https');
-  }
-  //var copyright = response.copyright;  // FIXME do we need to display this?
-  var resource = response.resourceSets[0].resources[0];
-  ol.DEBUG && console.assert(resource.imageWidth == resource.imageHeight,
-      'resource has imageWidth equal to imageHeight, i.e. is square');
-  var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_;
-
-  var sourceProjection = this.getProjection();
-  var extent = ol.tilegrid.extentFromProjection(sourceProjection);
-  var tileSize = resource.imageWidth == resource.imageHeight ?
-      resource.imageWidth : [resource.imageWidth, resource.imageHeight];
-  var tileGrid = ol.tilegrid.createXYZ({
-    extent: extent,
-    minZoom: resource.zoomMin,
-    maxZoom: maxZoom,
-    tileSize: tileSize
-  });
-  this.tileGrid = tileGrid;
-
-  var culture = this.culture_;
-  this.tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions(
-      resource.imageUrlSubdomains.map(function(subdomain) {
-        var quadKeyTileCoord = [0, 0, 0];
-        var imageUrl = resource.imageUrl
-            .replace('{subdomain}', subdomain)
-            .replace('{culture}', culture);
-        return (
-            /**
-             * @param {ol.TileCoord} tileCoord Tile coordinate.
-             * @param {number} pixelRatio Pixel ratio.
-             * @param {ol.proj.Projection} projection Projection.
-             * @return {string|undefined} Tile URL.
-             */
-            function(tileCoord, pixelRatio, projection) {
-              ol.DEBUG && console.assert(ol.proj.equivalent(
-                  projection, sourceProjection),
-                  'projections are equivalent');
-              if (!tileCoord) {
-                return undefined;
-              } else {
-                ol.tilecoord.createOrUpdate(tileCoord[0], tileCoord[1],
-                    -tileCoord[2] - 1, quadKeyTileCoord);
-                return imageUrl.replace('{quadkey}', ol.tilecoord.quadKey(
-                    quadKeyTileCoord));
-              }
-            });
-      }));
-
-  if (resource.imageryProviders) {
-    var transform = ol.proj.getTransformFromProjections(
-        ol.proj.get('EPSG:4326'), this.getProjection());
-
-    var attributions = resource.imageryProviders.map(function(imageryProvider) {
-      var html = imageryProvider.attribution;
-      /** @type {Object.<string, Array.<ol.TileRange>>} */
-      var tileRanges = {};
-      imageryProvider.coverageAreas.forEach(function(coverageArea) {
-        var minZ = coverageArea.zoomMin;
-        var maxZ = Math.min(coverageArea.zoomMax, maxZoom);
-        var bbox = coverageArea.bbox;
-        var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]];
-        var extent = ol.extent.applyTransform(epsg4326Extent, transform);
-        var tileRange, z, zKey;
-        for (z = minZ; z <= maxZ; ++z) {
-          zKey = z.toString();
-          tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
-          if (zKey in tileRanges) {
-            tileRanges[zKey].push(tileRange);
-          } else {
-            tileRanges[zKey] = [tileRange];
-          }
-        }
-      });
-      return new ol.Attribution({html: html, tileRanges: tileRanges});
-    });
-    attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION);
-    this.setAttributions(attributions);
-  }
-
-  this.setLogo(brandLogoUri);
-
-  this.setState(ol.source.State.READY);
-
-};
-
-goog.provide('ol.source.XYZ');
-
-goog.require('ol');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilegrid');
-
-
-/**
- * @classdesc
- * Layer source for tile data with URLs in a set XYZ format that are
- * defined in a URL template. By default, this follows the widely-used
- * Google grid where `x` 0 and `y` 0 are in the top left. Grids like
- * TMS where `x` 0 and `y` 0 are in the bottom left can be used by
- * using the `{-y}` placeholder in the URL template, so long as the
- * source does not have a custom tile grid. In this case,
- * {@link ol.source.TileImage} can be used with a `tileUrlFunction`
- * such as:
- *
- *  tileUrlFunction: function(coordinate) {
- *    return 'http://mapserver.com/' + coordinate[0] + '/' +
- *        coordinate[1] + '/' + coordinate[2] + '.png';
- *    }
- *
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.XYZOptions=} opt_options XYZ options.
- * @api stable
- */
-ol.source.XYZ = function(opt_options) {
-  var options = opt_options || {};
-  var projection = options.projection !== undefined ?
-      options.projection : 'EPSG:3857';
-
-  var tileGrid = options.tileGrid !== undefined ? options.tileGrid :
-      ol.tilegrid.createXYZ({
-        extent: ol.tilegrid.extentFromProjection(projection),
-        maxZoom: options.maxZoom,
-        minZoom: options.minZoom,
-        tileSize: options.tileSize
-      });
-
-  ol.source.TileImage.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    opaque: options.opaque,
-    projection: projection,
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileGrid: tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    tilePixelRatio: options.tilePixelRatio,
-    tileUrlFunction: options.tileUrlFunction,
-    url: options.url,
-    urls: options.urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-};
-ol.inherits(ol.source.XYZ, ol.source.TileImage);
-
-goog.provide('ol.source.CartoDB');
-
-goog.require('ol');
-goog.require('ol.obj');
-goog.require('ol.source.State');
-goog.require('ol.source.XYZ');
-
-
-/**
- * @classdesc
- * Layer source for the CartoDB tiles.
- *
- * @constructor
- * @extends {ol.source.XYZ}
- * @param {olx.source.CartoDBOptions} options CartoDB options.
- * @api
- */
-ol.source.CartoDB = function(options) {
-
-  /**
-   * @type {string}
-   * @private
-   */
-  this.account_ = options.account;
-
-  /**
-   * @type {string}
-   * @private
-   */
-  this.mapId_ = options.map || '';
-
-  /**
-   * @type {!Object}
-   * @private
-   */
-  this.config_ = options.config || {};
-
-  /**
-   * @type {!Object.<string, CartoDBLayerInfo>}
-   * @private
-   */
-  this.templateCache_ = {};
-
-  ol.source.XYZ.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    maxZoom: options.maxZoom !== undefined ? options.maxZoom : 18,
-    minZoom: options.minZoom,
-    projection: options.projection,
-    state: ol.source.State.LOADING,
-    wrapX: options.wrapX
-  });
-  this.initializeMap_();
-};
-ol.inherits(ol.source.CartoDB, ol.source.XYZ);
-
-
-/**
- * Returns the current config.
- * @return {!Object} The current configuration.
- * @api
- */
-ol.source.CartoDB.prototype.getConfig = function() {
-  return this.config_;
-};
-
-
-/**
- * Updates the carto db config.
- * @param {Object} config a key-value lookup. Values will replace current values
- *     in the config.
- * @api
- */
-ol.source.CartoDB.prototype.updateConfig = function(config) {
-  ol.obj.assign(this.config_, config);
-  this.initializeMap_();
-};
-
-
-/**
- * Sets the CartoDB config
- * @param {Object} config In the case of anonymous maps, a CartoDB configuration
- *     object.
- * If using named maps, a key-value lookup with the template parameters.
- * @api
- */
-ol.source.CartoDB.prototype.setConfig = function(config) {
-  this.config_ = config || {};
-  this.initializeMap_();
-};
-
-
-/**
- * Issue a request to initialize the CartoDB map.
- * @private
- */
-ol.source.CartoDB.prototype.initializeMap_ = function() {
-  var paramHash = JSON.stringify(this.config_);
-  if (this.templateCache_[paramHash]) {
-    this.applyTemplate_(this.templateCache_[paramHash]);
-    return;
-  }
-  var mapUrl = 'https://' + this.account_ + '.cartodb.com/api/v1/map';
-
-  if (this.mapId_) {
-    mapUrl += '/named/' + this.mapId_;
-  }
-
-  var client = new XMLHttpRequest();
-  client.addEventListener('load', this.handleInitResponse_.bind(this, paramHash));
-  client.addEventListener('error', this.handleInitError_.bind(this));
-  client.open('POST', mapUrl);
-  client.setRequestHeader('Content-type', 'application/json');
-  client.send(JSON.stringify(this.config_));
-};
-
-
-/**
- * Handle map initialization response.
- * @param {string} paramHash a hash representing the parameter set that was used
- *     for the request
- * @param {Event} event Event.
- * @private
- */
-ol.source.CartoDB.prototype.handleInitResponse_ = function(paramHash, event) {
-  var client = /** @type {XMLHttpRequest} */ (event.target);
-  // status will be 0 for file:// urls
-  if (!client.status || client.status >= 200 && client.status < 300) {
-    var response;
-    try {
-      response = /** @type {CartoDBLayerInfo} */(JSON.parse(client.responseText));
-    } catch (err) {
-      this.setState(ol.source.State.ERROR);
-      return;
-    }
-    this.applyTemplate_(response);
-    this.templateCache_[paramHash] = response;
-    this.setState(ol.source.State.READY);
-  } else {
-    this.setState(ol.source.State.ERROR);
-  }
-};
-
-
-/**
- * @private
- * @param {Event} event Event.
- */
-ol.source.CartoDB.prototype.handleInitError_ = function(event) {
-  this.setState(ol.source.State.ERROR);
-};
-
-
-/**
- * Apply the new tile urls returned by carto db
- * @param {CartoDBLayerInfo} data Result of carto db call.
- * @private
- */
-ol.source.CartoDB.prototype.applyTemplate_ = function(data) {
-  var tilesUrl = 'https://' + data.cdn_url.https + '/' + this.account_ +
-      '/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png';
-  this.setUrl(tilesUrl);
-};
-
-// FIXME keep cluster cache by resolution ?
-// FIXME distance not respected because of the centroid
-
-goog.provide('ol.source.Cluster');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.Feature');
-goog.require('ol.coordinate');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.geom.Point');
-goog.require('ol.source.Vector');
-
-
-/**
- * @classdesc
- * Layer source to cluster vector data. Works out of the box with point
- * geometries. For other geometry types, or if not all geometries should be
- * considered for clustering, a custom `geometryFunction` can be defined.
- *
- * @constructor
- * @param {olx.source.ClusterOptions} options Constructor options.
- * @extends {ol.source.Vector}
- * @api
- */
-ol.source.Cluster = function(options) {
-  ol.source.Vector.call(this, {
-    attributions: options.attributions,
-    extent: options.extent,
-    logo: options.logo,
-    projection: options.projection,
-    wrapX: options.wrapX
-  });
-
-  /**
-   * @type {number|undefined}
-   * @private
-   */
-  this.resolution_ = undefined;
-
-  /**
-   * @type {number}
-   * @private
-   */
-  this.distance_ = options.distance !== undefined ? options.distance : 20;
-
-  /**
-   * @type {Array.<ol.Feature>}
-   * @private
-   */
-  this.features_ = [];
-
-  /**
-   * @param {ol.Feature} feature Feature.
-   * @return {ol.geom.Point} Cluster calculation point.
-   */
-  this.geometryFunction_ = options.geometryFunction || function(feature) {
-    var geometry = /** @type {ol.geom.Point} */ (feature.getGeometry());
-    ol.asserts.assert(geometry instanceof ol.geom.Point,
-        10); // The default `geometryFunction` can only handle `ol.geom.Point` geometries
-    return geometry;
-  };
-
-  /**
-   * @type {ol.source.Vector}
-   * @private
-   */
-  this.source_ = options.source;
-
-  this.source_.on(ol.events.EventType.CHANGE,
-      ol.source.Cluster.prototype.refresh_, this);
-};
-ol.inherits(ol.source.Cluster, ol.source.Vector);
-
-
-/**
- * Get a reference to the wrapped source.
- * @return {ol.source.Vector} Source.
- * @api
- */
-ol.source.Cluster.prototype.getSource = function() {
-  return this.source_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.Cluster.prototype.loadFeatures = function(extent, resolution,
-    projection) {
-  this.source_.loadFeatures(extent, resolution, projection);
-  if (resolution !== this.resolution_) {
-    this.clear();
-    this.resolution_ = resolution;
-    this.cluster_();
-    this.addFeatures(this.features_);
-  }
-};
-
-
-/**
- * Set the distance in pixels between clusters.
- * @param {number} distance The distance in pixels.
- * @api
- */
-ol.source.Cluster.prototype.setDistance = function(distance) {
-  this.distance_ = distance;
-  this.refresh_();
-};
-
-
-/**
- * handle the source changing
- * @private
- */
-ol.source.Cluster.prototype.refresh_ = function() {
-  this.clear();
-  this.cluster_();
-  this.addFeatures(this.features_);
-  this.changed();
-};
-
-
-/**
- * @private
- */
-ol.source.Cluster.prototype.cluster_ = function() {
-  if (this.resolution_ === undefined) {
-    return;
-  }
-  this.features_.length = 0;
-  var extent = ol.extent.createEmpty();
-  var mapDistance = this.distance_ * this.resolution_;
-  var features = this.source_.getFeatures();
-
-  /**
-   * @type {!Object.<string, boolean>}
-   */
-  var clustered = {};
-
-  for (var i = 0, ii = features.length; i < ii; i++) {
-    var feature = features[i];
-    if (!(ol.getUid(feature).toString() in clustered)) {
-      var geometry = this.geometryFunction_(feature);
-      if (geometry) {
-        var coordinates = geometry.getCoordinates();
-        ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
-        ol.extent.buffer(extent, mapDistance, extent);
-
-        var neighbors = this.source_.getFeaturesInExtent(extent);
-        ol.DEBUG && console.assert(neighbors.length >= 1, 'at least one neighbor found');
-        neighbors = neighbors.filter(function(neighbor) {
-          var uid = ol.getUid(neighbor).toString();
-          if (!(uid in clustered)) {
-            clustered[uid] = true;
-            return true;
-          } else {
-            return false;
-          }
-        });
-        this.features_.push(this.createCluster_(neighbors));
-      }
-    }
-  }
-  ol.DEBUG && console.assert(
-      Object.keys(clustered).length == this.source_.getFeatures().length,
-      'number of clustered equals number of features in the source');
-};
-
-
-/**
- * @param {Array.<ol.Feature>} features Features
- * @return {ol.Feature} The cluster feature.
- * @private
- */
-ol.source.Cluster.prototype.createCluster_ = function(features) {
-  var centroid = [0, 0];
-  for (var i = features.length - 1; i >= 0; --i) {
-    var geometry = this.geometryFunction_(features[i]);
-    if (geometry) {
-      ol.coordinate.add(centroid, geometry.getCoordinates());
-    } else {
-      features.splice(i, 1);
-    }
-  }
-  ol.coordinate.scale(centroid, 1 / features.length);
-
-  var cluster = new ol.Feature(new ol.geom.Point(centroid));
-  cluster.set('features', features);
-  return cluster;
-};
-
-goog.provide('ol.uri');
-
-
-/**
- * Appends query parameters to a URI.
- *
- * @param {string} uri The original URI, which may already have query data.
- * @param {!Object} params An object where keys are URI-encoded parameter keys,
- *     and the values are arbitrary types or arrays.
- * @return {string} The new URI.
- */
-ol.uri.appendParams = function(uri, params) {
-  var keyParams = [];
-  // Skip any null or undefined parameter values
-  Object.keys(params).forEach(function(k) {
-    if (params[k] !== null && params[k] !== undefined) {
-      keyParams.push(k + '=' + encodeURIComponent(params[k]));
-    }
-  });
-  var qs = keyParams.join('&');
-  // remove any trailing ? or &
-  uri = uri.replace(/[?&]$/, '');
-  // append ? or & depending on whether uri has existing parameters
-  uri = uri.indexOf('?') === -1 ? uri + '?' : uri + '&';
-  return uri + qs;
-};
-
-goog.provide('ol.source.ImageArcGISRest');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.asserts');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.source.Image');
-goog.require('ol.uri');
-
-
-/**
- * @classdesc
- * Source for data from ArcGIS Rest services providing single, untiled images.
- * Useful when underlying map service has labels.
- *
- * If underlying map service is not using labels,
- * take advantage of ol image caching and use
- * {@link ol.source.TileArcGISRest} data source.
- *
- * @constructor
- * @fires ol.source.Image.Event
- * @extends {ol.source.Image}
- * @param {olx.source.ImageArcGISRestOptions=} opt_options Image ArcGIS Rest Options.
- * @api
- */
-ol.source.ImageArcGISRest = function(opt_options) {
-
-  var options = opt_options || {};
-
-  ol.source.Image.call(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection,
-    resolutions: options.resolutions
-  });
-
-  /**
-   * @private
-   * @type {?string}
-   */
-  this.crossOrigin_ =
-      options.crossOrigin !== undefined ? options.crossOrigin : null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.url_ = options.url;
-
-  /**
-   * @private
-   * @type {ol.ImageLoadFunctionType}
-   */
-  this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
-
-
-  /**
-   * @private
-   * @type {!Object}
-   */
-  this.params_ = options.params || {};
-
-  /**
-   * @private
-   * @type {ol.Image}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.imageSize_ = [0, 0];
-
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5;
-
-};
-ol.inherits(ol.source.ImageArcGISRest, ol.source.Image);
-
-
-/**
- * Get the user-provided params, i.e. those passed to the constructor through
- * the "params" option, and possibly updated using the updateParams method.
- * @return {Object} Params.
- * @api stable
- */
-ol.source.ImageArcGISRest.prototype.getParams = function() {
-  return this.params_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageArcGISRest.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
-
-  if (this.url_ === undefined) {
-    return null;
-  }
-
-  resolution = this.findNearestResolution(resolution);
-
-  var image = this.image_;
-  if (image &&
-      this.renderedRevision_ == this.getRevision() &&
-      image.getResolution() == resolution &&
-      image.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(image.getExtent(), extent)) {
-    return image;
-  }
-
-  var params = {
-    'F': 'image',
-    'FORMAT': 'PNG32',
-    'TRANSPARENT': true
-  };
-  ol.obj.assign(params, this.params_);
-
-  extent = extent.slice();
-  var centerX = (extent[0] + extent[2]) / 2;
-  var centerY = (extent[1] + extent[3]) / 2;
-  if (this.ratio_ != 1) {
-    var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2;
-    var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2;
-    extent[0] = centerX - halfWidth;
-    extent[1] = centerY - halfHeight;
-    extent[2] = centerX + halfWidth;
-    extent[3] = centerY + halfHeight;
-  }
-
-  var imageResolution = resolution / pixelRatio;
-
-  // Compute an integer width and height.
-  var width = Math.ceil(ol.extent.getWidth(extent) / imageResolution);
-  var height = Math.ceil(ol.extent.getHeight(extent) / imageResolution);
-
-  // Modify the extent to match the integer width and height.
-  extent[0] = centerX - imageResolution * width / 2;
-  extent[2] = centerX + imageResolution * width / 2;
-  extent[1] = centerY - imageResolution * height / 2;
-  extent[3] = centerY + imageResolution * height / 2;
-
-  this.imageSize_[0] = width;
-  this.imageSize_[1] = height;
-
-  var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
-      projection, params);
-
-  this.image_ = new ol.Image(extent, resolution, pixelRatio,
-      this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);
-
-  this.renderedRevision_ = this.getRevision();
-
-  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
-      this.handleImageChange, this);
-
-  return this.image_;
-
-};
-
-
-/**
- * Return the image load function of the source.
- * @return {ol.ImageLoadFunctionType} The image load function.
- * @api
- */
-ol.source.ImageArcGISRest.prototype.getImageLoadFunction = function() {
-  return this.imageLoadFunction_;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Size} size Size.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @param {Object} params Params.
- * @return {string} Request URL.
- * @private
- */
-ol.source.ImageArcGISRest.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) {
-
-  ol.DEBUG && console.assert(this.url_ !== undefined, 'url is defined');
-
-  // ArcGIS Server only wants the numeric portion of the projection ID.
-  var srid = projection.getCode().split(':').pop();
-
-  params['SIZE'] = size[0] + ',' + size[1];
-  params['BBOX'] = extent.join(',');
-  params['BBOXSR'] = srid;
-  params['IMAGESR'] = srid;
-  params['DPI'] = 90 * pixelRatio;
-
-  var url = this.url_;
-
-  var modifiedUrl = url
-    .replace(/MapServer\/?$/, 'MapServer/export')
-    .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
-  if (modifiedUrl == url) {
-    ol.asserts.assert(false, 50); // `options.featureTypes` should be an Array
-  }
-  return ol.uri.appendParams(modifiedUrl, params);
-};
-
-
-/**
- * Return the URL used for this ArcGIS source.
- * @return {string|undefined} URL.
- * @api stable
- */
-ol.source.ImageArcGISRest.prototype.getUrl = function() {
-  return this.url_;
-};
-
-
-/**
- * Set the image load function of the source.
- * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
- * @api
- */
-ol.source.ImageArcGISRest.prototype.setImageLoadFunction = function(imageLoadFunction) {
-  this.image_ = null;
-  this.imageLoadFunction_ = imageLoadFunction;
-  this.changed();
-};
-
-
-/**
- * Set the URL to use for requests.
- * @param {string|undefined} url URL.
- * @api stable
- */
-ol.source.ImageArcGISRest.prototype.setUrl = function(url) {
-  if (url != this.url_) {
-    this.url_ = url;
-    this.image_ = null;
-    this.changed();
-  }
-};
-
-
-/**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
- */
-ol.source.ImageArcGISRest.prototype.updateParams = function(params) {
-  ol.obj.assign(this.params_, params);
-  this.image_ = null;
-  this.changed();
-};
-
-goog.provide('ol.source.ImageMapGuide');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.source.Image');
-goog.require('ol.uri');
-
-
-/**
- * @classdesc
- * Source for images from Mapguide servers
- *
- * @constructor
- * @fires ol.source.Image.Event
- * @extends {ol.source.Image}
- * @param {olx.source.ImageMapGuideOptions} options Options.
- * @api stable
- */
-ol.source.ImageMapGuide = function(options) {
-
-  ol.source.Image.call(this, {
-    projection: options.projection,
-    resolutions: options.resolutions
-  });
-
-  /**
-   * @private
-   * @type {?string}
-   */
-  this.crossOrigin_ =
-      options.crossOrigin !== undefined ? options.crossOrigin : null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.displayDpi_ = options.displayDpi !== undefined ?
-      options.displayDpi : 96;
-
-  /**
-   * @private
-   * @type {!Object}
-   */
-  this.params_ = options.params || {};
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.url_ = options.url;
-
-  /**
-   * @private
-   * @type {ol.ImageLoadFunctionType}
-   */
-  this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.metersPerUnit_ = options.metersPerUnit !== undefined ?
-      options.metersPerUnit : 1;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.ratio_ = options.ratio !== undefined ? options.ratio : 1;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.useOverlay_ = options.useOverlay !== undefined ?
-      options.useOverlay : false;
-
-  /**
-   * @private
-   * @type {ol.Image}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = 0;
-
-};
-ol.inherits(ol.source.ImageMapGuide, ol.source.Image);
-
-
-/**
- * Get the user-provided params, i.e. those passed to the constructor through
- * the "params" option, and possibly updated using the updateParams method.
- * @return {Object} Params.
- * @api stable
- */
-ol.source.ImageMapGuide.prototype.getParams = function() {
-  return this.params_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageMapGuide.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
-  resolution = this.findNearestResolution(resolution);
-  pixelRatio = this.hidpi_ ? pixelRatio : 1;
-
-  var image = this.image_;
-  if (image &&
-      this.renderedRevision_ == this.getRevision() &&
-      image.getResolution() == resolution &&
-      image.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(image.getExtent(), extent)) {
-    return image;
-  }
-
-  if (this.ratio_ != 1) {
-    extent = extent.slice();
-    ol.extent.scaleFromCenter(extent, this.ratio_);
-  }
-  var width = ol.extent.getWidth(extent) / resolution;
-  var height = ol.extent.getHeight(extent) / resolution;
-  var size = [width * pixelRatio, height * pixelRatio];
-
-  if (this.url_ !== undefined) {
-    var imageUrl = this.getUrl(this.url_, this.params_, extent, size,
-        projection);
-    image = new ol.Image(extent, resolution, pixelRatio,
-        this.getAttributions(), imageUrl, this.crossOrigin_,
-        this.imageLoadFunction_);
-    ol.events.listen(image, ol.events.EventType.CHANGE,
-        this.handleImageChange, this);
-  } else {
-    image = null;
-  }
-  this.image_ = image;
-  this.renderedRevision_ = this.getRevision();
-
-  return image;
-};
-
-
-/**
- * Return the image load function of the source.
- * @return {ol.ImageLoadFunctionType} The image load function.
- * @api
- */
-ol.source.ImageMapGuide.prototype.getImageLoadFunction = function() {
-  return this.imageLoadFunction_;
-};
-
-
-/**
- * @param {ol.Extent} extent The map extents.
- * @param {ol.Size} size The viewport size.
- * @param {number} metersPerUnit The meters-per-unit value.
- * @param {number} dpi The display resolution.
- * @return {number} The computed map scale.
- */
-ol.source.ImageMapGuide.getScale = function(extent, size, metersPerUnit, dpi) {
-  var mcsW = ol.extent.getWidth(extent);
-  var mcsH = ol.extent.getHeight(extent);
-  var devW = size[0];
-  var devH = size[1];
-  var mpp = 0.0254 / dpi;
-  if (devH * mcsW > devW * mcsH) {
-    return mcsW * metersPerUnit / (devW * mpp); // width limited
-  } else {
-    return mcsH * metersPerUnit / (devH * mpp); // height limited
-  }
-};
-
-
-/**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
- */
-ol.source.ImageMapGuide.prototype.updateParams = function(params) {
-  ol.obj.assign(this.params_, params);
-  this.changed();
-};
-
-
-/**
- * @param {string} baseUrl The mapagent url.
- * @param {Object.<string, string|number>} params Request parameters.
- * @param {ol.Extent} extent Extent.
- * @param {ol.Size} size Size.
- * @param {ol.proj.Projection} projection Projection.
- * @return {string} The mapagent map image request URL.
- */
-ol.source.ImageMapGuide.prototype.getUrl = function(baseUrl, params, extent, size, projection) {
-  var scale = ol.source.ImageMapGuide.getScale(extent, size,
-      this.metersPerUnit_, this.displayDpi_);
-  var center = ol.extent.getCenter(extent);
-  var baseParams = {
-    'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE',
-    'VERSION': '2.0.0',
-    'LOCALE': 'en',
-    'CLIENTAGENT': 'ol.source.ImageMapGuide source',
-    'CLIP': '1',
-    'SETDISPLAYDPI': this.displayDpi_,
-    'SETDISPLAYWIDTH': Math.round(size[0]),
-    'SETDISPLAYHEIGHT': Math.round(size[1]),
-    'SETVIEWSCALE': scale,
-    'SETVIEWCENTERX': center[0],
-    'SETVIEWCENTERY': center[1]
-  };
-  ol.obj.assign(baseParams, params);
-  return ol.uri.appendParams(baseUrl, baseParams);
-};
-
-
-/**
- * Set the image load function of the MapGuide source.
- * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
- * @api
- */
-ol.source.ImageMapGuide.prototype.setImageLoadFunction = function(
-    imageLoadFunction) {
-  this.image_ = null;
-  this.imageLoadFunction_ = imageLoadFunction;
-  this.changed();
-};
-
-goog.provide('ol.source.ImageStatic');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.proj');
-goog.require('ol.source.Image');
-
-
-/**
- * @classdesc
- * A layer source for displaying a single, static image.
- *
- * @constructor
- * @extends {ol.source.Image}
- * @param {olx.source.ImageStaticOptions} options Options.
- * @api stable
- */
-ol.source.ImageStatic = function(options) {
-  var imageExtent = options.imageExtent;
-
-  var crossOrigin = options.crossOrigin !== undefined ?
-      options.crossOrigin : null;
-
-  var /** @type {ol.ImageLoadFunctionType} */ imageLoadFunction =
-      options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
-
-  ol.source.Image.call(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: ol.proj.get(options.projection)
-  });
-
-  /**
-   * @private
-   * @type {ol.Image}
-   */
-  this.image_ = new ol.Image(imageExtent, undefined, 1, this.getAttributions(),
-      options.url, crossOrigin, imageLoadFunction);
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.imageSize_ = options.imageSize ? options.imageSize : null;
-
-  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
-      this.handleImageChange, this);
-
-};
-ol.inherits(ol.source.ImageStatic, ol.source.Image);
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageStatic.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
-  if (ol.extent.intersects(extent, this.image_.getExtent())) {
-    return this.image_;
-  }
-  return null;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageStatic.prototype.handleImageChange = function(evt) {
-  if (this.image_.getState() == ol.Image.State.LOADED) {
-    var imageExtent = this.image_.getExtent();
-    var image = this.image_.getImage();
-    var imageWidth, imageHeight;
-    if (this.imageSize_) {
-      imageWidth = this.imageSize_[0];
-      imageHeight = this.imageSize_[1];
-    } else {
-      // TODO: remove the type cast when a closure-compiler > 20160315 is used.
-      // see: https://github.com/google/closure-compiler/pull/1664
-      imageWidth = /** @type {number} */ (image.width);
-      imageHeight = /** @type {number} */ (image.height);
-    }
-    var resolution = ol.extent.getHeight(imageExtent) / imageHeight;
-    var targetWidth = Math.ceil(ol.extent.getWidth(imageExtent) / resolution);
-    if (targetWidth != imageWidth) {
-      var context = ol.dom.createCanvasContext2D(targetWidth, imageHeight);
-      var canvas = context.canvas;
-      context.drawImage(image, 0, 0, imageWidth, imageHeight,
-          0, 0, canvas.width, canvas.height);
-      this.image_.setImage(canvas);
-    }
-  }
-  ol.source.Image.prototype.handleImageChange.call(this, evt);
-};
-
-goog.provide('ol.source.WMSServerType');
-
-
-/**
- * Available server types: `'carmentaserver'`, `'geoserver'`, `'mapserver'`,
- *     `'qgis'`. These are servers that have vendor parameters beyond the WMS
- *     specification that OpenLayers can make use of.
- * @enum {string}
- */
-ol.source.WMSServerType = {
-  CARMENTA_SERVER: 'carmentaserver',
-  GEOSERVER: 'geoserver',
-  MAPSERVER: 'mapserver',
-  QGIS: 'qgis'
-};
-
-// FIXME cannot be shared between maps with different projections
-
-goog.provide('ol.source.ImageWMS');
-
-goog.require('ol');
-goog.require('ol.Image');
-goog.require('ol.asserts');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.proj');
-goog.require('ol.source.Image');
-goog.require('ol.source.WMSServerType');
-goog.require('ol.string');
-goog.require('ol.uri');
-
-
-/**
- * @classdesc
- * Source for WMS servers providing single, untiled images.
- *
- * @constructor
- * @fires ol.source.Image.Event
- * @extends {ol.source.Image}
- * @param {olx.source.ImageWMSOptions=} opt_options Options.
- * @api stable
- */
-ol.source.ImageWMS = function(opt_options) {
-
-  var options = opt_options || {};
-
-  ol.source.Image.call(this, {
-    attributions: options.attributions,
-    logo: options.logo,
-    projection: options.projection,
-    resolutions: options.resolutions
-  });
-
-  /**
-   * @private
-   * @type {?string}
-   */
-  this.crossOrigin_ =
-      options.crossOrigin !== undefined ? options.crossOrigin : null;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.url_ = options.url;
-
-  /**
-   * @private
-   * @type {ol.ImageLoadFunctionType}
-   */
-  this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
-      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
-
-  /**
-   * @private
-   * @type {!Object}
-   */
-  this.params_ = options.params || {};
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.v13_ = true;
-  this.updateV13_();
-
-  /**
-   * @private
-   * @type {ol.source.WMSServerType|undefined}
-   */
-  this.serverType_ =
-      /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
-
-  /**
-   * @private
-   * @type {ol.Image}
-   */
-  this.image_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.imageSize_ = [0, 0];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.renderedRevision_ = 0;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5;
-
-};
-ol.inherits(ol.source.ImageWMS, ol.source.Image);
-
-
-/**
- * @const
- * @type {ol.Size}
- * @private
- */
-ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101];
-
-
-/**
- * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
- * projection. Return `undefined` if the GetFeatureInfo URL cannot be
- * constructed.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {ol.ProjectionLike} projection Projection.
- * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
- *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
- *     in the `LAYERS` parameter will be used. `VERSION` should not be
- *     specified here.
- * @return {string|undefined} GetFeatureInfo URL.
- * @api stable
- */
-ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) {
-
-  ol.DEBUG && console.assert(!('VERSION' in params),
-      'key VERSION is not allowed in params');
-
-  if (this.url_ === undefined) {
-    return undefined;
-  }
-
-  var extent = ol.extent.getForViewAndSize(
-      coordinate, resolution, 0,
-      ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_);
-
-  var baseParams = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetFeatureInfo',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true,
-    'QUERY_LAYERS': this.params_['LAYERS']
-  };
-  ol.obj.assign(baseParams, this.params_, params);
-
-  var x = Math.floor((coordinate[0] - extent[0]) / resolution);
-  var y = Math.floor((extent[3] - coordinate[1]) / resolution);
-  baseParams[this.v13_ ? 'I' : 'X'] = x;
-  baseParams[this.v13_ ? 'J' : 'Y'] = y;
-
-  return this.getRequestUrl_(
-      extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_,
-      1, ol.proj.get(projection), baseParams);
-};
-
-
-/**
- * Get the user-provided params, i.e. those passed to the constructor through
- * the "params" option, and possibly updated using the updateParams method.
- * @return {Object} Params.
- * @api stable
- */
-ol.source.ImageWMS.prototype.getParams = function() {
-  return this.params_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.ImageWMS.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
-
-  if (this.url_ === undefined) {
-    return null;
-  }
-
-  resolution = this.findNearestResolution(resolution);
-
-  if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) {
-    pixelRatio = 1;
-  }
-
-  extent = extent.slice();
-  var centerX = (extent[0] + extent[2]) / 2;
-  var centerY = (extent[1] + extent[3]) / 2;
-
-  var imageResolution = resolution / pixelRatio;
-  var imageWidth = ol.extent.getWidth(extent) / imageResolution;
-  var imageHeight = ol.extent.getHeight(extent) / imageResolution;
-
-  var image = this.image_;
-  if (image &&
-      this.renderedRevision_ == this.getRevision() &&
-      image.getResolution() == resolution &&
-      image.getPixelRatio() == pixelRatio &&
-      ol.extent.containsExtent(image.getExtent(), extent)) {
-    return image;
-  }
-
-  if (this.ratio_ != 1) {
-    var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2;
-    var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2;
-    extent[0] = centerX - halfWidth;
-    extent[1] = centerY - halfHeight;
-    extent[2] = centerX + halfWidth;
-    extent[3] = centerY + halfHeight;
-  }
-
-  var params = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetMap',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true
-  };
-  ol.obj.assign(params, this.params_);
-
-  this.imageSize_[0] = Math.ceil(imageWidth * this.ratio_);
-  this.imageSize_[1] = Math.ceil(imageHeight * this.ratio_);
-
-  var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
-      projection, params);
-
-  this.image_ = new ol.Image(extent, resolution, pixelRatio,
-      this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);
-
-  this.renderedRevision_ = this.getRevision();
-
-  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
-      this.handleImageChange, this);
-
-  return this.image_;
-
-};
-
-
-/**
- * Return the image load function of the source.
- * @return {ol.ImageLoadFunctionType} The image load function.
- * @api
- */
-ol.source.ImageWMS.prototype.getImageLoadFunction = function() {
-  return this.imageLoadFunction_;
-};
-
-
-/**
- * @param {ol.Extent} extent Extent.
- * @param {ol.Size} size Size.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @param {Object} params Params.
- * @return {string} Request URL.
- * @private
- */
-ol.source.ImageWMS.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) {
-
-  ol.asserts.assert(this.url_ !== undefined, 9); // `url` must be configured or set using `#setUrl()`
-
-  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
-
-  if (!('STYLES' in this.params_)) {
-    params['STYLES'] = '';
-  }
-
-  if (pixelRatio != 1) {
-    switch (this.serverType_) {
-      case ol.source.WMSServerType.GEOSERVER:
-        var dpi = (90 * pixelRatio + 0.5) | 0;
-        if ('FORMAT_OPTIONS' in params) {
-          params['FORMAT_OPTIONS'] += ';dpi:' + dpi;
-        } else {
-          params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
-        }
-        break;
-      case ol.source.WMSServerType.MAPSERVER:
-        params['MAP_RESOLUTION'] = 90 * pixelRatio;
-        break;
-      case ol.source.WMSServerType.CARMENTA_SERVER:
-      case ol.source.WMSServerType.QGIS:
-        params['DPI'] = 90 * pixelRatio;
-        break;
-      default:
-        ol.asserts.assert(false, 8); // Unknown `serverType` configured
-        break;
-    }
-  }
-
-  params['WIDTH'] = size[0];
-  params['HEIGHT'] = size[1];
-
-  var axisOrientation = projection.getAxisOrientation();
-  var bbox;
-  if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') {
-    bbox = [extent[1], extent[0], extent[3], extent[2]];
-  } else {
-    bbox = extent;
-  }
-  params['BBOX'] = bbox.join(',');
-
-  return ol.uri.appendParams(/** @type {string} */ (this.url_), params);
-};
-
-
-/**
- * Return the URL used for this WMS source.
- * @return {string|undefined} URL.
- * @api stable
- */
-ol.source.ImageWMS.prototype.getUrl = function() {
-  return this.url_;
-};
-
-
-/**
- * Set the image load function of the source.
- * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
- * @api
- */
-ol.source.ImageWMS.prototype.setImageLoadFunction = function(
-    imageLoadFunction) {
-  this.image_ = null;
-  this.imageLoadFunction_ = imageLoadFunction;
-  this.changed();
-};
-
-
-/**
- * Set the URL to use for requests.
- * @param {string|undefined} url URL.
- * @api stable
- */
-ol.source.ImageWMS.prototype.setUrl = function(url) {
-  if (url != this.url_) {
-    this.url_ = url;
-    this.image_ = null;
-    this.changed();
-  }
-};
-
-
-/**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
- */
-ol.source.ImageWMS.prototype.updateParams = function(params) {
-  ol.obj.assign(this.params_, params);
-  this.updateV13_();
-  this.image_ = null;
-  this.changed();
-};
-
-
-/**
- * @private
- */
-ol.source.ImageWMS.prototype.updateV13_ = function() {
-  var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION;
-  this.v13_ = ol.string.compareVersions(version, '1.3') >= 0;
-};
-
-goog.provide('ol.source.OSM');
-
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.source.XYZ');
-
-
-/**
- * @classdesc
- * Layer source for the OpenStreetMap tile server.
- *
- * @constructor
- * @extends {ol.source.XYZ}
- * @param {olx.source.OSMOptions=} opt_options Open Street Map options.
- * @api stable
- */
-ol.source.OSM = function(opt_options) {
-
-  var options = opt_options || {};
-
-  var attributions;
-  if (options.attributions !== undefined) {
-    attributions = options.attributions;
-  } else {
-    attributions = [ol.source.OSM.ATTRIBUTION];
-  }
-
-  var crossOrigin = options.crossOrigin !== undefined ?
-      options.crossOrigin : 'anonymous';
-
-  var url = options.url !== undefined ?
-      options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';
-
-  ol.source.XYZ.call(this, {
-    attributions: attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: crossOrigin,
-    opaque: options.opaque !== undefined ? options.opaque : true,
-    maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19,
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileLoadFunction: options.tileLoadFunction,
-    url: url,
-    wrapX: options.wrapX
-  });
-
-};
-ol.inherits(ol.source.OSM, ol.source.XYZ);
-
-
-/**
- * The attribution containing a link to the OpenStreetMap Copyright and License
- * page.
- * @const
- * @type {ol.Attribution}
- * @api
- */
-ol.source.OSM.ATTRIBUTION = new ol.Attribution({
-  html: '© ' +
-      '<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> ' +
-      'contributors.'
-});
-
-goog.provide('ol.ext.pixelworks');
-/** @typedef {function(*)} */
-ol.ext.pixelworks;
-(function() {
-var exports = {};
-var module = {exports: exports};
-var define;
-/**
- * @fileoverview
- * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
- */
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pixelworks = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);i [...]
-var Processor = _dereq_('./processor');
-
-exports.Processor = Processor;
-
-},{"./processor":2}],2:[function(_dereq_,module,exports){
-var newImageData = _dereq_('./util').newImageData;
-
-/**
- * Create a function for running operations.  This function is serialized for
- * use in a worker.
- * @param {function(Array, Object):*} operation The operation.
- * @return {function(Object):ArrayBuffer} A function that takes an object with
- * buffers, meta, imageOps, width, and height properties and returns an array
- * buffer.
- */
-function createMinion(operation) {
-  var workerHasImageData = true;
-  try {
-    new ImageData(10, 10);
-  } catch (_) {
-    workerHasImageData = false;
-  }
-
-  function newWorkerImageData(data, width, height) {
-    if (workerHasImageData) {
-      return new ImageData(data, width, height);
-    } else {
-      return {data: data, width: width, height: height};
-    }
-  }
-
-  return function(data) {
-    // bracket notation for minification support
-    var buffers = data['buffers'];
-    var meta = data['meta'];
-    var imageOps = data['imageOps'];
-    var width = data['width'];
-    var height = data['height'];
-
-    var numBuffers = buffers.length;
-    var numBytes = buffers[0].byteLength;
-    var output, b;
-
-    if (imageOps) {
-      var images = new Array(numBuffers);
-      for (b = 0; b < numBuffers; ++b) {
-        images[b] = newWorkerImageData(
-            new Uint8ClampedArray(buffers[b]), width, height);
-      }
-      output = operation(images, meta).data;
-    } else {
-      output = new Uint8ClampedArray(numBytes);
-      var arrays = new Array(numBuffers);
-      var pixels = new Array(numBuffers);
-      for (b = 0; b < numBuffers; ++b) {
-        arrays[b] = new Uint8ClampedArray(buffers[b]);
-        pixels[b] = [0, 0, 0, 0];
-      }
-      for (var i = 0; i < numBytes; i += 4) {
-        for (var j = 0; j < numBuffers; ++j) {
-          var array = arrays[j];
-          pixels[j][0] = array[i];
-          pixels[j][1] = array[i + 1];
-          pixels[j][2] = array[i + 2];
-          pixels[j][3] = array[i + 3];
-        }
-        var pixel = operation(pixels, meta);
-        output[i] = pixel[0];
-        output[i + 1] = pixel[1];
-        output[i + 2] = pixel[2];
-        output[i + 3] = pixel[3];
-      }
-    }
-    return output.buffer;
-  };
-}
-
-/**
- * Create a worker for running operations.
- * @param {Object} config Configuration.
- * @param {function(MessageEvent)} onMessage Called with a message event.
- * @return {Worker} The worker.
- */
-function createWorker(config, onMessage) {
-  var lib = Object.keys(config.lib || {}).map(function(name) {
-    return 'var ' + name + ' = ' + config.lib[name].toString() + ';';
-  });
-
-  var lines = lib.concat([
-    'var __minion__ = (' + createMinion.toString() + ')(', config.operation.toString(), ');',
-    'self.addEventListener("message", function(event) {',
-    '  var buffer = __minion__(event.data);',
-    '  self.postMessage({buffer: buffer, meta: event.data.meta}, [buffer]);',
-    '});'
-  ]);
-
-  var blob = new Blob(lines, {type: 'text/javascript'});
-  var source = URL.createObjectURL(blob);
-  var worker = new Worker(source);
-  worker.addEventListener('message', onMessage);
-  return worker;
-}
-
-/**
- * Create a faux worker for running operations.
- * @param {Object} config Configuration.
- * @param {function(MessageEvent)} onMessage Called with a message event.
- * @return {Object} The faux worker.
- */
-function createFauxWorker(config, onMessage) {
-  var minion = createMinion(config.operation);
-  return {
-    postMessage: function(data) {
-      setTimeout(function() {
-        onMessage({'data': {'buffer': minion(data), 'meta': data['meta']}});
-      }, 0);
-    }
-  };
-}
-
-/**
- * A processor runs pixel or image operations in workers.
- * @param {Object} config Configuration.
- */
-function Processor(config) {
-  this._imageOps = !!config.imageOps;
-  var threads;
-  if (config.threads === 0) {
-    threads = 0;
-  } else if (this._imageOps) {
-    threads = 1;
-  } else {
-    threads = config.threads || 1;
-  }
-  var workers = [];
-  if (threads) {
-    for (var i = 0; i < threads; ++i) {
-      workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i));
-    }
-  } else {
-    workers[0] = createFauxWorker(config, this._onWorkerMessage.bind(this, 0));
-  }
-  this._workers = workers;
-  this._queue = [];
-  this._maxQueueLength = config.queue || Infinity;
-  this._running = 0;
-  this._dataLookup = {};
-  this._job = null;
-}
-
-/**
- * Run operation on input data.
- * @param {Array.<Array|ImageData>} inputs Array of pixels or image data
- *     (depending on the operation type).
- * @param {Object} meta A user data object.  This is passed to all operations
- *     and must be serializable.
- * @param {function(Error, ImageData, Object)} callback Called when work
- *     completes.  The first argument is any error.  The second is the ImageData
- *     generated by operations.  The third is the user data object.
- */
-Processor.prototype.process = function(inputs, meta, callback) {
-  this._enqueue({
-    inputs: inputs,
-    meta: meta,
-    callback: callback
-  });
-  this._dispatch();
-};
-
-/**
- * Stop responding to any completed work and destroy the processor.
- */
-Processor.prototype.destroy = function() {
-  for (var key in this) {
-    this[key] = null;
-  }
-  this._destroyed = true;
-};
-
-/**
- * Add a job to the queue.
- * @param {Object} job The job.
- */
-Processor.prototype._enqueue = function(job) {
-  this._queue.push(job);
-  while (this._queue.length > this._maxQueueLength) {
-    this._queue.shift().callback(null, null);
-  }
-};
-
-/**
- * Dispatch a job.
- */
-Processor.prototype._dispatch = function() {
-  if (this._running === 0 && this._queue.length > 0) {
-    var job = this._job = this._queue.shift();
-    var width = job.inputs[0].width;
-    var height = job.inputs[0].height;
-    var buffers = job.inputs.map(function(input) {
-      return input.data.buffer;
-    });
-    var threads = this._workers.length;
-    this._running = threads;
-    if (threads === 1) {
-      this._workers[0].postMessage({
-        'buffers': buffers,
-        'meta': job.meta,
-        'imageOps': this._imageOps,
-        'width': width,
-        'height': height
-      }, buffers);
-    } else {
-      var length = job.inputs[0].data.length;
-      var segmentLength = 4 * Math.ceil(length / 4 / threads);
-      for (var i = 0; i < threads; ++i) {
-        var offset = i * segmentLength;
-        var slices = [];
-        for (var j = 0, jj = buffers.length; j < jj; ++j) {
-          slices.push(buffers[i].slice(offset, offset + segmentLength));
-        }
-        this._workers[i].postMessage({
-          'buffers': slices,
-          'meta': job.meta,
-          'imageOps': this._imageOps,
-          'width': width,
-          'height': height
-        }, slices);
-      }
-    }
-  }
-};
-
-/**
- * Handle messages from the worker.
- * @param {number} index The worker index.
- * @param {MessageEvent} event The message event.
- */
-Processor.prototype._onWorkerMessage = function(index, event) {
-  if (this._destroyed) {
-    return;
-  }
-  this._dataLookup[index] = event.data;
-  --this._running;
-  if (this._running === 0) {
-    this._resolveJob();
-  }
-};
-
-/**
- * Resolve a job.  If there are no more worker threads, the processor callback
- * will be called.
- */
-Processor.prototype._resolveJob = function() {
-  var job = this._job;
-  var threads = this._workers.length;
-  var data, meta;
-  if (threads === 1) {
-    data = new Uint8ClampedArray(this._dataLookup[0]['buffer']);
-    meta = this._dataLookup[0]['meta'];
-  } else {
-    var length = job.inputs[0].data.length;
-    data = new Uint8ClampedArray(length);
-    meta = new Array(length);
-    var segmentLength = 4 * Math.ceil(length / 4 / threads);
-    for (var i = 0; i < threads; ++i) {
-      var buffer = this._dataLookup[i]['buffer'];
-      var offset = i * segmentLength;
-      data.set(new Uint8ClampedArray(buffer), offset);
-      meta[i] = this._dataLookup[i]['meta'];
-    }
-  }
-  this._job = null;
-  this._dataLookup = {};
-  job.callback(null,
-      newImageData(data, job.inputs[0].width, job.inputs[0].height), meta);
-  this._dispatch();
-};
-
-module.exports = Processor;
-
-},{"./util":3}],3:[function(_dereq_,module,exports){
-var hasImageData = true;
-try {
-  new ImageData(10, 10);
-} catch (_) {
-  hasImageData = false;
-}
-
-var context = document.createElement('canvas').getContext('2d');
-
-function newImageData(data, width, height) {
-  if (hasImageData) {
-    return new ImageData(data, width, height);
-  } else {
-    var imageData = context.createImageData(width, height);
-    imageData.data.set(data);
-    return imageData;
-  }
-}
-
-exports.newImageData = newImageData;
-
-},{}]},{},[1])(1)
-});
-ol.ext.pixelworks = module.exports;
-})();
-
-goog.provide('ol.source.Raster');
-goog.provide('ol.RasterOperationType');
-
-goog.require('ol');
-goog.require('ol.transform');
-goog.require('ol.ImageCanvas');
-goog.require('ol.TileQueue');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.EventType');
-goog.require('ol.ext.pixelworks');
-goog.require('ol.extent');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Tile');
-goog.require('ol.obj');
-goog.require('ol.renderer.canvas.ImageLayer');
-goog.require('ol.renderer.canvas.TileLayer');
-goog.require('ol.source.Image');
-goog.require('ol.source.State');
-goog.require('ol.source.Tile');
-
-
-/**
- * Raster operation type. Supported values are `'pixel'` and `'image'`.
- * @enum {string}
- */
-ol.RasterOperationType = {
-  PIXEL: 'pixel',
-  IMAGE: 'image'
-};
-
-
-/**
- * @classdesc
- * A source that transforms data from any number of input sources using an array
- * of {@link ol.RasterOperation} functions to transform input pixel values into
- * output pixel values.
- *
- * @constructor
- * @extends {ol.source.Image}
- * @fires ol.source.Raster.Event
- * @param {olx.source.RasterOptions} options Options.
- * @api
- */
-ol.source.Raster = function(options) {
-
-  /**
-   * @private
-   * @type {*}
-   */
-  this.worker_ = null;
-
-  /**
-   * @private
-   * @type {ol.RasterOperationType}
-   */
-  this.operationType_ = options.operationType !== undefined ?
-      options.operationType : ol.RasterOperationType.PIXEL;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.threads_ = options.threads !== undefined ? options.threads : 1;
-
-  /**
-   * @private
-   * @type {Array.<ol.renderer.canvas.Layer>}
-   */
-  this.renderers_ = ol.source.Raster.createRenderers_(options.sources);
-
-  for (var r = 0, rr = this.renderers_.length; r < rr; ++r) {
-    ol.events.listen(this.renderers_[r], ol.events.EventType.CHANGE,
-        this.changed, this);
-  }
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.canvasContext_ = ol.dom.createCanvasContext2D();
-
-  /**
-   * @private
-   * @type {ol.TileQueue}
-   */
-  this.tileQueue_ = new ol.TileQueue(
-      function() {
-        return 1;
-      },
-      this.changed.bind(this));
-
-  var layerStatesArray = ol.source.Raster.getLayerStatesArray_(this.renderers_);
-  var layerStates = {};
-  for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) {
-    layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
-  }
-
-  /**
-   * The most recently rendered state.
-   * @type {?ol.SourceRasterRenderedState}
-   * @private
-   */
-  this.renderedState_ = null;
-
-  /**
-   * The most recently rendered image canvas.
-   * @type {ol.ImageCanvas}
-   * @private
-   */
-  this.renderedImageCanvas_ = null;
-
-  /**
-   * @private
-   * @type {olx.FrameState}
-   */
-  this.frameState_ = {
-    animate: false,
-    attributions: {},
-    coordinateToPixelTransform: ol.transform.create(),
-    extent: null,
-    focus: null,
-    index: 0,
-    layerStates: layerStates,
-    layerStatesArray: layerStatesArray,
-    logos: {},
-    pixelRatio: 1,
-    pixelToCoordinateTransform: ol.transform.create(),
-    postRenderFunctions: [],
-    size: [0, 0],
-    skippedFeatureUids: {},
-    tileQueue: this.tileQueue_,
-    time: Date.now(),
-    usedTiles: {},
-    viewState: /** @type {olx.ViewState} */ ({
-      rotation: 0
-    }),
-    viewHints: [],
-    wantedTiles: {}
-  };
-
-  ol.source.Image.call(this, {});
-
-  if (options.operation !== undefined) {
-    this.setOperation(options.operation, options.lib);
-  }
-
-};
-ol.inherits(ol.source.Raster, ol.source.Image);
-
-
-/**
- * Set the operation.
- * @param {ol.RasterOperation} operation New operation.
- * @param {Object=} opt_lib Functions that will be available to operations run
- *     in a worker.
- * @api
- */
-ol.source.Raster.prototype.setOperation = function(operation, opt_lib) {
-  this.worker_ = new ol.ext.pixelworks.Processor({
-    operation: operation,
-    imageOps: this.operationType_ === ol.RasterOperationType.IMAGE,
-    queue: 1,
-    lib: opt_lib,
-    threads: this.threads_
-  });
-  this.changed();
-};
-
-
-/**
- * Update the stored frame state.
- * @param {ol.Extent} extent The view extent (in map units).
- * @param {number} resolution The view resolution.
- * @param {ol.proj.Projection} projection The view projection.
- * @return {olx.FrameState} The updated frame state.
- * @private
- */
-ol.source.Raster.prototype.updateFrameState_ = function(extent, resolution, projection) {
-
-  var frameState = /** @type {olx.FrameState} */ (
-      ol.obj.assign({}, this.frameState_));
-
-  frameState.viewState = /** @type {olx.ViewState} */ (
-      ol.obj.assign({}, frameState.viewState));
-
-  var center = ol.extent.getCenter(extent);
-  var width = Math.round(ol.extent.getWidth(extent) / resolution);
-  var height = Math.round(ol.extent.getHeight(extent) / resolution);
-
-  frameState.extent = extent;
-  frameState.focus = ol.extent.getCenter(extent);
-  frameState.size[0] = width;
-  frameState.size[1] = height;
-
-  var viewState = frameState.viewState;
-  viewState.center = center;
-  viewState.projection = projection;
-  viewState.resolution = resolution;
-  return frameState;
-};
-
-
-/**
- * Determine if the most recently rendered image canvas is dirty.
- * @param {ol.Extent} extent The requested extent.
- * @param {number} resolution The requested resolution.
- * @return {boolean} The image is dirty.
- * @private
- */
-ol.source.Raster.prototype.isDirty_ = function(extent, resolution) {
-  var state = this.renderedState_;
-  return !state ||
-      this.getRevision() !== state.revision ||
-      resolution !== state.resolution ||
-      !ol.extent.equals(extent, state.extent);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.Raster.prototype.getImage = function(extent, resolution, pixelRatio, projection) {
-
-  if (!this.allSourcesReady_()) {
-    return null;
-  }
-
-  var currentExtent = extent.slice();
-  if (!this.isDirty_(currentExtent, resolution)) {
-    return this.renderedImageCanvas_;
-  }
-
-  var context = this.canvasContext_;
-  var canvas = context.canvas;
-
-  var width = Math.round(ol.extent.getWidth(currentExtent) / resolution);
-  var height = Math.round(ol.extent.getHeight(currentExtent) / resolution);
-
-  if (width !== canvas.width ||
-      height !== canvas.height) {
-    canvas.width = width;
-    canvas.height = height;
-  }
-
-  var frameState = this.updateFrameState_(currentExtent, resolution, projection);
-
-  var imageCanvas = new ol.ImageCanvas(
-      currentExtent, resolution, 1, this.getAttributions(), canvas,
-      this.composeFrame_.bind(this, frameState));
-
-  this.renderedImageCanvas_ = imageCanvas;
-
-  this.renderedState_ = {
-    extent: currentExtent,
-    resolution: resolution,
-    revision: this.getRevision()
-  };
-
-  return imageCanvas;
-};
-
-
-/**
- * Determine if all sources are ready.
- * @return {boolean} All sources are ready.
- * @private
- */
-ol.source.Raster.prototype.allSourcesReady_ = function() {
-  var ready = true;
-  var source;
-  for (var i = 0, ii = this.renderers_.length; i < ii; ++i) {
-    source = this.renderers_[i].getLayer().getSource();
-    if (source.getState() !== ol.source.State.READY) {
-      ready = false;
-      break;
-    }
-  }
-  return ready;
-};
-
-
-/**
- * Compose the frame.  This renders data from all sources, runs pixel-wise
- * operations, and renders the result to the stored canvas context.
- * @param {olx.FrameState} frameState The frame state.
- * @param {function(Error)} callback Called when composition is complete.
- * @private
- */
-ol.source.Raster.prototype.composeFrame_ = function(frameState, callback) {
-  var len = this.renderers_.length;
-  var imageDatas = new Array(len);
-  for (var i = 0; i < len; ++i) {
-    var imageData = ol.source.Raster.getImageData_(
-        this.renderers_[i], frameState, frameState.layerStatesArray[i]);
-    if (imageData) {
-      imageDatas[i] = imageData;
-    } else {
-      // image not yet ready
-      return;
-    }
-  }
-
-  var data = {};
-  this.dispatchEvent(new ol.source.Raster.Event(
-      ol.source.Raster.EventType.BEFOREOPERATIONS, frameState, data));
-
-  this.worker_.process(imageDatas, data,
-      this.onWorkerComplete_.bind(this, frameState, callback));
-
-  frameState.tileQueue.loadMoreTiles(16, 16);
-};
-
-
-/**
- * Called when pixel processing is complete.
- * @param {olx.FrameState} frameState The frame state.
- * @param {function(Error)} callback Called when rendering is complete.
- * @param {Error} err Any error during processing.
- * @param {ImageData} output The output image data.
- * @param {Object} data The user data.
- * @private
- */
-ol.source.Raster.prototype.onWorkerComplete_ = function(frameState, callback, err, output, data) {
-  if (err) {
-    callback(err);
-    return;
-  }
-  if (!output) {
-    // job aborted
-    return;
-  }
-
-  this.dispatchEvent(new ol.source.Raster.Event(
-      ol.source.Raster.EventType.AFTEROPERATIONS, frameState, data));
-
-  var resolution = frameState.viewState.resolution / frameState.pixelRatio;
-  if (!this.isDirty_(frameState.extent, resolution)) {
-    this.canvasContext_.putImageData(output, 0, 0);
-  }
-
-  callback(null);
-};
-
-
-/**
- * Get image data from a renderer.
- * @param {ol.renderer.canvas.Layer} renderer Layer renderer.
- * @param {olx.FrameState} frameState The frame state.
- * @param {ol.LayerState} layerState The layer state.
- * @return {ImageData} The image data.
- * @private
- */
-ol.source.Raster.getImageData_ = function(renderer, frameState, layerState) {
-  if (!renderer.prepareFrame(frameState, layerState)) {
-    return null;
-  }
-  var width = frameState.size[0];
-  var height = frameState.size[1];
-  if (!ol.source.Raster.context_) {
-    ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height);
-  } else {
-    var canvas = ol.source.Raster.context_.canvas;
-    if (canvas.width !== width || canvas.height !== height) {
-      ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height);
-    } else {
-      ol.source.Raster.context_.clearRect(0, 0, width, height);
-    }
-  }
-  renderer.composeFrame(frameState, layerState, ol.source.Raster.context_);
-  return ol.source.Raster.context_.getImageData(0, 0, width, height);
-};
-
-
-/**
- * A reusable canvas context.
- * @type {CanvasRenderingContext2D}
- * @private
- */
-ol.source.Raster.context_ = null;
-
-
-/**
- * Get a list of layer states from a list of renderers.
- * @param {Array.<ol.renderer.canvas.Layer>} renderers Layer renderers.
- * @return {Array.<ol.LayerState>} The layer states.
- * @private
- */
-ol.source.Raster.getLayerStatesArray_ = function(renderers) {
-  return renderers.map(function(renderer) {
-    return renderer.getLayer().getLayerState();
-  });
-};
-
-
-/**
- * Create renderers for all sources.
- * @param {Array.<ol.source.Source>} sources The sources.
- * @return {Array.<ol.renderer.canvas.Layer>} Array of layer renderers.
- * @private
- */
-ol.source.Raster.createRenderers_ = function(sources) {
-  var len = sources.length;
-  var renderers = new Array(len);
-  for (var i = 0; i < len; ++i) {
-    renderers[i] = ol.source.Raster.createRenderer_(sources[i]);
-  }
-  return renderers;
-};
-
-
-/**
- * Create a renderer for the provided source.
- * @param {ol.source.Source} source The source.
- * @return {ol.renderer.canvas.Layer} The renderer.
- * @private
- */
-ol.source.Raster.createRenderer_ = function(source) {
-  var renderer = null;
-  if (source instanceof ol.source.Tile) {
-    renderer = ol.source.Raster.createTileRenderer_(source);
-  } else if (source instanceof ol.source.Image) {
-    renderer = ol.source.Raster.createImageRenderer_(source);
-  } else {
-    ol.DEBUG && console.assert(false, 'Unsupported source type: ' + source);
-  }
-  return renderer;
-};
-
-
-/**
- * Create an image renderer for the provided source.
- * @param {ol.source.Image} source The source.
- * @return {ol.renderer.canvas.Layer} The renderer.
- * @private
- */
-ol.source.Raster.createImageRenderer_ = function(source) {
-  var layer = new ol.layer.Image({source: source});
-  return new ol.renderer.canvas.ImageLayer(layer);
-};
-
-
-/**
- * Create a tile renderer for the provided source.
- * @param {ol.source.Tile} source The source.
- * @return {ol.renderer.canvas.Layer} The renderer.
- * @private
- */
-ol.source.Raster.createTileRenderer_ = function(source) {
-  var layer = new ol.layer.Tile({source: source});
-  return new ol.renderer.canvas.TileLayer(layer);
-};
-
-
-/**
- * @classdesc
- * Events emitted by {@link ol.source.Raster} instances are instances of this
- * type.
- *
- * @constructor
- * @extends {ol.events.Event}
- * @implements {oli.source.RasterEvent}
- * @param {string} type Type.
- * @param {olx.FrameState} frameState The frame state.
- * @param {Object} data An object made available to operations.
- */
-ol.source.Raster.Event = function(type, frameState, data) {
-  ol.events.Event.call(this, type);
-
-  /**
-   * The raster extent.
-   * @type {ol.Extent}
-   * @api
-   */
-  this.extent = frameState.extent;
-
-  /**
-   * The pixel resolution (map units per pixel).
-   * @type {number}
-   * @api
-   */
-  this.resolution = frameState.viewState.resolution / frameState.pixelRatio;
-
-  /**
-   * An object made available to all operations.  This can be used by operations
-   * as a storage object (e.g. for calculating statistics).
-   * @type {Object}
-   * @api
-   */
-  this.data = data;
-
-};
-ol.inherits(ol.source.Raster.Event, ol.events.Event);
-
-
-/**
- * @enum {string}
- */
-ol.source.Raster.EventType = {
-  /**
-   * Triggered before operations are run.
-   * @event ol.source.Raster.Event#beforeoperations
-   * @api
-   */
-  BEFOREOPERATIONS: 'beforeoperations',
-
-  /**
-   * Triggered after operations are run.
-   * @event ol.source.Raster.Event#afteroperations
-   * @api
-   */
-  AFTEROPERATIONS: 'afteroperations'
-};
-
-goog.provide('ol.source.Stamen');
-
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.source.OSM');
-goog.require('ol.source.XYZ');
-
-
-/**
- * @classdesc
- * Layer source for the Stamen tile server.
- *
- * @constructor
- * @extends {ol.source.XYZ}
- * @param {olx.source.StamenOptions} options Stamen options.
- * @api stable
- */
-ol.source.Stamen = function(options) {
-
-  var i = options.layer.indexOf('-');
-  var provider = i == -1 ? options.layer : options.layer.slice(0, i);
-  ol.DEBUG && console.assert(provider in ol.source.Stamen.ProviderConfig,
-      'known provider configured');
-  var providerConfig = ol.source.Stamen.ProviderConfig[provider];
-
-  ol.DEBUG && console.assert(options.layer in ol.source.Stamen.LayerConfig,
-      'known layer configured');
-  var layerConfig = ol.source.Stamen.LayerConfig[options.layer];
-
-  var url = options.url !== undefined ? options.url :
-      'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' + options.layer +
-      '/{z}/{x}/{y}.' + layerConfig.extension;
-
-  ol.source.XYZ.call(this, {
-    attributions: ol.source.Stamen.ATTRIBUTIONS,
-    cacheSize: options.cacheSize,
-    crossOrigin: 'anonymous',
-    maxZoom: options.maxZoom != undefined ? options.maxZoom : providerConfig.maxZoom,
-    minZoom: options.minZoom != undefined ? options.minZoom : providerConfig.minZoom,
-    opaque: layerConfig.opaque,
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileLoadFunction: options.tileLoadFunction,
-    url: url
-  });
-
-};
-ol.inherits(ol.source.Stamen, ol.source.XYZ);
-
-
-/**
- * @const
- * @type {Array.<ol.Attribution>}
- */
-ol.source.Stamen.ATTRIBUTIONS = [
-  new ol.Attribution({
-    html: 'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ' +
-        'under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY' +
-        ' 3.0</a>.'
-  }),
-  ol.source.OSM.ATTRIBUTION
-];
-
-/**
- * @type {Object.<string, {extension: string, opaque: boolean}>}
- */
-ol.source.Stamen.LayerConfig = {
-  'terrain': {
-    extension: 'jpg',
-    opaque: true
-  },
-  'terrain-background': {
-    extension: 'jpg',
-    opaque: true
-  },
-  'terrain-labels': {
-    extension: 'png',
-    opaque: false
-  },
-  'terrain-lines': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-background': {
-    extension: 'png',
-    opaque: true
-  },
-  'toner': {
-    extension: 'png',
-    opaque: true
-  },
-  'toner-hybrid': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-labels': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-lines': {
-    extension: 'png',
-    opaque: false
-  },
-  'toner-lite': {
-    extension: 'png',
-    opaque: true
-  },
-  'watercolor': {
-    extension: 'jpg',
-    opaque: true
-  }
-};
-
-/**
- * @type {Object.<string, {minZoom: number, maxZoom: number}>}
- */
-ol.source.Stamen.ProviderConfig = {
-  'terrain': {
-    minZoom: 4,
-    maxZoom: 18
-  },
-  'toner': {
-    minZoom: 0,
-    maxZoom: 20
-  },
-  'watercolor': {
-    minZoom: 1,
-    maxZoom: 16
-  }
-};
-
-goog.provide('ol.source.TileArcGISRest');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.extent');
-goog.require('ol.math');
-goog.require('ol.obj');
-goog.require('ol.size');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilecoord');
-goog.require('ol.uri');
-
-
-/**
- * @classdesc
- * Layer source for tile data from ArcGIS Rest services. Map and Image
- * Services are supported.
- *
- * For cached ArcGIS services, better performance is available using the
- * {@link ol.source.XYZ} data source.
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.TileArcGISRestOptions=} opt_options Tile ArcGIS Rest
- *     options.
- * @api
- */
-ol.source.TileArcGISRest = function(opt_options) {
-
-  var options = opt_options || {};
-
-  ol.source.TileImage.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    projection: options.projection,
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileGrid: options.tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    url: options.url,
-    urls: options.urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-  /**
-   * @private
-   * @type {!Object}
-   */
-  this.params_ = options.params || {};
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.tmpExtent_ = ol.extent.createEmpty();
-
-};
-ol.inherits(ol.source.TileArcGISRest, ol.source.TileImage);
-
-
-/**
- * Get the user-provided params, i.e. those passed to the constructor through
- * the "params" option, and possibly updated using the updateParams method.
- * @return {Object} Params.
- * @api
- */
-ol.source.TileArcGISRest.prototype.getParams = function() {
-  return this.params_;
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Size} tileSize Tile size.
- * @param {ol.Extent} tileExtent Tile extent.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @param {Object} params Params.
- * @return {string|undefined} Request URL.
- * @private
- */
-ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
-        pixelRatio, projection, params) {
-
-  var urls = this.urls;
-  if (!urls) {
-    return undefined;
-  }
-
-  // ArcGIS Server only wants the numeric portion of the projection ID.
-  var srid = projection.getCode().split(':').pop();
-
-  params['SIZE'] = tileSize[0] + ',' + tileSize[1];
-  params['BBOX'] = tileExtent.join(',');
-  params['BBOXSR'] = srid;
-  params['IMAGESR'] = srid;
-  params['DPI'] = Math.round(
-      params['DPI'] ? params['DPI'] * pixelRatio : 90 * pixelRatio
-      );
-
-  var url;
-  if (urls.length == 1) {
-    url = urls[0];
-  } else {
-    var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
-    url = urls[index];
-  }
-
-  var modifiedUrl = url
-      .replace(/MapServer\/?$/, 'MapServer/export')
-      .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
-  if (modifiedUrl == url) {
-    ol.asserts.assert(false, 50); // Cannot determine Rest Service from url
-  }
-  return ol.uri.appendParams(modifiedUrl, params);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileArcGISRest.prototype.getTilePixelRatio = function(pixelRatio) {
-  return /** @type {number} */ (pixelRatio);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileArcGISRest.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) {
-
-  var tileGrid = this.getTileGrid();
-  if (!tileGrid) {
-    tileGrid = this.getTileGridForProjection(projection);
-  }
-
-  if (tileGrid.getResolutions().length <= tileCoord[0]) {
-    return undefined;
-  }
-
-  var tileExtent = tileGrid.getTileCoordExtent(
-      tileCoord, this.tmpExtent_);
-  var tileSize = ol.size.toSize(
-      tileGrid.getTileSize(tileCoord[0]), this.tmpSize);
-
-  if (pixelRatio != 1) {
-    tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize);
-  }
-
-  // Apply default params and override with user specified values.
-  var baseParams = {
-    'F': 'image',
-    'FORMAT': 'PNG32',
-    'TRANSPARENT': true
-  };
-  ol.obj.assign(baseParams, this.params_);
-
-  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
-      pixelRatio, projection, baseParams);
-};
-
-
-/**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
- */
-ol.source.TileArcGISRest.prototype.updateParams = function(params) {
-  ol.obj.assign(this.params_, params);
-  this.changed();
-};
-
-goog.provide('ol.source.TileDebug');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.dom');
-goog.require('ol.size');
-goog.require('ol.source.Tile');
-
-
-/**
- * @classdesc
- * A pseudo tile source, which does not fetch tiles from a server, but renders
- * a grid outline for the tile grid/projection along with the coordinates for
- * each tile. See examples/canvas-tiles for an example.
- *
- * Uses Canvas context2d, so requires Canvas support.
- *
- * @constructor
- * @extends {ol.source.Tile}
- * @param {olx.source.TileDebugOptions} options Debug tile options.
- * @api
- */
-ol.source.TileDebug = function(options) {
-
-  ol.source.Tile.call(this, {
-    opaque: false,
-    projection: options.projection,
-    tileGrid: options.tileGrid,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-};
-ol.inherits(ol.source.TileDebug, ol.source.Tile);
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileDebug.prototype.getTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    return /** @type {!ol.source.TileDebug.Tile_} */ (this.tileCache.get(tileCoordKey));
-  } else {
-    var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z));
-    var tileCoord = [z, x, y];
-    var textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord);
-    var text = !textTileCoord ? '' :
-        this.getTileCoordForTileUrlFunction(textTileCoord).toString();
-    var tile = new ol.source.TileDebug.Tile_(tileCoord, tileSize, text);
-    this.tileCache.set(tileCoordKey, tile);
-    return tile;
-  }
-};
-
-
-/**
- * @constructor
- * @extends {ol.Tile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Size} tileSize Tile size.
- * @param {string} text Text.
- * @private
- */
-ol.source.TileDebug.Tile_ = function(tileCoord, tileSize, text) {
-
-  ol.Tile.call(this, tileCoord, ol.Tile.State.LOADED);
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.tileSize_ = tileSize;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.text_ = text;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = null;
-
-};
-ol.inherits(ol.source.TileDebug.Tile_, ol.Tile);
-
-
-/**
- * Get the image element for this tile.
- * @return {HTMLCanvasElement} Image.
- */
-ol.source.TileDebug.Tile_.prototype.getImage = function() {
-  if (this.canvas_) {
-    return this.canvas_;
-  } else {
-    var tileSize = this.tileSize_;
-    var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]);
-
-    context.strokeStyle = 'black';
-    context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5);
-
-    context.fillStyle = 'black';
-    context.textAlign = 'center';
-    context.textBaseline = 'middle';
-    context.font = '24px sans-serif';
-    context.fillText(this.text_, tileSize[0] / 2, tileSize[1] / 2);
-
-    this.canvas_ = context.canvas;
-    return context.canvas;
-  }
-};
-
-// FIXME check order of async callbacks
-
-/**
- * @see http://mapbox.com/developers/api/
- */
-
-goog.provide('ol.source.TileJSON');
-
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.extent');
-goog.require('ol.net');
-goog.require('ol.proj');
-goog.require('ol.source.State');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilegrid');
-
-
-/**
- * @classdesc
- * Layer source for tile data in TileJSON format.
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.TileJSONOptions} options TileJSON options.
- * @api stable
- */
-ol.source.TileJSON = function(options) {
-
-  /**
-   * @type {TileJSON}
-   * @private
-   */
-  this.tileJSON_ = null;
-
-  ol.source.TileImage.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: options.crossOrigin,
-    projection: ol.proj.get('EPSG:3857'),
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    state: ol.source.State.LOADING,
-    tileLoadFunction: options.tileLoadFunction,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-  if (options.jsonp) {
-    ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this),
-        this.handleTileJSONError.bind(this));
-  } else {
-    var client = new XMLHttpRequest();
-    client.addEventListener('load', this.onXHRLoad_.bind(this));
-    client.addEventListener('error', this.onXHRError_.bind(this));
-    client.open('GET', options.url);
-    client.send();
-  }
-
-};
-ol.inherits(ol.source.TileJSON, ol.source.TileImage);
-
-
-/**
- * @private
- * @param {Event} event The load event.
- */
-ol.source.TileJSON.prototype.onXHRLoad_ = function(event) {
-  var client = /** @type {XMLHttpRequest} */ (event.target);
-  // status will be 0 for file:// urls
-  if (!client.status || client.status >= 200 && client.status < 300) {
-    var response;
-    try {
-      response = /** @type {TileJSON} */(JSON.parse(client.responseText));
-    } catch (err) {
-      this.handleTileJSONError();
-      return;
-    }
-    this.handleTileJSONResponse(response);
-  } else {
-    this.handleTileJSONError();
-  }
-};
-
-
-/**
- * @private
- * @param {Event} event The error event.
- */
-ol.source.TileJSON.prototype.onXHRError_ = function(event) {
-  this.handleTileJSONError();
-};
-
-
-/**
- * @return {TileJSON} The tilejson object.
- * @api
- */
-ol.source.TileJSON.prototype.getTileJSON = function() {
-  return this.tileJSON_;
-};
-
-
-/**
- * @protected
- * @param {TileJSON} tileJSON Tile JSON.
- */
-ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) {
-
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-
-  var sourceProjection = this.getProjection();
-  var extent;
-  if (tileJSON.bounds !== undefined) {
-    var transform = ol.proj.getTransformFromProjections(
-        epsg4326Projection, sourceProjection);
-    extent = ol.extent.applyTransform(tileJSON.bounds, transform);
-  }
-
-  if (tileJSON.scheme !== undefined) {
-    ol.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"');
-  }
-  var minZoom = tileJSON.minzoom || 0;
-  var maxZoom = tileJSON.maxzoom || 22;
-  var tileGrid = ol.tilegrid.createXYZ({
-    extent: ol.tilegrid.extentFromProjection(sourceProjection),
-    maxZoom: maxZoom,
-    minZoom: minZoom
-  });
-  this.tileGrid = tileGrid;
-
-  this.tileUrlFunction =
-      ol.TileUrlFunction.createFromTemplates(tileJSON.tiles, tileGrid);
-
-  if (tileJSON.attribution !== undefined && !this.getAttributions()) {
-    var attributionExtent = extent !== undefined ?
-        extent : epsg4326Projection.getExtent();
-    /** @type {Object.<string, Array.<ol.TileRange>>} */
-    var tileRanges = {};
-    var z, zKey;
-    for (z = minZoom; z <= maxZoom; ++z) {
-      zKey = z.toString();
-      tileRanges[zKey] =
-          [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
-    }
-    this.setAttributions([
-      new ol.Attribution({
-        html: tileJSON.attribution,
-        tileRanges: tileRanges
-      })
-    ]);
-  }
-  this.tileJSON_ = tileJSON;
-  this.setState(ol.source.State.READY);
-
-};
-
-
-/**
- * @protected
- */
-ol.source.TileJSON.prototype.handleTileJSONError = function() {
-  this.setState(ol.source.State.ERROR);
-};
-
-goog.provide('ol.source.TileUTFGrid');
-
-goog.require('ol');
-goog.require('ol.Attribution');
-goog.require('ol.Tile');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.asserts');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.net');
-goog.require('ol.proj');
-goog.require('ol.source.State');
-goog.require('ol.source.Tile');
-goog.require('ol.tilegrid');
-
-
-/**
- * @classdesc
- * Layer source for UTFGrid interaction data loaded from TileJSON format.
- *
- * @constructor
- * @extends {ol.source.Tile}
- * @param {olx.source.TileUTFGridOptions} options Source options.
- * @api
- */
-ol.source.TileUTFGrid = function(options) {
-  ol.source.Tile.call(this, {
-    projection: ol.proj.get('EPSG:3857'),
-    state: ol.source.State.LOADING
-  });
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.preemptive_ = options.preemptive !== undefined ?
-      options.preemptive : true;
-
-  /**
-   * @private
-   * @type {!ol.TileUrlFunctionType}
-   */
-  this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction;
-
-  /**
-   * @private
-   * @type {string|undefined}
-   */
-  this.template_ = undefined;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.jsonp_ = options.jsonp || false;
-
-  if (options.url) {
-    if (this.jsonp_) {
-      ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this),
-          this.handleTileJSONError.bind(this));
-    } else {
-      var client = new XMLHttpRequest();
-      client.addEventListener('load', this.onXHRLoad_.bind(this));
-      client.addEventListener('error', this.onXHRError_.bind(this));
-      client.open('GET', options.url);
-      client.send();
-    }
-  } else if (options.tileJSON) {
-    this.handleTileJSONResponse(options.tileJSON);
-  } else {
-    ol.asserts.assert(false, 51); // Either `url` or `tileJSON` options must be provided
-  }
-};
-ol.inherits(ol.source.TileUTFGrid, ol.source.Tile);
-
-
-/**
- * @private
- * @param {Event} event The load event.
- */
-ol.source.TileUTFGrid.prototype.onXHRLoad_ = function(event) {
-  var client = /** @type {XMLHttpRequest} */ (event.target);
-  // status will be 0 for file:// urls
-  if (!client.status || client.status >= 200 && client.status < 300) {
-    var response;
-    try {
-      response = /** @type {TileJSON} */(JSON.parse(client.responseText));
-    } catch (err) {
-      this.handleTileJSONError();
-      return;
-    }
-    this.handleTileJSONResponse(response);
-  } else {
-    this.handleTileJSONError();
-  }
-};
-
-
-/**
- * @private
- * @param {Event} event The error event.
- */
-ol.source.TileUTFGrid.prototype.onXHRError_ = function(event) {
-  this.handleTileJSONError();
-};
-
-
-/**
- * Return the template from TileJSON.
- * @return {string|undefined} The template from TileJSON.
- * @api
- */
-ol.source.TileUTFGrid.prototype.getTemplate = function() {
-  return this.template_;
-};
-
-
-/**
- * Calls the callback (synchronously by default) with the available data
- * for given coordinate and resolution (or `null` if not yet loaded or
- * in case of an error).
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {function(this: T, *)} callback Callback.
- * @param {T=} opt_this The object to use as `this` in the callback.
- * @param {boolean=} opt_request If `true` the callback is always async.
- *                               The tile data is requested if not yet loaded.
- * @template T
- * @api
- */
-ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function(
-    coordinate, resolution, callback, opt_this, opt_request) {
-  if (this.tileGrid) {
-    var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution(
-        coordinate, resolution);
-    var tile = /** @type {!ol.source.TileUTFGrid.Tile_} */(this.getTile(
-        tileCoord[0], tileCoord[1], tileCoord[2], 1, this.getProjection()));
-    tile.forDataAtCoordinate(coordinate, callback, opt_this, opt_request);
-  } else {
-    if (opt_request === true) {
-      setTimeout(function() {
-        callback.call(opt_this, null);
-      }, 0);
-    } else {
-      callback.call(opt_this, null);
-    }
-  }
-};
-
-
-/**
- * @protected
- */
-ol.source.TileUTFGrid.prototype.handleTileJSONError = function() {
-  this.setState(ol.source.State.ERROR);
-};
-
-
-/**
- * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse
- * @protected
- * @param {TileJSON} tileJSON Tile JSON.
- */
-ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {
-
-  var epsg4326Projection = ol.proj.get('EPSG:4326');
-
-  var sourceProjection = this.getProjection();
-  var extent;
-  if (tileJSON.bounds !== undefined) {
-    var transform = ol.proj.getTransformFromProjections(
-        epsg4326Projection, sourceProjection);
-    extent = ol.extent.applyTransform(tileJSON.bounds, transform);
-  }
-
-  if (tileJSON.scheme !== undefined) {
-    ol.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"');
-  }
-  var minZoom = tileJSON.minzoom || 0;
-  var maxZoom = tileJSON.maxzoom || 22;
-  var tileGrid = ol.tilegrid.createXYZ({
-    extent: ol.tilegrid.extentFromProjection(sourceProjection),
-    maxZoom: maxZoom,
-    minZoom: minZoom
-  });
-  this.tileGrid = tileGrid;
-
-  this.template_ = tileJSON.template;
-
-  var grids = tileJSON.grids;
-  if (!grids) {
-    this.setState(ol.source.State.ERROR);
-    return;
-  }
-
-  this.tileUrlFunction_ =
-      ol.TileUrlFunction.createFromTemplates(grids, tileGrid);
-
-  if (tileJSON.attribution !== undefined) {
-    var attributionExtent = extent !== undefined ?
-        extent : epsg4326Projection.getExtent();
-    /** @type {Object.<string, Array.<ol.TileRange>>} */
-    var tileRanges = {};
-    var z, zKey;
-    for (z = minZoom; z <= maxZoom; ++z) {
-      zKey = z.toString();
-      tileRanges[zKey] =
-          [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
-    }
-    this.setAttributions([
-      new ol.Attribution({
-        html: tileJSON.attribution,
-        tileRanges: tileRanges
-      })
-    ]);
-  }
-
-  this.setState(ol.source.State.READY);
-
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projection) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
-  } else {
-    ol.DEBUG && console.assert(projection, 'argument projection is truthy');
-    var tileCoord = [z, x, y];
-    var urlTileCoord =
-        this.getTileCoordForTileUrlFunction(tileCoord, projection);
-    var tileUrl = this.tileUrlFunction_(urlTileCoord, pixelRatio, projection);
-    var tile = new ol.source.TileUTFGrid.Tile_(
-        tileCoord,
-        tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY,
-        tileUrl !== undefined ? tileUrl : '',
-        this.tileGrid.getTileCoordExtent(tileCoord),
-        this.preemptive_,
-        this.jsonp_);
-    this.tileCache.set(tileCoordKey, tile);
-    return tile;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    this.tileCache.get(tileCoordKey);
-  }
-};
-
-
-/**
- * @constructor
- * @extends {ol.Tile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Tile.State} state State.
- * @param {string} src Image source URI.
- * @param {ol.Extent} extent Extent of the tile.
- * @param {boolean} preemptive Load the tile when visible (before it's needed).
- * @param {boolean} jsonp Load the tile as a script.
- * @private
- */
-ol.source.TileUTFGrid.Tile_ = function(tileCoord, state, src, extent, preemptive, jsonp) {
-
-  ol.Tile.call(this, tileCoord, state);
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.src_ = src;
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.extent_ = extent;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.preemptive_ = preemptive;
-
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.grid_ = null;
-
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.keys_ = null;
-
-  /**
-   * @private
-   * @type {Object.<string, Object>|undefined}
-   */
-  this.data_ = null;
-
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.jsonp_ = jsonp;
-
-};
-ol.inherits(ol.source.TileUTFGrid.Tile_, ol.Tile);
-
-
-/**
- * Get the image element for this tile.
- * @return {Image} Image.
- */
-ol.source.TileUTFGrid.Tile_.prototype.getImage = function() {
-  return null;
-};
-
-
-/**
- * Synchronously returns data at given coordinate (if available).
- * @param {ol.Coordinate} coordinate Coordinate.
- * @return {*} The data.
- */
-ol.source.TileUTFGrid.Tile_.prototype.getData = function(coordinate) {
-  if (!this.grid_ || !this.keys_) {
-    return null;
-  }
-  var xRelative = (coordinate[0] - this.extent_[0]) /
-      (this.extent_[2] - this.extent_[0]);
-  var yRelative = (coordinate[1] - this.extent_[1]) /
-      (this.extent_[3] - this.extent_[1]);
-
-  var row = this.grid_[Math.floor((1 - yRelative) * this.grid_.length)];
-
-  if (typeof row !== 'string') {
-    return null;
-  }
-
-  var code = row.charCodeAt(Math.floor(xRelative * row.length));
-  if (code >= 93) {
-    code--;
-  }
-  if (code >= 35) {
-    code--;
-  }
-  code -= 32;
-
-  var data = null;
-  if (code in this.keys_) {
-    var id = this.keys_[code];
-    if (this.data_ && id in this.data_) {
-      data = this.data_[id];
-    } else {
-      data = id;
-    }
-  }
-  return data;
-};
-
-
-/**
- * Calls the callback (synchronously by default) with the available data
- * for given coordinate (or `null` if not yet loaded).
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {function(this: T, *)} callback Callback.
- * @param {T=} opt_this The object to use as `this` in the callback.
- * @param {boolean=} opt_request If `true` the callback is always async.
- *                               The tile data is requested if not yet loaded.
- * @template T
- */
-ol.source.TileUTFGrid.Tile_.prototype.forDataAtCoordinate = function(coordinate, callback, opt_this, opt_request) {
-  if (this.state == ol.Tile.State.IDLE && opt_request === true) {
-    ol.events.listenOnce(this, ol.events.EventType.CHANGE, function(e) {
-      callback.call(opt_this, this.getData(coordinate));
-    }, this);
-    this.loadInternal_();
-  } else {
-    if (opt_request === true) {
-      setTimeout(function() {
-        callback.call(opt_this, this.getData(coordinate));
-      }.bind(this), 0);
-    } else {
-      callback.call(opt_this, this.getData(coordinate));
-    }
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileUTFGrid.Tile_.prototype.getKey = function() {
-  return this.src_;
-};
-
-
-/**
- * @private
- */
-ol.source.TileUTFGrid.Tile_.prototype.handleError_ = function() {
-  this.state = ol.Tile.State.ERROR;
-  this.changed();
-};
-
-
-/**
- * @param {!UTFGridJSON} json UTFGrid data.
- * @private
- */
-ol.source.TileUTFGrid.Tile_.prototype.handleLoad_ = function(json) {
-  this.grid_ = json.grid;
-  this.keys_ = json.keys;
-  this.data_ = json.data;
-
-  this.state = ol.Tile.State.EMPTY;
-  this.changed();
-};
-
-
-/**
- * @private
- */
-ol.source.TileUTFGrid.Tile_.prototype.loadInternal_ = function() {
-  if (this.state == ol.Tile.State.IDLE) {
-    this.state = ol.Tile.State.LOADING;
-    if (this.jsonp_) {
-      ol.net.jsonp(this.src_, this.handleLoad_.bind(this),
-          this.handleError_.bind(this));
-    } else {
-      var client = new XMLHttpRequest();
-      client.addEventListener('load', this.onXHRLoad_.bind(this));
-      client.addEventListener('error', this.onXHRError_.bind(this));
-      client.open('GET', this.src_);
-      client.send();
-    }
-  }
-};
-
-
-/**
- * @private
- * @param {Event} event The load event.
- */
-ol.source.TileUTFGrid.Tile_.prototype.onXHRLoad_ = function(event) {
-  var client = /** @type {XMLHttpRequest} */ (event.target);
-  // status will be 0 for file:// urls
-  if (!client.status || client.status >= 200 && client.status < 300) {
-    var response;
-    try {
-      response = /** @type {!UTFGridJSON} */(JSON.parse(client.responseText));
-    } catch (err) {
-      this.handleError_();
-      return;
-    }
-    this.handleLoad_(response);
-  } else {
-    this.handleError_();
-  }
-};
-
-
-/**
- * @private
- * @param {Event} event The error event.
- */
-ol.source.TileUTFGrid.Tile_.prototype.onXHRError_ = function(event) {
-  this.handleError_();
-};
-
-
-/**
- * Load not yet loaded URI.
- */
-ol.source.TileUTFGrid.Tile_.prototype.load = function() {
-  if (this.preemptive_) {
-    this.loadInternal_();
-  }
-};
-
-// FIXME add minZoom support
-// FIXME add date line wrap (tile coord transform)
-// FIXME cannot be shared between maps with different projections
-
-goog.provide('ol.source.TileWMS');
-
-goog.require('ol');
-goog.require('ol.asserts');
-goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.math');
-goog.require('ol.proj');
-goog.require('ol.size');
-goog.require('ol.source.TileImage');
-goog.require('ol.source.WMSServerType');
-goog.require('ol.tilecoord');
-goog.require('ol.string');
-goog.require('ol.uri');
-
-/**
- * @classdesc
- * Layer source for tile data from WMS servers.
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options.
- * @api stable
- */
-ol.source.TileWMS = function(opt_options) {
-
-  var options = opt_options || {};
-
-  var params = options.params || {};
-
-  var transparent = 'TRANSPARENT' in params ? params['TRANSPARENT'] : true;
-
-  ol.source.TileImage.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    opaque: !transparent,
-    projection: options.projection,
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileGrid: options.tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    url: options.url,
-    urls: options.urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : true
-  });
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.gutter_ = options.gutter !== undefined ? options.gutter : 0;
-
-  /**
-   * @private
-   * @type {!Object}
-   */
-  this.params_ = params;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.v13_ = true;
-
-  /**
-   * @private
-   * @type {ol.source.WMSServerType|undefined}
-   */
-  this.serverType_ =
-      /** @type {ol.source.WMSServerType|undefined} */ (options.serverType);
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.coordKeyPrefix_ = '';
-  this.resetCoordKeyPrefix_();
-
-  /**
-   * @private
-   * @type {ol.Extent}
-   */
-  this.tmpExtent_ = ol.extent.createEmpty();
-
-  this.updateV13_();
-  this.setKey(this.getKeyForParams_());
-
-};
-ol.inherits(ol.source.TileWMS, ol.source.TileImage);
-
-
-/**
- * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
- * projection. Return `undefined` if the GetFeatureInfo URL cannot be
- * constructed.
- * @param {ol.Coordinate} coordinate Coordinate.
- * @param {number} resolution Resolution.
- * @param {ol.ProjectionLike} projection Projection.
- * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
- *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
- *     in the `LAYERS` parameter will be used. `VERSION` should not be
- *     specified here.
- * @return {string|undefined} GetFeatureInfo URL.
- * @api stable
- */
-ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) {
-
-  ol.DEBUG && console.assert(!('VERSION' in params),
-      'key VERSION is not allowed in params');
-
-  var projectionObj = ol.proj.get(projection);
-
-  var tileGrid = this.getTileGrid();
-  if (!tileGrid) {
-    tileGrid = this.getTileGridForProjection(projectionObj);
-  }
-
-  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(
-      coordinate, resolution);
-
-  if (tileGrid.getResolutions().length <= tileCoord[0]) {
-    return undefined;
-  }
-
-  var tileResolution = tileGrid.getResolution(tileCoord[0]);
-  var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_);
-  var tileSize = ol.size.toSize(
-      tileGrid.getTileSize(tileCoord[0]), this.tmpSize);
-
-  var gutter = this.gutter_;
-  if (gutter !== 0) {
-    tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize);
-    tileExtent = ol.extent.buffer(tileExtent,
-        tileResolution * gutter, tileExtent);
-  }
-
-  var baseParams = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetFeatureInfo',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true,
-    'QUERY_LAYERS': this.params_['LAYERS']
-  };
-  ol.obj.assign(baseParams, this.params_, params);
-
-  var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution);
-  var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution);
-
-  baseParams[this.v13_ ? 'I' : 'X'] = x;
-  baseParams[this.v13_ ? 'J' : 'Y'] = y;
-
-  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
-      1, projectionObj, baseParams);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.getGutterInternal = function() {
-  return this.gutter_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
-  return this.coordKeyPrefix_ + ol.source.TileImage.prototype.getKeyZXY.call(this, z, x, y);
-};
-
-
-/**
- * Get the user-provided params, i.e. those passed to the constructor through
- * the "params" option, and possibly updated using the updateParams method.
- * @return {Object} Params.
- * @api stable
- */
-ol.source.TileWMS.prototype.getParams = function() {
-  return this.params_;
-};
-
-
-/**
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Size} tileSize Tile size.
- * @param {ol.Extent} tileExtent Tile extent.
- * @param {number} pixelRatio Pixel ratio.
- * @param {ol.proj.Projection} projection Projection.
- * @param {Object} params Params.
- * @return {string|undefined} Request URL.
- * @private
- */
-ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
-        pixelRatio, projection, params) {
-
-  var urls = this.urls;
-  if (!urls) {
-    return undefined;
-  }
-
-  params['WIDTH'] = tileSize[0];
-  params['HEIGHT'] = tileSize[1];
-
-  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();
-
-  if (!('STYLES' in this.params_)) {
-    params['STYLES'] = '';
-  }
-
-  if (pixelRatio != 1) {
-    switch (this.serverType_) {
-      case ol.source.WMSServerType.GEOSERVER:
-        var dpi = (90 * pixelRatio + 0.5) | 0;
-        if ('FORMAT_OPTIONS' in params) {
-          params['FORMAT_OPTIONS'] += ';dpi:' + dpi;
-        } else {
-          params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
-        }
-        break;
-      case ol.source.WMSServerType.MAPSERVER:
-        params['MAP_RESOLUTION'] = 90 * pixelRatio;
-        break;
-      case ol.source.WMSServerType.CARMENTA_SERVER:
-      case ol.source.WMSServerType.QGIS:
-        params['DPI'] = 90 * pixelRatio;
-        break;
-      default:
-        ol.asserts.assert(false, 52); // Unknown `serverType` configured
-        break;
-    }
-  }
-
-  var axisOrientation = projection.getAxisOrientation();
-  var bbox = tileExtent;
-  if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') {
-    var tmp;
-    tmp = tileExtent[0];
-    bbox[0] = tileExtent[1];
-    bbox[1] = tmp;
-    tmp = tileExtent[2];
-    bbox[2] = tileExtent[3];
-    bbox[3] = tmp;
-  }
-  params['BBOX'] = bbox.join(',');
-
-  var url;
-  if (urls.length == 1) {
-    url = urls[0];
-  } else {
-    var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
-    url = urls[index];
-  }
-  return ol.uri.appendParams(url, params);
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.getTilePixelRatio = function(pixelRatio) {
-  return (!this.hidpi_ || this.serverType_ === undefined) ? 1 :
-      /** @type {number} */ (pixelRatio);
-};
-
-
-/**
- * @private
- */
-ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
-  var i = 0;
-  var res = [];
-
-  if (this.urls) {
-    var j, jj;
-    for (j = 0, jj = this.urls.length; j < jj; ++j) {
-      res[i++] = this.urls[j];
-    }
-  }
-
-  this.coordKeyPrefix_ = res.join('#');
-};
-
-
-/**
- * @private
- * @return {string} The key for the current params.
- */
-ol.source.TileWMS.prototype.getKeyForParams_ = function() {
-  var i = 0;
-  var res = [];
-  for (var key in this.params_) {
-    res[i++] = key + '-' + this.params_[key];
-  }
-  return res.join('/');
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) {
-
-  var tileGrid = this.getTileGrid();
-  if (!tileGrid) {
-    tileGrid = this.getTileGridForProjection(projection);
-  }
-
-  if (tileGrid.getResolutions().length <= tileCoord[0]) {
-    return undefined;
-  }
-
-  if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) {
-    pixelRatio = 1;
-  }
-
-  var tileResolution = tileGrid.getResolution(tileCoord[0]);
-  var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_);
-  var tileSize = ol.size.toSize(
-      tileGrid.getTileSize(tileCoord[0]), this.tmpSize);
-
-  var gutter = this.gutter_;
-  if (gutter !== 0) {
-    tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize);
-    tileExtent = ol.extent.buffer(tileExtent,
-        tileResolution * gutter, tileExtent);
-  }
-
-  if (pixelRatio != 1) {
-    tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize);
-  }
-
-  var baseParams = {
-    'SERVICE': 'WMS',
-    'VERSION': ol.DEFAULT_WMS_VERSION,
-    'REQUEST': 'GetMap',
-    'FORMAT': 'image/png',
-    'TRANSPARENT': true
-  };
-  ol.obj.assign(baseParams, this.params_);
-
-  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
-      pixelRatio, projection, baseParams);
-};
-
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.setUrls = function(urls) {
-  ol.source.TileImage.prototype.setUrls.call(this, urls);
-  this.resetCoordKeyPrefix_();
-};
-
-
-/**
- * Update the user-provided params.
- * @param {Object} params Params.
- * @api stable
- */
-ol.source.TileWMS.prototype.updateParams = function(params) {
-  ol.obj.assign(this.params_, params);
-  this.resetCoordKeyPrefix_();
-  this.updateV13_();
-  this.setKey(this.getKeyForParams_());
-};
-
-
-/**
- * @private
- */
-ol.source.TileWMS.prototype.updateV13_ = function() {
-  var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION;
-  this.v13_ = ol.string.compareVersions(version, '1.3') >= 0;
-};
-
-goog.provide('ol.VectorTile');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.dom');
-
-
-/**
- * @constructor
- * @extends {ol.Tile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Tile.State} state State.
- * @param {string} src Data source url.
- * @param {ol.format.Feature} format Feature format.
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
- */
-ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
-
-  ol.Tile.call(this, tileCoord, state);
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D();
-
-  /**
-   * @private
-   * @type {ol.format.Feature}
-   */
-  this.format_ = format;
-
-  /**
-   * @private
-   * @type {Array.<ol.Feature>}
-   */
-  this.features_ = null;
-
-  /**
-   * @private
-   * @type {ol.FeatureLoader}
-   */
-  this.loader_;
-
-  /**
-   * Data projection
-   * @private
-   * @type {ol.proj.Projection}
-   */
-  this.projection_;
-
-  /**
-   * @private
-   * @type {ol.TileReplayState}
-   */
-  this.replayState_ = {
-    dirty: false,
-    renderedRenderOrder: null,
-    renderedRevision: -1,
-    renderedTileRevision: -1,
-    replayGroup: null,
-    skippedFeatures: []
-  };
-
-  /**
-   * @private
-   * @type {ol.TileLoadFunctionType}
-   */
-  this.tileLoadFunction_ = tileLoadFunction;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.url_ = src;
-
-};
-ol.inherits(ol.VectorTile, ol.Tile);
-
-
-/**
- * @return {CanvasRenderingContext2D} The rendering context.
- */
-ol.VectorTile.prototype.getContext = function() {
-  return this.context_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.VectorTile.prototype.getImage = function() {
-  return this.replayState_.renderedTileRevision == -1 ?
-      null : this.context_.canvas;
-};
-
-
-/**
- * Get the feature format assigned for reading this tile's features.
- * @return {ol.format.Feature} Feature format.
- * @api
- */
-ol.VectorTile.prototype.getFormat = function() {
-  return this.format_;
-};
-
-
-/**
- * @return {Array.<ol.Feature>} Features.
- */
-ol.VectorTile.prototype.getFeatures = function() {
-  return this.features_;
-};
-
-
-/**
- * @return {ol.TileReplayState} The replay state.
- */
-ol.VectorTile.prototype.getReplayState = function() {
-  return this.replayState_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.VectorTile.prototype.getKey = function() {
-  return this.url_;
-};
-
-
-/**
- * @return {ol.proj.Projection} Feature projection.
- */
-ol.VectorTile.prototype.getProjection = function() {
-  return this.projection_;
-};
-
-
-/**
- * Load the tile.
- */
-ol.VectorTile.prototype.load = function() {
-  if (this.state == ol.Tile.State.IDLE) {
-    this.setState(ol.Tile.State.LOADING);
-    this.tileLoadFunction_(this, this.url_);
-    this.loader_(null, NaN, null);
-  }
-};
-
-
-/**
- * @param {Array.<ol.Feature>} features Features.
- * @api
- */
-ol.VectorTile.prototype.setFeatures = function(features) {
-  this.features_ = features;
-  this.setState(ol.Tile.State.LOADED);
-};
-
-
-/**
- * Set the projection of the features that were added with {@link #setFeatures}.
- * @param {ol.proj.Projection} projection Feature projection.
- * @api
- */
-ol.VectorTile.prototype.setProjection = function(projection) {
-  this.projection_ = projection;
-};
-
-
-/**
- * @param {ol.Tile.State} tileState Tile state.
- */
-ol.VectorTile.prototype.setState = function(tileState) {
-  this.state = tileState;
-  this.changed();
-};
-
-
-/**
- * Set the feature loader for reading this tile's features.
- * @param {ol.FeatureLoader} loader Feature loader.
- * @api
- */
-ol.VectorTile.prototype.setLoader = function(loader) {
-  this.loader_ = loader;
-};
-
-goog.provide('ol.source.VectorTile');
-
-goog.require('ol');
-goog.require('ol.Tile');
-goog.require('ol.VectorTile');
-goog.require('ol.events');
-goog.require('ol.events.EventType');
-goog.require('ol.featureloader');
-goog.require('ol.size');
-goog.require('ol.source.UrlTile');
-
-
-/**
- * @classdesc
- * Class for layer sources providing vector data divided into a tile grid, to be
- * used with {@link ol.layer.VectorTile}. Although this source receives tiles
- * with vector features from the server, it is not meant for feature editing.
- * Features are optimized for rendering, their geometries are clipped at or near
- * tile boundaries and simplified for a view resolution. See
- * {@link ol.source.Vector} for vector sources that are suitable for feature
- * editing.
- *
- * @constructor
- * @fires ol.source.Tile.Event
- * @extends {ol.source.UrlTile}
- * @param {olx.source.VectorTileOptions} options Vector tile options.
- * @api
- */
-ol.source.VectorTile = function(options) {
-
-  ol.source.UrlTile.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize !== undefined ? options.cacheSize : 128,
-    extent: options.extent,
-    logo: options.logo,
-    opaque: false,
-    projection: options.projection,
-    state: options.state,
-    tileGrid: options.tileGrid,
-    tileLoadFunction: options.tileLoadFunction ?
-        options.tileLoadFunction : ol.source.VectorTile.defaultTileLoadFunction,
-    tileUrlFunction: options.tileUrlFunction,
-    tilePixelRatio: options.tilePixelRatio,
-    url: options.url,
-    urls: options.urls,
-    wrapX: options.wrapX === undefined ? true : options.wrapX
-  });
-
-  /**
-   * @private
-   * @type {ol.format.Feature}
-   */
-  this.format_ = options.format ? options.format : null;
-
-  /**
-   * @private
-   * @type {boolean}
-   */
-  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;
-
-  /**
-   * @protected
-   * @type {function(new: ol.VectorTile, ol.TileCoord, ol.Tile.State, string,
-   *        ol.format.Feature, ol.TileLoadFunctionType)}
-   */
-  this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile;
-
-};
-ol.inherits(ol.source.VectorTile, ol.source.UrlTile);
-
-
-/**
- * @return {boolean} The source can have overlapping geometries.
- */
-ol.source.VectorTile.prototype.getOverlaps = function() {
-  return this.overlaps_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projection) {
-  var tileCoordKey = this.getKeyZXY(z, x, y);
-  if (this.tileCache.containsKey(tileCoordKey)) {
-    return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
-  } else {
-    var tileCoord = [z, x, y];
-    var urlTileCoord = this.getTileCoordForTileUrlFunction(
-        tileCoord, projection);
-    var tileUrl = urlTileCoord ?
-        this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
-    var tile = new this.tileClass(
-        tileCoord,
-        tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY,
-        tileUrl !== undefined ? tileUrl : '',
-        this.format_, this.tileLoadFunction);
-    ol.events.listen(tile, ol.events.EventType.CHANGE,
-        this.handleTileChange, this);
-
-    this.tileCache.set(tileCoordKey, tile);
-    return tile;
-  }
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
-  return opt_pixelRatio == undefined ?
-      ol.source.UrlTile.prototype.getTilePixelRatio.call(this, opt_pixelRatio) :
-      opt_pixelRatio;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) {
-  var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z));
-  return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)];
-};
-
-
-/**
- * @param {ol.VectorTile} vectorTile Vector tile.
- * @param {string} url URL.
- */
-ol.source.VectorTile.defaultTileLoadFunction = function(vectorTile, url) {
-  vectorTile.setLoader(ol.featureloader.tile(url, vectorTile.getFormat()));
-};
-
-goog.provide('ol.tilegrid.WMTS');
-
-goog.require('ol');
-goog.require('ol.proj');
-goog.require('ol.tilegrid.TileGrid');
-
-
-/**
- * @classdesc
- * Set the grid pattern for sources accessing WMTS tiled-image servers.
- *
- * @constructor
- * @extends {ol.tilegrid.TileGrid}
- * @param {olx.tilegrid.WMTSOptions} options WMTS options.
- * @struct
- * @api
- */
-ol.tilegrid.WMTS = function(options) {
-
-  ol.DEBUG && console.assert(
-      options.resolutions.length == options.matrixIds.length,
-      'options resolutions and matrixIds must have equal length (%s == %s)',
-      options.resolutions.length, options.matrixIds.length);
-
-  /**
-   * @private
-   * @type {!Array.<string>}
-   */
-  this.matrixIds_ = options.matrixIds;
-  // FIXME: should the matrixIds become optionnal?
-
-  ol.tilegrid.TileGrid.call(this, {
-    extent: options.extent,
-    origin: options.origin,
-    origins: options.origins,
-    resolutions: options.resolutions,
-    tileSize: options.tileSize,
-    tileSizes: options.tileSizes,
-    sizes: options.sizes
-  });
-
-};
-ol.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
-
-
-/**
- * @param {number} z Z.
- * @return {string} MatrixId..
- */
-ol.tilegrid.WMTS.prototype.getMatrixId = function(z) {
-  ol.DEBUG && console.assert(0 <= z && z < this.matrixIds_.length,
-      'attempted to retrieve matrixId for illegal z (%s)', z);
-  return this.matrixIds_[z];
-};
-
-
-/**
- * Get the list of matrix identifiers.
- * @return {Array.<string>} MatrixIds.
- * @api
- */
-ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
-  return this.matrixIds_;
-};
-
-
-/**
- * Create a tile grid from a WMTS capabilities matrix set.
- * @param {Object} matrixSet An object representing a matrixSet in the
- *     capabilities document.
- * @param {ol.Extent=} opt_extent An optional extent to restrict the tile
- *     ranges the server provides.
- * @return {ol.tilegrid.WMTS} WMTS tileGrid instance.
- * @api
- */
-ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_extent) {
-
-  /** @type {!Array.<number>} */
-  var resolutions = [];
-  /** @type {!Array.<string>} */
-  var matrixIds = [];
-  /** @type {!Array.<ol.Coordinate>} */
-  var origins = [];
-  /** @type {!Array.<ol.Size>} */
-  var tileSizes = [];
-  /** @type {!Array.<ol.Size>} */
-  var sizes = [];
-
-  var supportedCRSPropName = 'SupportedCRS';
-  var matrixIdsPropName = 'TileMatrix';
-  var identifierPropName = 'Identifier';
-  var scaleDenominatorPropName = 'ScaleDenominator';
-  var topLeftCornerPropName = 'TopLeftCorner';
-  var tileWidthPropName = 'TileWidth';
-  var tileHeightPropName = 'TileHeight';
-
-  var projection;
-  projection = ol.proj.get(matrixSet[supportedCRSPropName].replace(
-      /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'));
-  var metersPerUnit = projection.getMetersPerUnit();
-  // swap origin x and y coordinates if axis orientation is lat/long
-  var switchOriginXY = projection.getAxisOrientation().substr(0, 2) == 'ne';
-
-  matrixSet[matrixIdsPropName].sort(function(a, b) {
-    return b[scaleDenominatorPropName] - a[scaleDenominatorPropName];
-  });
-
-  matrixSet[matrixIdsPropName].forEach(function(elt, index, array) {
-    matrixIds.push(elt[identifierPropName]);
-    var resolution = elt[scaleDenominatorPropName] * 0.28E-3 / metersPerUnit;
-    var tileWidth = elt[tileWidthPropName];
-    var tileHeight = elt[tileHeightPropName];
-    if (switchOriginXY) {
-      origins.push([elt[topLeftCornerPropName][1],
-        elt[topLeftCornerPropName][0]]);
-    } else {
-      origins.push(elt[topLeftCornerPropName]);
-    }
-    resolutions.push(resolution);
-    tileSizes.push(tileWidth == tileHeight ?
-        tileWidth : [tileWidth, tileHeight]);
-    // top-left origin, so height is negative
-    sizes.push([elt['MatrixWidth'], -elt['MatrixHeight']]);
-  });
-
-  return new ol.tilegrid.WMTS({
-    extent: opt_extent,
-    origins: origins,
-    resolutions: resolutions,
-    matrixIds: matrixIds,
-    tileSizes: tileSizes,
-    sizes: sizes
-  });
-};
-
-goog.provide('ol.source.WMTS');
-
-goog.require('ol');
-goog.require('ol.TileUrlFunction');
-goog.require('ol.array');
-goog.require('ol.extent');
-goog.require('ol.obj');
-goog.require('ol.proj');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilegrid.WMTS');
-goog.require('ol.uri');
-
-
-/**
- * @classdesc
- * Layer source for tile data from WMTS servers.
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.WMTSOptions} options WMTS options.
- * @api stable
- */
-ol.source.WMTS = function(options) {
-
-  // TODO: add support for TileMatrixLimits
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.version_ = options.version !== undefined ? options.version : '1.0.0';
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.format_ = options.format !== undefined ? options.format : 'image/jpeg';
-
-  /**
-   * @private
-   * @type {!Object}
-   */
-  this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {};
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.layer_ = options.layer;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.matrixSet_ = options.matrixSet;
-
-  /**
-   * @private
-   * @type {string}
-   */
-  this.style_ = options.style;
-
-  var urls = options.urls;
-  if (urls === undefined && options.url !== undefined) {
-    urls = ol.TileUrlFunction.expandUrl(options.url);
-  }
-
-  // FIXME: should we guess this requestEncoding from options.url(s)
-  //        structure? that would mean KVP only if a template is not provided.
-
-  /**
-   * @private
-   * @type {ol.source.WMTS.RequestEncoding}
-   */
-  this.requestEncoding_ = options.requestEncoding !== undefined ?
-      /** @type {ol.source.WMTS.RequestEncoding} */ (options.requestEncoding) :
-      ol.source.WMTS.RequestEncoding.KVP;
-
-  var requestEncoding = this.requestEncoding_;
-
-  // FIXME: should we create a default tileGrid?
-  // we could issue a getCapabilities xhr to retrieve missing configuration
-  var tileGrid = options.tileGrid;
-
-  // context property names are lower case to allow for a case insensitive
-  // replacement as some services use different naming conventions
-  var context = {
-    'layer': this.layer_,
-    'style': this.style_,
-    'tilematrixset': this.matrixSet_
-  };
-
-  if (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) {
-    ol.obj.assign(context, {
-      'Service': 'WMTS',
-      'Request': 'GetTile',
-      'Version': this.version_,
-      'Format': this.format_
-    });
-  }
-
-  var dimensions = this.dimensions_;
-
-  /**
-   * @param {string} template Template.
-   * @return {ol.TileUrlFunctionType} Tile URL function.
-   */
-  function createFromWMTSTemplate(template) {
-
-    // TODO: we may want to create our own appendParams function so that params
-    // order conforms to wmts spec guidance, and so that we can avoid to escape
-    // special template params
-
-    template = (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) ?
-        ol.uri.appendParams(template, context) :
-        template.replace(/\{(\w+?)\}/g, function(m, p) {
-          return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m;
-        });
-
-    return (
-        /**
-         * @param {ol.TileCoord} tileCoord Tile coordinate.
-         * @param {number} pixelRatio Pixel ratio.
-         * @param {ol.proj.Projection} projection Projection.
-         * @return {string|undefined} Tile URL.
-         */
-        function(tileCoord, pixelRatio, projection) {
-          if (!tileCoord) {
-            return undefined;
-          } else {
-            var localContext = {
-              'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
-              'TileCol': tileCoord[1],
-              'TileRow': -tileCoord[2] - 1
-            };
-            ol.obj.assign(localContext, dimensions);
-            var url = template;
-            if (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) {
-              url = ol.uri.appendParams(url, localContext);
-            } else {
-              url = url.replace(/\{(\w+?)\}/g, function(m, p) {
-                return localContext[p];
-              });
-            }
-            return url;
-          }
-        });
-  }
-
-  var tileUrlFunction = (urls && urls.length > 0) ?
-      ol.TileUrlFunction.createFromTileUrlFunctions(
-          urls.map(createFromWMTSTemplate)) :
-      ol.TileUrlFunction.nullTileUrlFunction;
-
-  ol.source.TileImage.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    projection: options.projection,
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileClass: options.tileClass,
-    tileGrid: tileGrid,
-    tileLoadFunction: options.tileLoadFunction,
-    tilePixelRatio: options.tilePixelRatio,
-    tileUrlFunction: tileUrlFunction,
-    urls: urls,
-    wrapX: options.wrapX !== undefined ? options.wrapX : false
-  });
-
-  this.setKey(this.getKeyForDimensions_());
-
-};
-ol.inherits(ol.source.WMTS, ol.source.TileImage);
-
-
-/**
- * Get the dimensions, i.e. those passed to the constructor through the
- * "dimensions" option, and possibly updated using the updateDimensions
- * method.
- * @return {!Object} Dimensions.
- * @api
- */
-ol.source.WMTS.prototype.getDimensions = function() {
-  return this.dimensions_;
-};
-
-
-/**
- * Return the image format of the WMTS source.
- * @return {string} Format.
- * @api
- */
-ol.source.WMTS.prototype.getFormat = function() {
-  return this.format_;
-};
-
-
-/**
- * Return the layer of the WMTS source.
- * @return {string} Layer.
- * @api
- */
-ol.source.WMTS.prototype.getLayer = function() {
-  return this.layer_;
-};
-
-
-/**
- * Return the matrix set of the WMTS source.
- * @return {string} MatrixSet.
- * @api
- */
-ol.source.WMTS.prototype.getMatrixSet = function() {
-  return this.matrixSet_;
-};
-
-
-/**
- * Return the request encoding, either "KVP" or "REST".
- * @return {ol.source.WMTS.RequestEncoding} Request encoding.
- * @api
- */
-ol.source.WMTS.prototype.getRequestEncoding = function() {
-  return this.requestEncoding_;
-};
-
-
-/**
- * Return the style of the WMTS source.
- * @return {string} Style.
- * @api
- */
-ol.source.WMTS.prototype.getStyle = function() {
-  return this.style_;
-};
-
-
-/**
- * Return the version of the WMTS source.
- * @return {string} Version.
- * @api
- */
-ol.source.WMTS.prototype.getVersion = function() {
-  return this.version_;
-};
-
-
-/**
- * @private
- * @return {string} The key for the current dimensions.
- */
-ol.source.WMTS.prototype.getKeyForDimensions_ = function() {
-  var i = 0;
-  var res = [];
-  for (var key in this.dimensions_) {
-    res[i++] = key + '-' + this.dimensions_[key];
-  }
-  return res.join('/');
-};
-
-
-/**
- * Update the dimensions.
- * @param {Object} dimensions Dimensions.
- * @api
- */
-ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
-  ol.obj.assign(this.dimensions_, dimensions);
-  this.setKey(this.getKeyForDimensions_());
-};
-
-
-/**
- * Generate source options from a capabilities object.
- * @param {Object} wmtsCap An object representing the capabilities document.
- * @param {Object} config Configuration properties for the layer.  Defaults for
- *                  the layer will apply if not provided.
- *
- * Required config properties:
- *  - layer - {string} The layer identifier.
- *
- * Optional config properties:
- *  - matrixSet - {string} The matrix set identifier, required if there is
- *       more than one matrix set in the layer capabilities.
- *  - projection - {string} The desired CRS when no matrixSet is specified.
- *       eg: "EPSG:3857". If the desired projection is not available,
- *       an error is thrown.
- *  - requestEncoding - {string} url encoding format for the layer. Default is
- *       the first tile url format found in the GetCapabilities response.
- *  - style - {string} The name of the style
- *  - format - {string} Image format for the layer. Default is the first
- *       format returned in the GetCapabilities response.
- * @return {olx.source.WMTSOptions} WMTS source options object.
- * @api
- */
-ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
-
-  // TODO: add support for TileMatrixLimits
-  ol.DEBUG && console.assert(config['layer'],
-      'config "layer" must not be null');
-
-  var layers = wmtsCap['Contents']['Layer'];
-  var l = ol.array.find(layers, function(elt, index, array) {
-    return elt['Identifier'] == config['layer'];
-  });
-  ol.DEBUG && console.assert(l, 'found a matching layer in Contents/Layer');
-
-  ol.DEBUG && console.assert(l['TileMatrixSetLink'].length > 0,
-      'layer has TileMatrixSetLink');
-  var tileMatrixSets = wmtsCap['Contents']['TileMatrixSet'];
-  var idx, matrixSet;
-  if (l['TileMatrixSetLink'].length > 1) {
-    if ('projection' in config) {
-      idx = ol.array.findIndex(l['TileMatrixSetLink'],
-          function(elt, index, array) {
-            var tileMatrixSet = ol.array.find(tileMatrixSets, function(el) {
-              return el['Identifier'] == elt['TileMatrixSet'];
-            });
-            return tileMatrixSet['SupportedCRS'].replace(
-                /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'
-                   ) == config['projection'];
-          });
-    } else {
-      idx = ol.array.findIndex(l['TileMatrixSetLink'],
-          function(elt, index, array) {
-            return elt['TileMatrixSet'] == config['matrixSet'];
-          });
-    }
-  } else {
-    idx = 0;
-  }
-  if (idx < 0) {
-    idx = 0;
-  }
-  matrixSet = /** @type {string} */
-      (l['TileMatrixSetLink'][idx]['TileMatrixSet']);
-
-  ol.DEBUG && console.assert(matrixSet, 'TileMatrixSet must not be null');
-
-  var format = /** @type {string} */ (l['Format'][0]);
-  if ('format' in config) {
-    format = config['format'];
-  }
-  idx = ol.array.findIndex(l['Style'], function(elt, index, array) {
-    if ('style' in config) {
-      return elt['Title'] == config['style'];
-    } else {
-      return elt['isDefault'];
-    }
-  });
-  if (idx < 0) {
-    idx = 0;
-  }
-  var style = /** @type {string} */ (l['Style'][idx]['Identifier']);
-
-  var dimensions = {};
-  if ('Dimension' in l) {
-    l['Dimension'].forEach(function(elt, index, array) {
-      var key = elt['Identifier'];
-      var value = elt['Default'];
-      if (value !== undefined) {
-        ol.DEBUG && console.assert(ol.array.includes(elt['Value'], value),
-            'default value contained in values');
-      } else {
-        value = elt['Value'][0];
-      }
-      ol.DEBUG && console.assert(value !== undefined, 'value could be found');
-      dimensions[key] = value;
-    });
-  }
-
-  var matrixSets = wmtsCap['Contents']['TileMatrixSet'];
-  var matrixSetObj = ol.array.find(matrixSets, function(elt, index, array) {
-    return elt['Identifier'] == matrixSet;
-  });
-  ol.DEBUG && console.assert(matrixSetObj,
-      'found matrixSet in Contents/TileMatrixSet');
-
-  var projection;
-  if ('projection' in config) {
-    projection = ol.proj.get(config['projection']);
-  } else {
-    projection = ol.proj.get(matrixSetObj['SupportedCRS'].replace(
-        /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'));
-  }
-
-  var wgs84BoundingBox = l['WGS84BoundingBox'];
-  var extent, wrapX;
-  if (wgs84BoundingBox !== undefined) {
-    var wgs84ProjectionExtent = ol.proj.get('EPSG:4326').getExtent();
-    wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] &&
-        wgs84BoundingBox[2] == wgs84ProjectionExtent[2]);
-    extent = ol.proj.transformExtent(
-        wgs84BoundingBox, 'EPSG:4326', projection);
-    var projectionExtent = projection.getExtent();
-    if (projectionExtent) {
-      // If possible, do a sanity check on the extent - it should never be
-      // bigger than the validity extent of the projection of a matrix set.
-      if (!ol.extent.containsExtent(projectionExtent, extent)) {
-        extent = undefined;
-      }
-    }
-  }
-
-  var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet(
-      matrixSetObj, extent);
-
-  /** @type {!Array.<string>} */
-  var urls = [];
-  var requestEncoding = config['requestEncoding'];
-  requestEncoding = requestEncoding !== undefined ? requestEncoding : '';
-
-  ol.DEBUG && console.assert(
-      ol.array.includes(['REST', 'RESTful', 'KVP', ''], requestEncoding),
-      'requestEncoding (%s) is one of "REST", "RESTful", "KVP" or ""',
-      requestEncoding);
-
-  if ('OperationsMetadata' in wmtsCap && 'GetTile' in wmtsCap['OperationsMetadata']) {
-    var gets = wmtsCap['OperationsMetadata']['GetTile']['DCP']['HTTP']['Get'];
-    ol.DEBUG && console.assert(gets.length >= 1);
-
-    for (var i = 0, ii = gets.length; i < ii; ++i) {
-      var constraint = ol.array.find(gets[i]['Constraint'], function(element) {
-        return element['name'] == 'GetEncoding';
-      });
-      var encodings = constraint['AllowedValues']['Value'];
-      ol.DEBUG && console.assert(encodings.length >= 1);
-
-      if (requestEncoding === '') {
-        // requestEncoding not provided, use the first encoding from the list
-        requestEncoding = encodings[0];
-      }
-      if (requestEncoding === ol.source.WMTS.RequestEncoding.KVP) {
-        if (ol.array.includes(encodings, ol.source.WMTS.RequestEncoding.KVP)) {
-          urls.push(/** @type {string} */ (gets[i]['href']));
-        }
-      } else {
-        break;
-      }
-    }
-  }
-  if (urls.length === 0) {
-    requestEncoding = ol.source.WMTS.RequestEncoding.REST;
-    l['ResourceURL'].forEach(function(element) {
-      if (element['resourceType'] === 'tile') {
-        format = element['format'];
-        urls.push(/** @type {string} */ (element['template']));
-      }
-    });
-  }
-  ol.DEBUG && console.assert(urls.length > 0, 'At least one URL found');
-
-  return {
-    urls: urls,
-    layer: config['layer'],
-    matrixSet: matrixSet,
-    format: format,
-    projection: projection,
-    requestEncoding: requestEncoding,
-    tileGrid: tileGrid,
-    style: style,
-    dimensions: dimensions,
-    wrapX: wrapX
-  };
-
-};
-
-
-/**
- * Request encoding. One of 'KVP', 'REST'.
- * @enum {string}
- */
-ol.source.WMTS.RequestEncoding = {
-  KVP: 'KVP',  // see spec §8
-  REST: 'REST' // see spec §10
-};
-
-goog.provide('ol.source.Zoomify');
-
-goog.require('ol');
-goog.require('ol.ImageTile');
-goog.require('ol.Tile');
-goog.require('ol.asserts');
-goog.require('ol.dom');
-goog.require('ol.extent');
-goog.require('ol.source.TileImage');
-goog.require('ol.tilegrid.TileGrid');
-
-
-/**
- * @classdesc
- * Layer source for tile data in Zoomify format.
- *
- * @constructor
- * @extends {ol.source.TileImage}
- * @param {olx.source.ZoomifyOptions=} opt_options Options.
- * @api stable
- */
-ol.source.Zoomify = function(opt_options) {
-
-  var options = opt_options || {};
-
-  var size = options.size;
-  var tierSizeCalculation = options.tierSizeCalculation !== undefined ?
-      options.tierSizeCalculation :
-      ol.source.Zoomify.TierSizeCalculation.DEFAULT;
-
-  var imageWidth = size[0];
-  var imageHeight = size[1];
-  var tierSizeInTiles = [];
-  var tileSize = ol.DEFAULT_TILE_SIZE;
-
-  switch (tierSizeCalculation) {
-    case ol.source.Zoomify.TierSizeCalculation.DEFAULT:
-      while (imageWidth > tileSize || imageHeight > tileSize) {
-        tierSizeInTiles.push([
-          Math.ceil(imageWidth / tileSize),
-          Math.ceil(imageHeight / tileSize)
-        ]);
-        tileSize += tileSize;
-      }
-      break;
-    case ol.source.Zoomify.TierSizeCalculation.TRUNCATED:
-      var width = imageWidth;
-      var height = imageHeight;
-      while (width > tileSize || height > tileSize) {
-        tierSizeInTiles.push([
-          Math.ceil(width / tileSize),
-          Math.ceil(height / tileSize)
-        ]);
-        width >>= 1;
-        height >>= 1;
-      }
-      break;
-    default:
-      ol.asserts.assert(false, 53); // Unknown `tierSizeCalculation` configured
-      break;
-  }
-
-  tierSizeInTiles.push([1, 1]);
-  tierSizeInTiles.reverse();
-
-  var resolutions = [1];
-  var tileCountUpToTier = [0];
-  var i, ii;
-  for (i = 1, ii = tierSizeInTiles.length; i < ii; i++) {
-    resolutions.push(1 << i);
-    tileCountUpToTier.push(
-        tierSizeInTiles[i - 1][0] * tierSizeInTiles[i - 1][1] +
-        tileCountUpToTier[i - 1]
-    );
-  }
-  resolutions.reverse();
-
-  var extent = [0, -size[1], size[0], 0];
-  var tileGrid = new ol.tilegrid.TileGrid({
-    extent: extent,
-    origin: ol.extent.getTopLeft(extent),
-    resolutions: resolutions
-  });
-
-  var url = options.url;
-
-  /**
-   * @this {ol.source.TileImage}
-   * @param {ol.TileCoord} tileCoord Tile Coordinate.
-   * @param {number} pixelRatio Pixel ratio.
-   * @param {ol.proj.Projection} projection Projection.
-   * @return {string|undefined} Tile URL.
-   */
-  function tileUrlFunction(tileCoord, pixelRatio, projection) {
-    if (!tileCoord) {
-      return undefined;
-    } else {
-      var tileCoordZ = tileCoord[0];
-      var tileCoordX = tileCoord[1];
-      var tileCoordY = -tileCoord[2] - 1;
-      var tileIndex =
-          tileCoordX +
-          tileCoordY * tierSizeInTiles[tileCoordZ][0] +
-          tileCountUpToTier[tileCoordZ];
-      var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0;
-      return url + 'TileGroup' + tileGroup + '/' +
-          tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg';
-    }
-  }
-
-  ol.source.TileImage.call(this, {
-    attributions: options.attributions,
-    cacheSize: options.cacheSize,
-    crossOrigin: options.crossOrigin,
-    logo: options.logo,
-    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
-    tileClass: ol.source.Zoomify.Tile_,
-    tileGrid: tileGrid,
-    tileUrlFunction: tileUrlFunction
-  });
-
-};
-ol.inherits(ol.source.Zoomify, ol.source.TileImage);
-
-
-/**
- * @constructor
- * @extends {ol.ImageTile}
- * @param {ol.TileCoord} tileCoord Tile coordinate.
- * @param {ol.Tile.State} state State.
- * @param {string} src Image source URI.
- * @param {?string} crossOrigin Cross origin.
- * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
- * @private
- */
-ol.source.Zoomify.Tile_ = function(
-    tileCoord, state, src, crossOrigin, tileLoadFunction) {
-
-  ol.ImageTile.call(this, tileCoord, state, src, crossOrigin, tileLoadFunction);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement}
-   */
-  this.zoomifyImage_ = null;
-
-};
-ol.inherits(ol.source.Zoomify.Tile_, ol.ImageTile);
-
-
-/**
- * @inheritDoc
- */
-ol.source.Zoomify.Tile_.prototype.getImage = function() {
-  if (this.zoomifyImage_) {
-    return this.zoomifyImage_;
-  }
-  var tileSize = ol.DEFAULT_TILE_SIZE;
-  var image = ol.ImageTile.prototype.getImage.call(this);
-  if (this.state == ol.Tile.State.LOADED) {
-    if (image.width == tileSize && image.height == tileSize) {
-      this.zoomifyImage_ = image;
-      return image;
-    } else {
-      var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
-      context.drawImage(image, 0, 0);
-      this.zoomifyImage_ = context.canvas;
-      return context.canvas;
-    }
-  } else {
-    return image;
-  }
-};
-
-
-/**
- * @enum {string}
- */
-ol.source.Zoomify.TierSizeCalculation = {
-  DEFAULT: 'default',
-  TRUNCATED: 'truncated'
-};
-
-goog.provide('ol.style.Atlas');
-
-goog.require('ol');
-goog.require('ol.dom');
-
-
-/**
- * This class facilitates the creation of image atlases.
- *
- * Images added to an atlas will be rendered onto a single
- * atlas canvas. The distribution of images on the canvas is
- * managed with the bin packing algorithm described in:
- * http://www.blackpawn.com/texts/lightmaps/
- *
- * @constructor
- * @struct
- * @param {number} size The size in pixels of the sprite image.
- * @param {number} space The space in pixels between images.
- *    Because texture coordinates are float values, the edges of
- *    images might not be completely correct (in a way that the
- *    edges overlap when being rendered). To avoid this we add a
- *    padding around each image.
- */
-ol.style.Atlas = function(size, space) {
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.space_ = space;
-
-  /**
-   * @private
-   * @type {Array.<ol.AtlasBlock>}
-   */
-  this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
-
-  /**
-   * @private
-   * @type {Object.<string, ol.AtlasInfo>}
-   */
-  this.entries_ = {};
-
-  /**
-   * @private
-   * @type {CanvasRenderingContext2D}
-   */
-  this.context_ = ol.dom.createCanvasContext2D(size, size);
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = this.context_.canvas;
-};
-
-
-/**
- * @param {string} id The identifier of the entry to check.
- * @return {?ol.AtlasInfo} The atlas info.
- */
-ol.style.Atlas.prototype.get = function(id) {
-  return this.entries_[id] || null;
-};
-
-
-/**
- * @param {string} id The identifier of the entry to add.
- * @param {number} width The width.
- * @param {number} height The height.
- * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
- *    Called to render the new image onto an atlas image.
- * @param {Object=} opt_this Value to use as `this` when executing
- *    `renderCallback`.
- * @return {?ol.AtlasInfo} The position and atlas image for the entry.
- */
-ol.style.Atlas.prototype.add = function(id, width, height, renderCallback, opt_this) {
-  var block, i, ii;
-  for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) {
-    block = this.emptyBlocks_[i];
-    if (block.width >= width + this.space_ &&
-        block.height >= height + this.space_) {
-      // we found a block that is big enough for our entry
-      var entry = {
-        offsetX: block.x + this.space_,
-        offsetY: block.y + this.space_,
-        image: this.canvas_
-      };
-      this.entries_[id] = entry;
-
-      // render the image on the atlas image
-      renderCallback.call(opt_this, this.context_,
-          block.x + this.space_, block.y + this.space_);
-
-      // split the block after the insertion, either horizontally or vertically
-      this.split_(i, block, width + this.space_, height + this.space_);
-
-      return entry;
-    }
-  }
-
-  // there is no space for the new entry in this atlas
-  return null;
-};
-
-
-/**
- * @private
- * @param {number} index The index of the block.
- * @param {ol.AtlasBlock} block The block to split.
- * @param {number} width The width of the entry to insert.
- * @param {number} height The height of the entry to insert.
- */
-ol.style.Atlas.prototype.split_ = function(index, block, width, height) {
-  var deltaWidth = block.width - width;
-  var deltaHeight = block.height - height;
-
-  /** @type {ol.AtlasBlock} */
-  var newBlock1;
-  /** @type {ol.AtlasBlock} */
-  var newBlock2;
-
-  if (deltaWidth > deltaHeight) {
-    // split vertically
-    // block right of the inserted entry
-    newBlock1 = {
-      x: block.x + width,
-      y: block.y,
-      width: block.width - width,
-      height: block.height
-    };
-
-    // block below the inserted entry
-    newBlock2 = {
-      x: block.x,
-      y: block.y + height,
-      width: width,
-      height: block.height - height
-    };
-    this.updateBlocks_(index, newBlock1, newBlock2);
-  } else {
-    // split horizontally
-    // block right of the inserted entry
-    newBlock1 = {
-      x: block.x + width,
-      y: block.y,
-      width: block.width - width,
-      height: height
-    };
-
-    // block below the inserted entry
-    newBlock2 = {
-      x: block.x,
-      y: block.y + height,
-      width: block.width,
-      height: block.height - height
-    };
-    this.updateBlocks_(index, newBlock1, newBlock2);
-  }
-};
-
-
-/**
- * Remove the old block and insert new blocks at the same array position.
- * The new blocks are inserted at the same position, so that splitted
- * blocks (that are potentially smaller) are filled first.
- * @private
- * @param {number} index The index of the block to remove.
- * @param {ol.AtlasBlock} newBlock1 The 1st block to add.
- * @param {ol.AtlasBlock} newBlock2 The 2nd block to add.
- */
-ol.style.Atlas.prototype.updateBlocks_ = function(index, newBlock1, newBlock2) {
-  var args = [index, 1];
-  if (newBlock1.width > 0 && newBlock1.height > 0) {
-    args.push(newBlock1);
-  }
-  if (newBlock2.width > 0 && newBlock2.height > 0) {
-    args.push(newBlock2);
-  }
-  this.emptyBlocks_.splice.apply(this.emptyBlocks_, args);
-};
-
-goog.provide('ol.style.AtlasManager');
-
-goog.require('ol');
-goog.require('ol.style.Atlas');
-
-
-/**
- * Manages the creation of image atlases.
- *
- * Images added to this manager will be inserted into an atlas, which
- * will be used for rendering.
- * The `size` given in the constructor is the size for the first
- * atlas. After that, when new atlases are created, they will have
- * twice the size as the latest atlas (until `maxSize` is reached).
- *
- * If an application uses many images or very large images, it is recommended
- * to set a higher `size` value to avoid the creation of too many atlases.
- *
- * @constructor
- * @struct
- * @api
- * @param {olx.style.AtlasManagerOptions=} opt_options Options.
- */
-ol.style.AtlasManager = function(opt_options) {
-
-  var options = opt_options || {};
-
-  /**
-   * The size in pixels of the latest atlas image.
-   * @private
-   * @type {number}
-   */
-  this.currentSize_ = options.initialSize !== undefined ?
-      options.initialSize : ol.INITIAL_ATLAS_SIZE;
-
-  /**
-   * The maximum size in pixels of atlas images.
-   * @private
-   * @type {number}
-   */
-  this.maxSize_ = options.maxSize !== undefined ?
-      options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
-          ol.MAX_ATLAS_SIZE : ol.WEBGL_MAX_TEXTURE_SIZE !== undefined ?
-              ol.WEBGL_MAX_TEXTURE_SIZE : 2048;
-
-  /**
-   * The size in pixels between images.
-   * @private
-   * @type {number}
-   */
-  this.space_ = options.space !== undefined ? options.space : 1;
-
-  /**
-   * @private
-   * @type {Array.<ol.style.Atlas>}
-   */
-  this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
-
-  /**
-   * The size in pixels of the latest atlas image for hit-detection images.
-   * @private
-   * @type {number}
-   */
-  this.currentHitSize_ = this.currentSize_;
-
-  /**
-   * @private
-   * @type {Array.<ol.style.Atlas>}
-   */
-  this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
-};
-
-
-/**
- * @param {string} id The identifier of the entry to check.
- * @return {?ol.AtlasManagerInfo} The position and atlas image for the
- *    entry, or `null` if the entry is not part of the atlas manager.
- */
-ol.style.AtlasManager.prototype.getInfo = function(id) {
-  /** @type {?ol.AtlasInfo} */
-  var info = this.getInfo_(this.atlases_, id);
-
-  if (!info) {
-    return null;
-  }
-  var hitInfo = /** @type {ol.AtlasInfo} */ (this.getInfo_(this.hitAtlases_, id));
-
-  return this.mergeInfos_(info, hitInfo);
-};
-
-
-/**
- * @private
- * @param {Array.<ol.style.Atlas>} atlases The atlases to search.
- * @param {string} id The identifier of the entry to check.
- * @return {?ol.AtlasInfo} The position and atlas image for the entry,
- *    or `null` if the entry is not part of the atlases.
- */
-ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
-  var atlas, info, i, ii;
-  for (i = 0, ii = atlases.length; i < ii; ++i) {
-    atlas = atlases[i];
-    info = atlas.get(id);
-    if (info) {
-      return info;
-    }
-  }
-  return null;
-};
-
-
-/**
- * @private
- * @param {ol.AtlasInfo} info The info for the real image.
- * @param {ol.AtlasInfo} hitInfo The info for the hit-detection
- *    image.
- * @return {?ol.AtlasManagerInfo} The position and atlas image for the
- *    entry, or `null` if the entry is not part of the atlases.
- */
-ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
-  ol.DEBUG && console.assert(info.offsetX === hitInfo.offsetX,
-      'in order to merge, offsetX of info and hitInfo must be equal');
-  ol.DEBUG && console.assert(info.offsetY === hitInfo.offsetY,
-      'in order to merge, offsetY of info and hitInfo must be equal');
-  return /** @type {ol.AtlasManagerInfo} */ ({
-    offsetX: info.offsetX,
-    offsetY: info.offsetY,
-    image: info.image,
-    hitImage: hitInfo.image
-  });
-};
-
-
-/**
- * Add an image to the atlas manager.
- *
- * If an entry for the given id already exists, the entry will
- * be overridden (but the space on the atlas graphic will not be freed).
- *
- * If `renderHitCallback` is provided, the image (or the hit-detection version
- * of the image) will be rendered into a separate hit-detection atlas image.
- *
- * @param {string} id The identifier of the entry to add.
- * @param {number} width The width.
- * @param {number} height The height.
- * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
- *    Called to render the new image onto an atlas image.
- * @param {function(CanvasRenderingContext2D, number, number)=}
- *    opt_renderHitCallback Called to render a hit-detection image onto a hit
- *    detection atlas image.
- * @param {Object=} opt_this Value to use as `this` when executing
- *    `renderCallback` and `renderHitCallback`.
- * @return {?ol.AtlasManagerInfo}  The position and atlas image for the
- *    entry, or `null` if the image is too big.
- */
-ol.style.AtlasManager.prototype.add = function(id, width, height,
-        renderCallback, opt_renderHitCallback, opt_this) {
-  if (width + this.space_ > this.maxSize_ ||
-      height + this.space_ > this.maxSize_) {
-    return null;
-  }
-
-  /** @type {?ol.AtlasInfo} */
-  var info = this.add_(false,
-      id, width, height, renderCallback, opt_this);
-  if (!info) {
-    return null;
-  }
-
-  // even if no hit-detection entry is requested, we insert a fake entry into
-  // the hit-detection atlas, to make sure that the offset is the same for
-  // the original image and the hit-detection image.
-  var renderHitCallback = opt_renderHitCallback !== undefined ?
-      opt_renderHitCallback : ol.nullFunction;
-
-  var hitInfo = /** @type {ol.AtlasInfo} */ (this.add_(true,
-      id, width, height, renderHitCallback, opt_this));
-
-  return this.mergeInfos_(info, hitInfo);
-};
-
-
-/**
- * @private
- * @param {boolean} isHitAtlas If the hit-detection atlases are used.
- * @param {string} id The identifier of the entry to add.
- * @param {number} width The width.
- * @param {number} height The height.
- * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
- *    Called to render the new image onto an atlas image.
- * @param {Object=} opt_this Value to use as `this` when executing
- *    `renderCallback` and `renderHitCallback`.
- * @return {?ol.AtlasInfo}  The position and atlas image for the entry,
- *    or `null` if the image is too big.
- */
-ol.style.AtlasManager.prototype.add_ = function(isHitAtlas, id, width, height,
-        renderCallback, opt_this) {
-  var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_;
-  var atlas, info, i, ii;
-  for (i = 0, ii = atlases.length; i < ii; ++i) {
-    atlas = atlases[i];
-    info = atlas.add(id, width, height, renderCallback, opt_this);
-    if (info) {
-      return info;
-    } else if (!info && i === ii - 1) {
-      // the entry could not be added to one of the existing atlases,
-      // create a new atlas that is twice as big and try to add to this one.
-      var size;
-      if (isHitAtlas) {
-        size = Math.min(this.currentHitSize_ * 2, this.maxSize_);
-        this.currentHitSize_ = size;
-      } else {
-        size = Math.min(this.currentSize_ * 2, this.maxSize_);
-        this.currentSize_ = size;
-      }
-      atlas = new ol.style.Atlas(size, this.space_);
-      atlases.push(atlas);
-      // run the loop another time
-      ++ii;
-    }
-  }
-  ol.DEBUG && console.assert(false, 'Failed to add to atlasmanager');
-  return null;
-};
-
-goog.provide('ol.style.RegularShape');
-
-goog.require('ol');
-goog.require('ol.colorlike');
-goog.require('ol.dom');
-goog.require('ol.has');
-goog.require('ol.Image');
-goog.require('ol.render.canvas');
-goog.require('ol.style.Image');
-
-
-/**
- * @classdesc
- * Set regular shape style for vector features. The resulting shape will be
- * a regular polygon when `radius` is provided, or a star when `radius1` and
- * `radius2` are provided.
- *
- * @constructor
- * @param {olx.style.RegularShapeOptions} options Options.
- * @extends {ol.style.Image}
- * @api
- */
-ol.style.RegularShape = function(options) {
-
-  ol.DEBUG && console.assert(
-      options.radius !== undefined || options.radius1 !== undefined,
-      'must provide either "radius" or "radius1"');
-
-  /**
-   * @private
-   * @type {Array.<string>}
-   */
-  this.checksums_ = null;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.canvas_ = null;
-
-  /**
-   * @private
-   * @type {HTMLCanvasElement}
-   */
-  this.hitDetectionCanvas_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.Fill}
-   */
-  this.fill_ = options.fill !== undefined ? options.fill : null;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.origin_ = [0, 0];
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.points_ = options.points;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.radius_ = /** @type {number} */ (options.radius !== undefined ?
-      options.radius : options.radius1);
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.radius2_ =
-      options.radius2 !== undefined ? options.radius2 : this.radius_;
-
-  /**
-   * @private
-   * @type {number}
-   */
-  this.angle_ = options.angle !== undefined ? options.angle : 0;
-
-  /**
-   * @private
-   * @type {ol.style.Stroke}
-   */
-  this.stroke_ = options.stroke !== undefined ? options.stroke : null;
-
-  /**
-   * @private
-   * @type {Array.<number>}
-   */
-  this.anchor_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.size_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.imageSize_ = null;
-
-  /**
-   * @private
-   * @type {ol.Size}
-   */
-  this.hitDetectionImageSize_ = null;
-
-  /**
-   * @private
-   * @type {ol.style.AtlasManager|undefined}
-   */
-  this.atlasManager_ = options.atlasManager;
-
-  this.render_(this.atlasManager_);
-
-  /**
-   * @type {boolean}
-   */
-  var snapToPixel = options.snapToPixel !== undefined ?
-      options.snapToPixel : true;
-
-  /**
-   * @type {boolean}
-   */
-  var rotateWithView = options.rotateWithView !== undefined ?
-      options.rotateWithView : false;
-
-  ol.style.Image.call(this, {
-    opacity: 1,
-    rotateWithView: rotateWithView,
-    rotation: options.rotation !== undefined ? options.rotation : 0,
-    scale: 1,
-    snapToPixel: snapToPixel
-  });
-
-};
-ol.inherits(ol.style.RegularShape, ol.style.Image);
-
-
-/**
- * Clones the style. If an atlasmanger was provided to the original style it will be used in the cloned style, too.
- * @return {ol.style.RegularShape} The cloned style.
- * @api
- */
-ol.style.RegularShape.prototype.clone = function() {
-  var style = new ol.style.RegularShape({
-    fill: this.getFill() ? this.getFill().clone() : undefined,
-    points: this.getRadius2() !== this.getRadius() ? this.getPoints() / 2 : this.getPoints(),
-    radius: this.getRadius(),
-    radius2: this.getRadius2(),
-    angle: this.getAngle(),
-    snapToPixel: this.getSnapToPixel(),
-    stroke: this.getStroke() ?  this.getStroke().clone() : undefined,
-    rotation: this.getRotation(),
-    rotateWithView: this.getRotateWithView(),
-    atlasManager: this.atlasManager_
-  });
-  style.setOpacity(this.getOpacity());
-  style.setScale(this.getScale());
-  return style;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.style.RegularShape.prototype.getAnchor = function() {
-  return this.anchor_;
-};
-
-
-/**
- * Get the angle used in generating the shape.
- * @return {number} Shape's rotation in radians.
- * @api
- */
-ol.style.RegularShape.prototype.getAngle = function() {
-  return this.angle_;
-};
-
-
-/**
- * Get the fill style for the shape.
- * @return {ol.style.Fill} Fill style.
- * @api
- */
-ol.style.RegularShape.prototype.getFill = function() {
-  return this.fill_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) {
-  return this.hitDetectionCanvas_;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
-  return this.canvas_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.getImageSize = function() {
-  return this.imageSize_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
-  return this.hitDetectionImageSize_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.getImageState = function() {
-  return ol.Image.State.LOADED;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.style.RegularShape.prototype.getOrigin = function() {
-  return this.origin_;
-};
-
-
-/**
- * Get the number of points for generating the shape.
- * @return {number} Number of points for stars and regular polygons.
- * @api
- */
-ol.style.RegularShape.prototype.getPoints = function() {
-  return this.points_;
-};
-
-
-/**
- * Get the (primary) radius for the shape.
- * @return {number} Radius.
- * @api
- */
-ol.style.RegularShape.prototype.getRadius = function() {
-  return this.radius_;
-};
-
-
-/**
- * Get the secondary radius for the shape.
- * @return {number} Radius2.
- * @api
- */
-ol.style.RegularShape.prototype.getRadius2 = function() {
-  return this.radius2_;
-};
-
-
-/**
- * @inheritDoc
- * @api
- */
-ol.style.RegularShape.prototype.getSize = function() {
-  return this.size_;
-};
-
-
-/**
- * Get the stroke style for the shape.
- * @return {ol.style.Stroke} Stroke style.
- * @api
- */
-ol.style.RegularShape.prototype.getStroke = function() {
-  return this.stroke_;
-};
-
-
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.listenImageChange = ol.nullFunction;
-
-
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.load = ol.nullFunction;
-
-
-/**
- * @inheritDoc
- */
-ol.style.RegularShape.prototype.unlistenImageChange = ol.nullFunction;
-
-
-/**
- * @private
- * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
- */
-ol.style.RegularShape.prototype.render_ = function(atlasManager) {
-  var imageSize;
-  var lineCap = '';
-  var lineJoin = '';
-  var miterLimit = 0;
-  var lineDash = null;
-  var strokeStyle;
-  var strokeWidth = 0;
-
-  if (this.stroke_) {
-    strokeStyle = ol.colorlike.asColorLike(this.stroke_.getColor());
-    strokeWidth = this.stroke_.getWidth();
-    if (strokeWidth === undefined) {
-      strokeWidth = ol.render.canvas.defaultLineWidth;
-    }
-    lineDash = this.stroke_.getLineDash();
-    if (!ol.has.CANVAS_LINE_DASH) {
-      lineDash = null;
-    }
-    lineJoin = this.stroke_.getLineJoin();
-    if (lineJoin === undefined) {
-      lineJoin = ol.render.canvas.defaultLineJoin;
-    }
-    lineCap = this.stroke_.getLineCap();
-    if (lineCap === undefined) {
-      lineCap = ol.render.canvas.defaultLineCap;
-    }
-    miterLimit = this.stroke_.getMiterLimit();
-    if (miterLimit === undefined) {
-      miterLimit = ol.render.canvas.defaultMiterLimit;
-    }
-  }
-
-  var size = 2 * (this.radius_ + strokeWidth) + 1;
-
-  /** @type {ol.RegularShapeRenderOptions} */
-  var renderOptions = {
-    strokeStyle: strokeStyle,
-    strokeWidth: strokeWidth,
-    size: size,
-    lineCap: lineCap,
-    lineDash: lineDash,
-    lineJoin: lineJoin,
-    miterLimit: miterLimit
-  };
-
-  if (atlasManager === undefined) {
-    // no atlas manager is used, create a new canvas
-    var context = ol.dom.createCanvasContext2D(size, size);
-    this.canvas_ = context.canvas;
-
-    // canvas.width and height are rounded to the closest integer
-    size = this.canvas_.width;
-    imageSize = size;
-
-    this.draw_(renderOptions, context, 0, 0);
-
-    this.createHitDetectionCanvas_(renderOptions);
-  } else {
-    // an atlas manager is used, add the symbol to an atlas
-    size = Math.round(size);
-
-    var hasCustomHitDetectionImage = !this.fill_;
-    var renderHitDetectionCallback;
-    if (hasCustomHitDetectionImage) {
-      // render the hit-detection image into a separate atlas image
-      renderHitDetectionCallback =
-          this.drawHitDetectionCanvas_.bind(this, renderOptions);
-    }
-
-    var id = this.getChecksum();
-    var info = atlasManager.add(
-        id, size, size, this.draw_.bind(this, renderOptions),
-        renderHitDetectionCallback);
-    ol.DEBUG && console.assert(info, 'shape size is too large');
-
-    this.canvas_ = info.image;
-    this.origin_ = [info.offsetX, info.offsetY];
-    imageSize = info.image.width;
-
-    if (hasCustomHitDetectionImage) {
-      this.hitDetectionCanvas_ = info.hitImage;
-      this.hitDetectionImageSize_ =
-          [info.hitImage.width, info.hitImage.height];
-    } else {
-      this.hitDetectionCanvas_ = this.canvas_;
-      this.hitDetectionImageSize_ = [imageSize, imageSize];
-    }
-  }
-
-  this.anchor_ = [size / 2, size / 2];
-  this.size_ = [size, size];
-  this.imageSize_ = [imageSize, imageSize];
-};
-
-
-/**
- * @private
- * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
- * @param {CanvasRenderingContext2D} context The rendering context.
- * @param {number} x The origin for the symbol (x).
- * @param {number} y The origin for the symbol (y).
- */
-ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
-  var i, angle0, radiusC;
-  // reset transform
-  context.setTransform(1, 0, 0, 1, 0, 0);
-
-  // then move to (x, y)
-  context.translate(x, y);
-
-  context.beginPath();
-  if (this.radius2_ !== this.radius_) {
-    this.points_ = 2 * this.points_;
-  }
-  for (i = 0; i <= this.points_; i++) {
-    angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
-    radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
-    context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
-                   renderOptions.size / 2 + radiusC * Math.sin(angle0));
-  }
-
-  if (this.fill_) {
-    context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor());
-    context.fill();
-  }
-  if (this.stroke_) {
-    context.strokeStyle = renderOptions.strokeStyle;
-    context.lineWidth = renderOptions.strokeWidth;
-    if (renderOptions.lineDash) {
-      context.setLineDash(renderOptions.lineDash);
-    }
-    context.lineCap = renderOptions.lineCap;
-    context.lineJoin = renderOptions.lineJoin;
-    context.miterLimit = renderOptions.miterLimit;
-    context.stroke();
-  }
-  context.closePath();
-};
-
-
-/**
- * @private
- * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
- */
-ol.style.RegularShape.prototype.createHitDetectionCanvas_ = function(renderOptions) {
-  this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
-  if (this.fill_) {
-    this.hitDetectionCanvas_ = this.canvas_;
-    return;
-  }
-
-  // if no fill style is set, create an extra hit-detection image with a
-  // default fill style
-  var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size);
-  this.hitDetectionCanvas_ = context.canvas;
-
-  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
-};
-
-
-/**
- * @private
- * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
- * @param {CanvasRenderingContext2D} context The context.
- * @param {number} x The origin for the symbol (x).
- * @param {number} y The origin for the symbol (y).
- */
-ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) {
-  // reset transform
-  context.setTransform(1, 0, 0, 1, 0, 0);
-
-  // then move to (x, y)
-  context.translate(x, y);
-
-  context.beginPath();
-  if (this.radius2_ !== this.radius_) {
-    this.points_ = 2 * this.points_;
-  }
-  var i, radiusC, angle0;
-  for (i = 0; i <= this.points_; i++) {
-    angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
-    radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
-    context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
-                   renderOptions.size / 2 + radiusC * Math.sin(angle0));
-  }
-
-  context.fillStyle = ol.render.canvas.defaultFillStyle;
-  context.fill();
-  if (this.stroke_) {
-    context.strokeStyle = renderOptions.strokeStyle;
-    context.lineWidth = renderOptions.strokeWidth;
-    if (renderOptions.lineDash) {
-      context.setLineDash(renderOptions.lineDash);
-    }
-    context.stroke();
-  }
-  context.closePath();
-};
-
-
-/**
- * @return {string} The checksum.
- */
-ol.style.RegularShape.prototype.getChecksum = function() {
-  var strokeChecksum = this.stroke_ ?
-      this.stroke_.getChecksum() : '-';
-  var fillChecksum = this.fill_ ?
-      this.fill_.getChecksum() : '-';
-
-  var recalculate = !this.checksums_ ||
-      (strokeChecksum != this.checksums_[1] ||
-      fillChecksum != this.checksums_[2] ||
-      this.radius_ != this.checksums_[3] ||
-      this.radius2_ != this.checksums_[4] ||
-      this.angle_ != this.checksums_[5] ||
-      this.points_ != this.checksums_[6]);
-
-  if (recalculate) {
-    var checksum = 'r' + strokeChecksum + fillChecksum +
-        (this.radius_ !== undefined ? this.radius_.toString() : '-') +
-        (this.radius2_ !== undefined ? this.radius2_.toString() : '-') +
-        (this.angle_ !== undefined ? this.angle_.toString() : '-') +
-        (this.points_ !== undefined ? this.points_.toString() : '-');
-    this.checksums_ = [checksum, strokeChecksum, fillChecksum,
-      this.radius_, this.radius2_, this.angle_, this.points_];
-  }
-
-  return this.checksums_[0];
-};
-
-// Copyright 2009 The Closure Library Authors.
-// All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// This file has been auto-generated by GenJsDeps, please do not edit.
-
-goog.addDependency(
-    'demos/editor/equationeditor.js', ['goog.demos.editor.EquationEditor'],
-    ['goog.ui.equation.EquationEditorDialog']);
-goog.addDependency(
-    'demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'],
-    ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']);
-goog.addDependency(
-    'demos/editor/helloworlddialog.js',
-    [
-      'goog.demos.editor.HelloWorldDialog',
-      'goog.demos.editor.HelloWorldDialog.OkEvent'
-    ],
-    [
-      'goog.dom.TagName', 'goog.events.Event', 'goog.string',
-      'goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder',
-      'goog.ui.editor.AbstractDialog.EventType'
-    ]);
-goog.addDependency(
-    'demos/editor/helloworlddialogplugin.js',
-    [
-      'goog.demos.editor.HelloWorldDialogPlugin',
-      'goog.demos.editor.HelloWorldDialogPlugin.Command'
-    ],
-    [
-      'goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName',
-      'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range',
-      'goog.functions', 'goog.ui.editor.AbstractDialog.EventType'
-    ]);
-
-/**
- * @fileoverview Custom exports file.
- * @suppress {checkVars,extraRequire}
- */
-
-goog.require('ol');
-goog.require('ol.AssertionError');
-goog.require('ol.Attribution');
-goog.require('ol.Collection');
-goog.require('ol.DeviceOrientation');
-goog.require('ol.Feature');
-goog.require('ol.Geolocation');
-goog.require('ol.Graticule');
-goog.require('ol.Image');
-goog.require('ol.ImageTile');
-goog.require('ol.Kinetic');
-goog.require('ol.Map');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEvent.EventType');
-goog.require('ol.MapBrowserEventHandler');
-goog.require('ol.MapBrowserPointerEvent');
-goog.require('ol.MapEvent');
-goog.require('ol.Object');
-goog.require('ol.ObjectEvent');
-goog.require('ol.ObjectEventType');
-goog.require('ol.Observable');
-goog.require('ol.Overlay');
-goog.require('ol.RasterOperationType');
-goog.require('ol.Sphere');
-goog.require('ol.Tile');
-goog.require('ol.VectorTile');
-goog.require('ol.View');
-goog.require('ol.animation');
-goog.require('ol.color');
-goog.require('ol.colorlike');
-goog.require('ol.control');
-goog.require('ol.control.Attribution');
-goog.require('ol.control.Control');
-goog.require('ol.control.FullScreen');
-goog.require('ol.control.MousePosition');
-goog.require('ol.control.OverviewMap');
-goog.require('ol.control.Rotate');
-goog.require('ol.control.ScaleLine');
-goog.require('ol.control.Zoom');
-goog.require('ol.control.ZoomSlider');
-goog.require('ol.control.ZoomToExtent');
-goog.require('ol.coordinate');
-goog.require('ol.easing');
-goog.require('ol.events.Event');
-goog.require('ol.events.condition');
-goog.require('ol.extent');
-goog.require('ol.extent.Corner');
-goog.require('ol.extent.Relationship');
-goog.require('ol.featureloader');
-goog.require('ol.format.EsriJSON');
-goog.require('ol.format.Feature');
-goog.require('ol.format.GML');
-goog.require('ol.format.GML2');
-goog.require('ol.format.GML3');
-goog.require('ol.format.GMLBase');
-goog.require('ol.format.GPX');
-goog.require('ol.format.GeoJSON');
-goog.require('ol.format.IGC');
-goog.require('ol.format.KML');
-goog.require('ol.format.MVT');
-goog.require('ol.format.OSMXML');
-goog.require('ol.format.Polyline');
-goog.require('ol.format.TopoJSON');
-goog.require('ol.format.WFS');
-goog.require('ol.format.WKT');
-goog.require('ol.format.WMSCapabilities');
-goog.require('ol.format.WMSGetFeatureInfo');
-goog.require('ol.format.WMTSCapabilities');
-goog.require('ol.format.filter');
-goog.require('ol.format.filter.And');
-goog.require('ol.format.filter.Bbox');
-goog.require('ol.format.filter.Comparison');
-goog.require('ol.format.filter.ComparisonBinary');
-goog.require('ol.format.filter.EqualTo');
-goog.require('ol.format.filter.Filter');
-goog.require('ol.format.filter.GreaterThan');
-goog.require('ol.format.filter.GreaterThanOrEqualTo');
-goog.require('ol.format.filter.Intersects');
-goog.require('ol.format.filter.IsBetween');
-goog.require('ol.format.filter.IsLike');
-goog.require('ol.format.filter.IsNull');
-goog.require('ol.format.filter.LessThan');
-goog.require('ol.format.filter.LessThanOrEqualTo');
-goog.require('ol.format.filter.Not');
-goog.require('ol.format.filter.NotEqualTo');
-goog.require('ol.format.filter.Or');
-goog.require('ol.format.filter.Spatial');
-goog.require('ol.format.filter.Within');
-goog.require('ol.geom.Circle');
-goog.require('ol.geom.Geometry');
-goog.require('ol.geom.GeometryCollection');
-goog.require('ol.geom.GeometryLayout');
-goog.require('ol.geom.GeometryType');
-goog.require('ol.geom.LineString');
-goog.require('ol.geom.LinearRing');
-goog.require('ol.geom.MultiLineString');
-goog.require('ol.geom.MultiPoint');
-goog.require('ol.geom.MultiPolygon');
-goog.require('ol.geom.Point');
-goog.require('ol.geom.Polygon');
-goog.require('ol.geom.SimpleGeometry');
-goog.require('ol.has');
-goog.require('ol.interaction');
-goog.require('ol.interaction.DoubleClickZoom');
-goog.require('ol.interaction.DragAndDrop');
-goog.require('ol.interaction.DragBox');
-goog.require('ol.interaction.DragPan');
-goog.require('ol.interaction.DragRotate');
-goog.require('ol.interaction.DragRotateAndZoom');
-goog.require('ol.interaction.DragZoom');
-goog.require('ol.interaction.Draw');
-goog.require('ol.interaction.Extent');
-goog.require('ol.interaction.Interaction');
-goog.require('ol.interaction.KeyboardPan');
-goog.require('ol.interaction.KeyboardZoom');
-goog.require('ol.interaction.Modify');
-goog.require('ol.interaction.MouseWheelZoom');
-goog.require('ol.interaction.PinchRotate');
-goog.require('ol.interaction.PinchZoom');
-goog.require('ol.interaction.Pointer');
-goog.require('ol.interaction.Select');
-goog.require('ol.interaction.Snap');
-goog.require('ol.interaction.Translate');
-goog.require('ol.layer.Base');
-goog.require('ol.layer.Group');
-goog.require('ol.layer.Heatmap');
-goog.require('ol.layer.Image');
-goog.require('ol.layer.Layer');
-goog.require('ol.layer.LayerProperty');
-goog.require('ol.layer.Tile');
-goog.require('ol.layer.Vector');
-goog.require('ol.layer.VectorTile');
-goog.require('ol.loadingstrategy');
-goog.require('ol.proj');
-goog.require('ol.proj.METERS_PER_UNIT');
-goog.require('ol.proj.Projection');
-goog.require('ol.proj.Units');
-goog.require('ol.proj.common');
-goog.require('ol.render');
-goog.require('ol.render.Event');
-goog.require('ol.render.Feature');
-goog.require('ol.render.VectorContext');
-goog.require('ol.render.canvas.Immediate');
-goog.require('ol.render.webgl.Immediate');
-goog.require('ol.size');
-goog.require('ol.source.BingMaps');
-goog.require('ol.source.CartoDB');
-goog.require('ol.source.Cluster');
-goog.require('ol.source.Image');
-goog.require('ol.source.ImageArcGISRest');
-goog.require('ol.source.ImageCanvas');
-goog.require('ol.source.ImageMapGuide');
-goog.require('ol.source.ImageStatic');
-goog.require('ol.source.ImageVector');
-goog.require('ol.source.ImageWMS');
-goog.require('ol.source.OSM');
-goog.require('ol.source.Raster');
-goog.require('ol.source.Source');
-goog.require('ol.source.Stamen');
-goog.require('ol.source.Tile');
-goog.require('ol.source.TileArcGISRest');
-goog.require('ol.source.TileDebug');
-goog.require('ol.source.TileImage');
-goog.require('ol.source.TileJSON');
-goog.require('ol.source.TileUTFGrid');
-goog.require('ol.source.TileWMS');
-goog.require('ol.source.UrlTile');
-goog.require('ol.source.Vector');
-goog.require('ol.source.VectorTile');
-goog.require('ol.source.WMTS');
-goog.require('ol.source.XYZ');
-goog.require('ol.source.Zoomify');
-goog.require('ol.style.AtlasManager');
-goog.require('ol.style.Circle');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Icon');
-goog.require('ol.style.Image');
-goog.require('ol.style.RegularShape');
-goog.require('ol.style.Stroke');
-goog.require('ol.style.Style');
-goog.require('ol.style.Text');
-goog.require('ol.tilegrid');
-goog.require('ol.tilegrid.TileGrid');
-goog.require('ol.tilegrid.WMTS');
-goog.require('ol.webgl.Context');
-goog.require('ol.xml');
-
-
-goog.exportSymbol(
-    'ol.animation.bounce',
-    ol.animation.bounce,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.animation.pan',
-    ol.animation.pan,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.animation.rotate',
-    ol.animation.rotate,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.animation.zoom',
-    ol.animation.zoom,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.AssertionError.prototype,
-    'code',
-    ol.AssertionError.prototype.code);
-
-goog.exportSymbol(
-    'ol.Attribution',
-    ol.Attribution,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Attribution.prototype,
-    'getHTML',
-    ol.Attribution.prototype.getHTML);
-
-goog.exportSymbol(
-    'ol.Collection',
-    ol.Collection,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'clear',
-    ol.Collection.prototype.clear);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'extend',
-    ol.Collection.prototype.extend);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'forEach',
-    ol.Collection.prototype.forEach);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'getArray',
-    ol.Collection.prototype.getArray);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'item',
-    ol.Collection.prototype.item);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'getLength',
-    ol.Collection.prototype.getLength);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'insertAt',
-    ol.Collection.prototype.insertAt);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'pop',
-    ol.Collection.prototype.pop);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'push',
-    ol.Collection.prototype.push);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'remove',
-    ol.Collection.prototype.remove);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'removeAt',
-    ol.Collection.prototype.removeAt);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'setAt',
-    ol.Collection.prototype.setAt);
-
-goog.exportProperty(
-    ol.Collection.Event.prototype,
-    'element',
-    ol.Collection.Event.prototype.element);
-
-goog.exportSymbol(
-    'ol.color.asArray',
-    ol.color.asArray,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.color.asString',
-    ol.color.asString,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.colorlike.asColorLike',
-    ol.colorlike.asColorLike,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.coordinate.add',
-    ol.coordinate.add,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.coordinate.createStringXY',
-    ol.coordinate.createStringXY,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.coordinate.format',
-    ol.coordinate.format,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.coordinate.rotate',
-    ol.coordinate.rotate,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.coordinate.toStringHDMS',
-    ol.coordinate.toStringHDMS,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.coordinate.toStringXY',
-    ol.coordinate.toStringXY,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.DeviceOrientation',
-    ol.DeviceOrientation,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getAlpha',
-    ol.DeviceOrientation.prototype.getAlpha);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getBeta',
-    ol.DeviceOrientation.prototype.getBeta);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getGamma',
-    ol.DeviceOrientation.prototype.getGamma);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getHeading',
-    ol.DeviceOrientation.prototype.getHeading);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getTracking',
-    ol.DeviceOrientation.prototype.getTracking);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'setTracking',
-    ol.DeviceOrientation.prototype.setTracking);
-
-goog.exportSymbol(
-    'ol.easing.easeIn',
-    ol.easing.easeIn,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.easing.easeOut',
-    ol.easing.easeOut,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.easing.inAndOut',
-    ol.easing.inAndOut,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.easing.linear',
-    ol.easing.linear,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.easing.upAndDown',
-    ol.easing.upAndDown,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.boundingExtent',
-    ol.extent.boundingExtent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.buffer',
-    ol.extent.buffer,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.containsCoordinate',
-    ol.extent.containsCoordinate,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.containsExtent',
-    ol.extent.containsExtent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.containsXY',
-    ol.extent.containsXY,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.createEmpty',
-    ol.extent.createEmpty,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.equals',
-    ol.extent.equals,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.extend',
-    ol.extent.extend,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getBottomLeft',
-    ol.extent.getBottomLeft,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getBottomRight',
-    ol.extent.getBottomRight,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getCenter',
-    ol.extent.getCenter,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getHeight',
-    ol.extent.getHeight,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getIntersection',
-    ol.extent.getIntersection,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getSize',
-    ol.extent.getSize,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getTopLeft',
-    ol.extent.getTopLeft,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getTopRight',
-    ol.extent.getTopRight,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.getWidth',
-    ol.extent.getWidth,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.intersects',
-    ol.extent.intersects,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.isEmpty',
-    ol.extent.isEmpty,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.extent.applyTransform',
-    ol.extent.applyTransform,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.Feature',
-    ol.Feature,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'clone',
-    ol.Feature.prototype.clone);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getGeometry',
-    ol.Feature.prototype.getGeometry);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getId',
-    ol.Feature.prototype.getId);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getGeometryName',
-    ol.Feature.prototype.getGeometryName);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getStyle',
-    ol.Feature.prototype.getStyle);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getStyleFunction',
-    ol.Feature.prototype.getStyleFunction);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'setGeometry',
-    ol.Feature.prototype.setGeometry);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'setStyle',
-    ol.Feature.prototype.setStyle);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'setId',
-    ol.Feature.prototype.setId);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'setGeometryName',
-    ol.Feature.prototype.setGeometryName);
-
-goog.exportSymbol(
-    'ol.featureloader.tile',
-    ol.featureloader.tile,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.featureloader.xhr',
-    ol.featureloader.xhr,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.Geolocation',
-    ol.Geolocation,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAccuracy',
-    ol.Geolocation.prototype.getAccuracy);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAccuracyGeometry',
-    ol.Geolocation.prototype.getAccuracyGeometry);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAltitude',
-    ol.Geolocation.prototype.getAltitude);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getAltitudeAccuracy',
-    ol.Geolocation.prototype.getAltitudeAccuracy);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getHeading',
-    ol.Geolocation.prototype.getHeading);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getPosition',
-    ol.Geolocation.prototype.getPosition);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getProjection',
-    ol.Geolocation.prototype.getProjection);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getSpeed',
-    ol.Geolocation.prototype.getSpeed);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getTracking',
-    ol.Geolocation.prototype.getTracking);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getTrackingOptions',
-    ol.Geolocation.prototype.getTrackingOptions);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'setProjection',
-    ol.Geolocation.prototype.setProjection);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'setTracking',
-    ol.Geolocation.prototype.setTracking);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'setTrackingOptions',
-    ol.Geolocation.prototype.setTrackingOptions);
-
-goog.exportSymbol(
-    'ol.Graticule',
-    ol.Graticule,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Graticule.prototype,
-    'getMap',
-    ol.Graticule.prototype.getMap);
-
-goog.exportProperty(
-    ol.Graticule.prototype,
-    'getMeridians',
-    ol.Graticule.prototype.getMeridians);
-
-goog.exportProperty(
-    ol.Graticule.prototype,
-    'getParallels',
-    ol.Graticule.prototype.getParallels);
-
-goog.exportProperty(
-    ol.Graticule.prototype,
-    'setMap',
-    ol.Graticule.prototype.setMap);
-
-goog.exportSymbol(
-    'ol.has.DEVICE_PIXEL_RATIO',
-    ol.has.DEVICE_PIXEL_RATIO,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.has.CANVAS',
-    ol.has.CANVAS,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.has.DEVICE_ORIENTATION',
-    ol.has.DEVICE_ORIENTATION,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.has.GEOLOCATION',
-    ol.has.GEOLOCATION,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.has.TOUCH',
-    ol.has.TOUCH,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.has.WEBGL',
-    ol.has.WEBGL,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Image.prototype,
-    'getImage',
-    ol.Image.prototype.getImage);
-
-goog.exportProperty(
-    ol.Image.prototype,
-    'load',
-    ol.Image.prototype.load);
-
-goog.exportProperty(
-    ol.ImageTile.prototype,
-    'getImage',
-    ol.ImageTile.prototype.getImage);
-
-goog.exportProperty(
-    ol.ImageTile.prototype,
-    'load',
-    ol.ImageTile.prototype.load);
-
-goog.exportSymbol(
-    'ol.inherits',
-    ol.inherits,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.Kinetic',
-    ol.Kinetic,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.loadingstrategy.all',
-    ol.loadingstrategy.all,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.loadingstrategy.bbox',
-    ol.loadingstrategy.bbox,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.loadingstrategy.tile',
-    ol.loadingstrategy.tile,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.Map',
-    ol.Map,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'addControl',
-    ol.Map.prototype.addControl);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'addInteraction',
-    ol.Map.prototype.addInteraction);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'addLayer',
-    ol.Map.prototype.addLayer);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'addOverlay',
-    ol.Map.prototype.addOverlay);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'beforeRender',
-    ol.Map.prototype.beforeRender);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'forEachFeatureAtPixel',
-    ol.Map.prototype.forEachFeatureAtPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'forEachLayerAtPixel',
-    ol.Map.prototype.forEachLayerAtPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'hasFeatureAtPixel',
-    ol.Map.prototype.hasFeatureAtPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getEventCoordinate',
-    ol.Map.prototype.getEventCoordinate);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getEventPixel',
-    ol.Map.prototype.getEventPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getTarget',
-    ol.Map.prototype.getTarget);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getTargetElement',
-    ol.Map.prototype.getTargetElement);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getCoordinateFromPixel',
-    ol.Map.prototype.getCoordinateFromPixel);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getControls',
-    ol.Map.prototype.getControls);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getOverlays',
-    ol.Map.prototype.getOverlays);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getOverlayById',
-    ol.Map.prototype.getOverlayById);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getInteractions',
-    ol.Map.prototype.getInteractions);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getLayerGroup',
-    ol.Map.prototype.getLayerGroup);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getLayers',
-    ol.Map.prototype.getLayers);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getPixelFromCoordinate',
-    ol.Map.prototype.getPixelFromCoordinate);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getSize',
-    ol.Map.prototype.getSize);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getView',
-    ol.Map.prototype.getView);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getViewport',
-    ol.Map.prototype.getViewport);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'renderSync',
-    ol.Map.prototype.renderSync);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'render',
-    ol.Map.prototype.render);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeControl',
-    ol.Map.prototype.removeControl);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeInteraction',
-    ol.Map.prototype.removeInteraction);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeLayer',
-    ol.Map.prototype.removeLayer);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'removeOverlay',
-    ol.Map.prototype.removeOverlay);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setLayerGroup',
-    ol.Map.prototype.setLayerGroup);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setSize',
-    ol.Map.prototype.setSize);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setTarget',
-    ol.Map.prototype.setTarget);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setView',
-    ol.Map.prototype.setView);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'updateSize',
-    ol.Map.prototype.updateSize);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'originalEvent',
-    ol.MapBrowserEvent.prototype.originalEvent);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'pixel',
-    ol.MapBrowserEvent.prototype.pixel);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'coordinate',
-    ol.MapBrowserEvent.prototype.coordinate);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'dragging',
-    ol.MapBrowserEvent.prototype.dragging);
-
-goog.exportProperty(
-    ol.MapEvent.prototype,
-    'map',
-    ol.MapEvent.prototype.map);
-
-goog.exportProperty(
-    ol.MapEvent.prototype,
-    'frameState',
-    ol.MapEvent.prototype.frameState);
-
-goog.exportProperty(
-    ol.ObjectEvent.prototype,
-    'key',
-    ol.ObjectEvent.prototype.key);
-
-goog.exportProperty(
-    ol.ObjectEvent.prototype,
-    'oldValue',
-    ol.ObjectEvent.prototype.oldValue);
-
-goog.exportSymbol(
-    'ol.Object',
-    ol.Object,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'get',
-    ol.Object.prototype.get);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'getKeys',
-    ol.Object.prototype.getKeys);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'getProperties',
-    ol.Object.prototype.getProperties);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'set',
-    ol.Object.prototype.set);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'setProperties',
-    ol.Object.prototype.setProperties);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'unset',
-    ol.Object.prototype.unset);
-
-goog.exportSymbol(
-    'ol.Observable',
-    ol.Observable,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.Observable.unByKey',
-    ol.Observable.unByKey,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Observable.prototype,
-    'changed',
-    ol.Observable.prototype.changed);
-
-goog.exportProperty(
-    ol.Observable.prototype,
-    'dispatchEvent',
-    ol.Observable.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.Observable.prototype,
-    'getRevision',
-    ol.Observable.prototype.getRevision);
-
-goog.exportProperty(
-    ol.Observable.prototype,
-    'on',
-    ol.Observable.prototype.on);
-
-goog.exportProperty(
-    ol.Observable.prototype,
-    'once',
-    ol.Observable.prototype.once);
-
-goog.exportProperty(
-    ol.Observable.prototype,
-    'un',
-    ol.Observable.prototype.un);
-
-goog.exportProperty(
-    ol.Observable.prototype,
-    'unByKey',
-    ol.Observable.prototype.unByKey);
-
-goog.exportSymbol(
-    'ol.Overlay',
-    ol.Overlay,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getElement',
-    ol.Overlay.prototype.getElement);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getId',
-    ol.Overlay.prototype.getId);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getMap',
-    ol.Overlay.prototype.getMap);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getOffset',
-    ol.Overlay.prototype.getOffset);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getPosition',
-    ol.Overlay.prototype.getPosition);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getPositioning',
-    ol.Overlay.prototype.getPositioning);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setElement',
-    ol.Overlay.prototype.setElement);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setMap',
-    ol.Overlay.prototype.setMap);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setOffset',
-    ol.Overlay.prototype.setOffset);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setPosition',
-    ol.Overlay.prototype.setPosition);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setPositioning',
-    ol.Overlay.prototype.setPositioning);
-
-goog.exportSymbol(
-    'ol.render.toContext',
-    ol.render.toContext,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.size.toSize',
-    ol.size.toSize,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Tile.prototype,
-    'getTileCoord',
-    ol.Tile.prototype.getTileCoord);
-
-goog.exportProperty(
-    ol.Tile.prototype,
-    'load',
-    ol.Tile.prototype.load);
-
-goog.exportProperty(
-    ol.VectorTile.prototype,
-    'getFormat',
-    ol.VectorTile.prototype.getFormat);
-
-goog.exportProperty(
-    ol.VectorTile.prototype,
-    'setFeatures',
-    ol.VectorTile.prototype.setFeatures);
-
-goog.exportProperty(
-    ol.VectorTile.prototype,
-    'setProjection',
-    ol.VectorTile.prototype.setProjection);
-
-goog.exportProperty(
-    ol.VectorTile.prototype,
-    'setLoader',
-    ol.VectorTile.prototype.setLoader);
-
-goog.exportSymbol(
-    'ol.View',
-    ol.View,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'constrainCenter',
-    ol.View.prototype.constrainCenter);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'constrainResolution',
-    ol.View.prototype.constrainResolution);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'constrainRotation',
-    ol.View.prototype.constrainRotation);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getCenter',
-    ol.View.prototype.getCenter);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'calculateExtent',
-    ol.View.prototype.calculateExtent);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getMaxResolution',
-    ol.View.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getMinResolution',
-    ol.View.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getProjection',
-    ol.View.prototype.getProjection);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getResolution',
-    ol.View.prototype.getResolution);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getResolutions',
-    ol.View.prototype.getResolutions);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getRotation',
-    ol.View.prototype.getRotation);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getZoom',
-    ol.View.prototype.getZoom);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'fit',
-    ol.View.prototype.fit);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'centerOn',
-    ol.View.prototype.centerOn);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'rotate',
-    ol.View.prototype.rotate);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'setCenter',
-    ol.View.prototype.setCenter);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'setResolution',
-    ol.View.prototype.setResolution);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'setRotation',
-    ol.View.prototype.setRotation);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'setZoom',
-    ol.View.prototype.setZoom);
-
-goog.exportSymbol(
-    'ol.xml.getAllTextContent',
-    ol.xml.getAllTextContent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.xml.parse',
-    ol.xml.parse,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.webgl.Context.prototype,
-    'getGL',
-    ol.webgl.Context.prototype.getGL);
-
-goog.exportProperty(
-    ol.webgl.Context.prototype,
-    'useProgram',
-    ol.webgl.Context.prototype.useProgram);
-
-goog.exportSymbol(
-    'ol.tilegrid.createXYZ',
-    ol.tilegrid.createXYZ,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.tilegrid.TileGrid',
-    ol.tilegrid.TileGrid,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'forEachTileCoord',
-    ol.tilegrid.TileGrid.prototype.forEachTileCoord);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getMaxZoom',
-    ol.tilegrid.TileGrid.prototype.getMaxZoom);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getMinZoom',
-    ol.tilegrid.TileGrid.prototype.getMinZoom);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getOrigin',
-    ol.tilegrid.TileGrid.prototype.getOrigin);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getResolution',
-    ol.tilegrid.TileGrid.prototype.getResolution);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getResolutions',
-    ol.tilegrid.TileGrid.prototype.getResolutions);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getTileCoordExtent',
-    ol.tilegrid.TileGrid.prototype.getTileCoordExtent);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getTileCoordForCoordAndResolution',
-    ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getTileCoordForCoordAndZ',
-    ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getTileSize',
-    ol.tilegrid.TileGrid.prototype.getTileSize);
-
-goog.exportProperty(
-    ol.tilegrid.TileGrid.prototype,
-    'getZForResolution',
-    ol.tilegrid.TileGrid.prototype.getZForResolution);
-
-goog.exportSymbol(
-    'ol.tilegrid.WMTS',
-    ol.tilegrid.WMTS,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getMatrixIds',
-    ol.tilegrid.WMTS.prototype.getMatrixIds);
-
-goog.exportSymbol(
-    'ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet',
-    ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.style.AtlasManager',
-    ol.style.AtlasManager,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.style.Circle',
-    ol.style.Circle,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'clone',
-    ol.style.Circle.prototype.clone);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getFill',
-    ol.style.Circle.prototype.getFill);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getImage',
-    ol.style.Circle.prototype.getImage);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getRadius',
-    ol.style.Circle.prototype.getRadius);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getStroke',
-    ol.style.Circle.prototype.getStroke);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'setRadius',
-    ol.style.Circle.prototype.setRadius);
-
-goog.exportSymbol(
-    'ol.style.Fill',
-    ol.style.Fill,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.Fill.prototype,
-    'clone',
-    ol.style.Fill.prototype.clone);
-
-goog.exportProperty(
-    ol.style.Fill.prototype,
-    'getColor',
-    ol.style.Fill.prototype.getColor);
-
-goog.exportProperty(
-    ol.style.Fill.prototype,
-    'setColor',
-    ol.style.Fill.prototype.setColor);
-
-goog.exportSymbol(
-    'ol.style.Icon',
-    ol.style.Icon,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'clone',
-    ol.style.Icon.prototype.clone);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getAnchor',
-    ol.style.Icon.prototype.getAnchor);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getImage',
-    ol.style.Icon.prototype.getImage);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getOrigin',
-    ol.style.Icon.prototype.getOrigin);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getSrc',
-    ol.style.Icon.prototype.getSrc);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getSize',
-    ol.style.Icon.prototype.getSize);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'load',
-    ol.style.Icon.prototype.load);
-
-goog.exportSymbol(
-    'ol.style.Image',
-    ol.style.Image,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'getOpacity',
-    ol.style.Image.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'getRotateWithView',
-    ol.style.Image.prototype.getRotateWithView);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'getRotation',
-    ol.style.Image.prototype.getRotation);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'getScale',
-    ol.style.Image.prototype.getScale);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'getSnapToPixel',
-    ol.style.Image.prototype.getSnapToPixel);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'setOpacity',
-    ol.style.Image.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'setRotation',
-    ol.style.Image.prototype.setRotation);
-
-goog.exportProperty(
-    ol.style.Image.prototype,
-    'setScale',
-    ol.style.Image.prototype.setScale);
-
-goog.exportSymbol(
-    'ol.style.RegularShape',
-    ol.style.RegularShape,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'clone',
-    ol.style.RegularShape.prototype.clone);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getAnchor',
-    ol.style.RegularShape.prototype.getAnchor);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getAngle',
-    ol.style.RegularShape.prototype.getAngle);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getFill',
-    ol.style.RegularShape.prototype.getFill);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getImage',
-    ol.style.RegularShape.prototype.getImage);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getOrigin',
-    ol.style.RegularShape.prototype.getOrigin);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getPoints',
-    ol.style.RegularShape.prototype.getPoints);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getRadius',
-    ol.style.RegularShape.prototype.getRadius);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getRadius2',
-    ol.style.RegularShape.prototype.getRadius2);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getSize',
-    ol.style.RegularShape.prototype.getSize);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getStroke',
-    ol.style.RegularShape.prototype.getStroke);
-
-goog.exportSymbol(
-    'ol.style.Stroke',
-    ol.style.Stroke,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'clone',
-    ol.style.Stroke.prototype.clone);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'getColor',
-    ol.style.Stroke.prototype.getColor);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'getLineCap',
-    ol.style.Stroke.prototype.getLineCap);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'getLineDash',
-    ol.style.Stroke.prototype.getLineDash);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'getLineJoin',
-    ol.style.Stroke.prototype.getLineJoin);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'getMiterLimit',
-    ol.style.Stroke.prototype.getMiterLimit);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'getWidth',
-    ol.style.Stroke.prototype.getWidth);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'setColor',
-    ol.style.Stroke.prototype.setColor);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'setLineCap',
-    ol.style.Stroke.prototype.setLineCap);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'setLineDash',
-    ol.style.Stroke.prototype.setLineDash);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'setLineJoin',
-    ol.style.Stroke.prototype.setLineJoin);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'setMiterLimit',
-    ol.style.Stroke.prototype.setMiterLimit);
-
-goog.exportProperty(
-    ol.style.Stroke.prototype,
-    'setWidth',
-    ol.style.Stroke.prototype.setWidth);
-
-goog.exportSymbol(
-    'ol.style.Style',
-    ol.style.Style,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'clone',
-    ol.style.Style.prototype.clone);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'getGeometry',
-    ol.style.Style.prototype.getGeometry);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'getGeometryFunction',
-    ol.style.Style.prototype.getGeometryFunction);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'getFill',
-    ol.style.Style.prototype.getFill);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'getImage',
-    ol.style.Style.prototype.getImage);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'getStroke',
-    ol.style.Style.prototype.getStroke);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'getText',
-    ol.style.Style.prototype.getText);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'getZIndex',
-    ol.style.Style.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'setGeometry',
-    ol.style.Style.prototype.setGeometry);
-
-goog.exportProperty(
-    ol.style.Style.prototype,
-    'setZIndex',
-    ol.style.Style.prototype.setZIndex);
-
-goog.exportSymbol(
-    'ol.style.Text',
-    ol.style.Text,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'clone',
-    ol.style.Text.prototype.clone);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getFont',
-    ol.style.Text.prototype.getFont);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getOffsetX',
-    ol.style.Text.prototype.getOffsetX);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getOffsetY',
-    ol.style.Text.prototype.getOffsetY);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getFill',
-    ol.style.Text.prototype.getFill);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getRotateWithView',
-    ol.style.Text.prototype.getRotateWithView);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getRotation',
-    ol.style.Text.prototype.getRotation);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getScale',
-    ol.style.Text.prototype.getScale);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getStroke',
-    ol.style.Text.prototype.getStroke);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getText',
-    ol.style.Text.prototype.getText);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getTextAlign',
-    ol.style.Text.prototype.getTextAlign);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'getTextBaseline',
-    ol.style.Text.prototype.getTextBaseline);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setFont',
-    ol.style.Text.prototype.setFont);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setOffsetX',
-    ol.style.Text.prototype.setOffsetX);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setOffsetY',
-    ol.style.Text.prototype.setOffsetY);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setFill',
-    ol.style.Text.prototype.setFill);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setRotation',
-    ol.style.Text.prototype.setRotation);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setScale',
-    ol.style.Text.prototype.setScale);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setStroke',
-    ol.style.Text.prototype.setStroke);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setText',
-    ol.style.Text.prototype.setText);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setTextAlign',
-    ol.style.Text.prototype.setTextAlign);
-
-goog.exportProperty(
-    ol.style.Text.prototype,
-    'setTextBaseline',
-    ol.style.Text.prototype.setTextBaseline);
-
-goog.exportSymbol(
-    'ol.Sphere',
-    ol.Sphere,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Sphere.prototype,
-    'geodesicArea',
-    ol.Sphere.prototype.geodesicArea);
-
-goog.exportProperty(
-    ol.Sphere.prototype,
-    'haversineDistance',
-    ol.Sphere.prototype.haversineDistance);
-
-goog.exportSymbol(
-    'ol.source.BingMaps',
-    ol.source.BingMaps,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.BingMaps.TOS_ATTRIBUTION',
-    ol.source.BingMaps.TOS_ATTRIBUTION,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getApiKey',
-    ol.source.BingMaps.prototype.getApiKey);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getImagerySet',
-    ol.source.BingMaps.prototype.getImagerySet);
-
-goog.exportSymbol(
-    'ol.source.CartoDB',
-    ol.source.CartoDB,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getConfig',
-    ol.source.CartoDB.prototype.getConfig);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'updateConfig',
-    ol.source.CartoDB.prototype.updateConfig);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setConfig',
-    ol.source.CartoDB.prototype.setConfig);
-
-goog.exportSymbol(
-    'ol.source.Cluster',
-    ol.source.Cluster,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getSource',
-    ol.source.Cluster.prototype.getSource);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'setDistance',
-    ol.source.Cluster.prototype.setDistance);
-
-goog.exportSymbol(
-    'ol.source.Image',
-    ol.source.Image,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.Image.Event.prototype,
-    'image',
-    ol.source.Image.Event.prototype.image);
-
-goog.exportSymbol(
-    'ol.source.ImageArcGISRest',
-    ol.source.ImageArcGISRest,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getParams',
-    ol.source.ImageArcGISRest.prototype.getParams);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getImageLoadFunction',
-    ol.source.ImageArcGISRest.prototype.getImageLoadFunction);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getUrl',
-    ol.source.ImageArcGISRest.prototype.getUrl);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'setImageLoadFunction',
-    ol.source.ImageArcGISRest.prototype.setImageLoadFunction);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'setUrl',
-    ol.source.ImageArcGISRest.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'updateParams',
-    ol.source.ImageArcGISRest.prototype.updateParams);
-
-goog.exportSymbol(
-    'ol.source.ImageCanvas',
-    ol.source.ImageCanvas,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.ImageMapGuide',
-    ol.source.ImageMapGuide,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getParams',
-    ol.source.ImageMapGuide.prototype.getParams);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getImageLoadFunction',
-    ol.source.ImageMapGuide.prototype.getImageLoadFunction);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'updateParams',
-    ol.source.ImageMapGuide.prototype.updateParams);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'setImageLoadFunction',
-    ol.source.ImageMapGuide.prototype.setImageLoadFunction);
-
-goog.exportSymbol(
-    'ol.source.ImageStatic',
-    ol.source.ImageStatic,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.ImageVector',
-    ol.source.ImageVector,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getSource',
-    ol.source.ImageVector.prototype.getSource);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getStyle',
-    ol.source.ImageVector.prototype.getStyle);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getStyleFunction',
-    ol.source.ImageVector.prototype.getStyleFunction);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'setStyle',
-    ol.source.ImageVector.prototype.setStyle);
-
-goog.exportSymbol(
-    'ol.source.ImageWMS',
-    ol.source.ImageWMS,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getGetFeatureInfoUrl',
-    ol.source.ImageWMS.prototype.getGetFeatureInfoUrl);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getParams',
-    ol.source.ImageWMS.prototype.getParams);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getImageLoadFunction',
-    ol.source.ImageWMS.prototype.getImageLoadFunction);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getUrl',
-    ol.source.ImageWMS.prototype.getUrl);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'setImageLoadFunction',
-    ol.source.ImageWMS.prototype.setImageLoadFunction);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'setUrl',
-    ol.source.ImageWMS.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'updateParams',
-    ol.source.ImageWMS.prototype.updateParams);
-
-goog.exportSymbol(
-    'ol.source.OSM',
-    ol.source.OSM,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.OSM.ATTRIBUTION',
-    ol.source.OSM.ATTRIBUTION,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.Raster',
-    ol.source.Raster,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'setOperation',
-    ol.source.Raster.prototype.setOperation);
-
-goog.exportProperty(
-    ol.source.Raster.Event.prototype,
-    'extent',
-    ol.source.Raster.Event.prototype.extent);
-
-goog.exportProperty(
-    ol.source.Raster.Event.prototype,
-    'resolution',
-    ol.source.Raster.Event.prototype.resolution);
-
-goog.exportProperty(
-    ol.source.Raster.Event.prototype,
-    'data',
-    ol.source.Raster.Event.prototype.data);
-
-goog.exportSymbol(
-    'ol.source.Source',
-    ol.source.Source,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'getAttributions',
-    ol.source.Source.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'getLogo',
-    ol.source.Source.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'getProjection',
-    ol.source.Source.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'getState',
-    ol.source.Source.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'refresh',
-    ol.source.Source.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'setAttributions',
-    ol.source.Source.prototype.setAttributions);
-
-goog.exportSymbol(
-    'ol.source.Stamen',
-    ol.source.Stamen,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.Tile',
-    ol.source.Tile,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getTileGrid',
-    ol.source.Tile.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.Tile.Event.prototype,
-    'tile',
-    ol.source.Tile.Event.prototype.tile);
-
-goog.exportSymbol(
-    'ol.source.TileArcGISRest',
-    ol.source.TileArcGISRest,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getParams',
-    ol.source.TileArcGISRest.prototype.getParams);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'updateParams',
-    ol.source.TileArcGISRest.prototype.updateParams);
-
-goog.exportSymbol(
-    'ol.source.TileDebug',
-    ol.source.TileDebug,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.TileImage',
-    ol.source.TileImage,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.TileImage.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setTileGridForProjection',
-    ol.source.TileImage.prototype.setTileGridForProjection);
-
-goog.exportSymbol(
-    'ol.source.TileJSON',
-    ol.source.TileJSON,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getTileJSON',
-    ol.source.TileJSON.prototype.getTileJSON);
-
-goog.exportSymbol(
-    'ol.source.TileUTFGrid',
-    ol.source.TileUTFGrid,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getTemplate',
-    ol.source.TileUTFGrid.prototype.getTemplate);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'forDataAtCoordinateAndResolution',
-    ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution);
-
-goog.exportSymbol(
-    'ol.source.TileWMS',
-    ol.source.TileWMS,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getGetFeatureInfoUrl',
-    ol.source.TileWMS.prototype.getGetFeatureInfoUrl);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getParams',
-    ol.source.TileWMS.prototype.getParams);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'updateParams',
-    ol.source.TileWMS.prototype.updateParams);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getTileLoadFunction',
-    ol.source.UrlTile.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getTileUrlFunction',
-    ol.source.UrlTile.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getUrls',
-    ol.source.UrlTile.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'setTileLoadFunction',
-    ol.source.UrlTile.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'setTileUrlFunction',
-    ol.source.UrlTile.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'setUrl',
-    ol.source.UrlTile.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'setUrls',
-    ol.source.UrlTile.prototype.setUrls);
-
-goog.exportSymbol(
-    'ol.source.Vector',
-    ol.source.Vector,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'addFeature',
-    ol.source.Vector.prototype.addFeature);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'addFeatures',
-    ol.source.Vector.prototype.addFeatures);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'clear',
-    ol.source.Vector.prototype.clear);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'forEachFeature',
-    ol.source.Vector.prototype.forEachFeature);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'forEachFeatureInExtent',
-    ol.source.Vector.prototype.forEachFeatureInExtent);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'forEachFeatureIntersectingExtent',
-    ol.source.Vector.prototype.forEachFeatureIntersectingExtent);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getFeaturesCollection',
-    ol.source.Vector.prototype.getFeaturesCollection);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getFeatures',
-    ol.source.Vector.prototype.getFeatures);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getFeaturesAtCoordinate',
-    ol.source.Vector.prototype.getFeaturesAtCoordinate);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getFeaturesInExtent',
-    ol.source.Vector.prototype.getFeaturesInExtent);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getClosestFeatureToCoordinate',
-    ol.source.Vector.prototype.getClosestFeatureToCoordinate);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getExtent',
-    ol.source.Vector.prototype.getExtent);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getFeatureById',
-    ol.source.Vector.prototype.getFeatureById);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getFormat',
-    ol.source.Vector.prototype.getFormat);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getUrl',
-    ol.source.Vector.prototype.getUrl);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'removeFeature',
-    ol.source.Vector.prototype.removeFeature);
-
-goog.exportProperty(
-    ol.source.Vector.Event.prototype,
-    'feature',
-    ol.source.Vector.Event.prototype.feature);
-
-goog.exportSymbol(
-    'ol.source.VectorTile',
-    ol.source.VectorTile,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.WMTS',
-    ol.source.WMTS,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getDimensions',
-    ol.source.WMTS.prototype.getDimensions);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getFormat',
-    ol.source.WMTS.prototype.getFormat);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getLayer',
-    ol.source.WMTS.prototype.getLayer);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getMatrixSet',
-    ol.source.WMTS.prototype.getMatrixSet);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getRequestEncoding',
-    ol.source.WMTS.prototype.getRequestEncoding);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getStyle',
-    ol.source.WMTS.prototype.getStyle);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getVersion',
-    ol.source.WMTS.prototype.getVersion);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'updateDimensions',
-    ol.source.WMTS.prototype.updateDimensions);
-
-goog.exportSymbol(
-    'ol.source.WMTS.optionsFromCapabilities',
-    ol.source.WMTS.optionsFromCapabilities,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.XYZ',
-    ol.source.XYZ,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.source.Zoomify',
-    ol.source.Zoomify,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'vectorContext',
-    ol.render.Event.prototype.vectorContext);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'frameState',
-    ol.render.Event.prototype.frameState);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'context',
-    ol.render.Event.prototype.context);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'glContext',
-    ol.render.Event.prototype.glContext);
-
-goog.exportProperty(
-    ol.render.Feature.prototype,
-    'get',
-    ol.render.Feature.prototype.get);
-
-goog.exportProperty(
-    ol.render.Feature.prototype,
-    'getExtent',
-    ol.render.Feature.prototype.getExtent);
-
-goog.exportProperty(
-    ol.render.Feature.prototype,
-    'getGeometry',
-    ol.render.Feature.prototype.getGeometry);
-
-goog.exportProperty(
-    ol.render.Feature.prototype,
-    'getProperties',
-    ol.render.Feature.prototype.getProperties);
-
-goog.exportProperty(
-    ol.render.Feature.prototype,
-    'getType',
-    ol.render.Feature.prototype.getType);
-
-goog.exportSymbol(
-    'ol.render.VectorContext',
-    ol.render.VectorContext,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'setStyle',
-    ol.render.webgl.Immediate.prototype.setStyle);
-
-goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawGeometry',
-    ol.render.webgl.Immediate.prototype.drawGeometry);
-
-goog.exportProperty(
-    ol.render.webgl.Immediate.prototype,
-    'drawFeature',
-    ol.render.webgl.Immediate.prototype.drawFeature);
-
-goog.exportProperty(
-    ol.render.canvas.Immediate.prototype,
-    'drawCircle',
-    ol.render.canvas.Immediate.prototype.drawCircle);
-
-goog.exportProperty(
-    ol.render.canvas.Immediate.prototype,
-    'setStyle',
-    ol.render.canvas.Immediate.prototype.setStyle);
-
-goog.exportProperty(
-    ol.render.canvas.Immediate.prototype,
-    'drawGeometry',
-    ol.render.canvas.Immediate.prototype.drawGeometry);
-
-goog.exportProperty(
-    ol.render.canvas.Immediate.prototype,
-    'drawFeature',
-    ol.render.canvas.Immediate.prototype.drawFeature);
-
-goog.exportSymbol(
-    'ol.proj.common.add',
-    ol.proj.common.add,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.METERS_PER_UNIT',
-    ol.proj.METERS_PER_UNIT,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.Projection',
-    ol.proj.Projection,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'getCode',
-    ol.proj.Projection.prototype.getCode);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'getExtent',
-    ol.proj.Projection.prototype.getExtent);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'getUnits',
-    ol.proj.Projection.prototype.getUnits);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'getMetersPerUnit',
-    ol.proj.Projection.prototype.getMetersPerUnit);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'getWorldExtent',
-    ol.proj.Projection.prototype.getWorldExtent);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'isGlobal',
-    ol.proj.Projection.prototype.isGlobal);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'setGlobal',
-    ol.proj.Projection.prototype.setGlobal);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'setExtent',
-    ol.proj.Projection.prototype.setExtent);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'setWorldExtent',
-    ol.proj.Projection.prototype.setWorldExtent);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'setGetPointResolution',
-    ol.proj.Projection.prototype.setGetPointResolution);
-
-goog.exportProperty(
-    ol.proj.Projection.prototype,
-    'getPointResolution',
-    ol.proj.Projection.prototype.getPointResolution);
-
-goog.exportSymbol(
-    'ol.proj.setProj4',
-    ol.proj.setProj4,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.addEquivalentProjections',
-    ol.proj.addEquivalentProjections,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.addProjection',
-    ol.proj.addProjection,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.addCoordinateTransforms',
-    ol.proj.addCoordinateTransforms,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.fromLonLat',
-    ol.proj.fromLonLat,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.toLonLat',
-    ol.proj.toLonLat,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.get',
-    ol.proj.get,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.equivalent',
-    ol.proj.equivalent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.getTransform',
-    ol.proj.getTransform,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.transform',
-    ol.proj.transform,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.proj.transformExtent',
-    ol.proj.transformExtent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.layer.Base',
-    ol.layer.Base,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getExtent',
-    ol.layer.Base.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getMaxResolution',
-    ol.layer.Base.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getMinResolution',
-    ol.layer.Base.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getOpacity',
-    ol.layer.Base.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getVisible',
-    ol.layer.Base.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getZIndex',
-    ol.layer.Base.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setExtent',
-    ol.layer.Base.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setMaxResolution',
-    ol.layer.Base.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setMinResolution',
-    ol.layer.Base.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setOpacity',
-    ol.layer.Base.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setVisible',
-    ol.layer.Base.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setZIndex',
-    ol.layer.Base.prototype.setZIndex);
-
-goog.exportSymbol(
-    'ol.layer.Group',
-    ol.layer.Group,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getLayers',
-    ol.layer.Group.prototype.getLayers);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setLayers',
-    ol.layer.Group.prototype.setLayers);
-
-goog.exportSymbol(
-    'ol.layer.Heatmap',
-    ol.layer.Heatmap,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getBlur',
-    ol.layer.Heatmap.prototype.getBlur);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getGradient',
-    ol.layer.Heatmap.prototype.getGradient);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getRadius',
-    ol.layer.Heatmap.prototype.getRadius);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setBlur',
-    ol.layer.Heatmap.prototype.setBlur);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setGradient',
-    ol.layer.Heatmap.prototype.setGradient);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setRadius',
-    ol.layer.Heatmap.prototype.setRadius);
-
-goog.exportSymbol(
-    'ol.layer.Image',
-    ol.layer.Image,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getSource',
-    ol.layer.Image.prototype.getSource);
-
-goog.exportSymbol(
-    'ol.layer.Layer',
-    ol.layer.Layer,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getSource',
-    ol.layer.Layer.prototype.getSource);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setMap',
-    ol.layer.Layer.prototype.setMap);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setSource',
-    ol.layer.Layer.prototype.setSource);
-
-goog.exportSymbol(
-    'ol.layer.Tile',
-    ol.layer.Tile,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getPreload',
-    ol.layer.Tile.prototype.getPreload);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getSource',
-    ol.layer.Tile.prototype.getSource);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setPreload',
-    ol.layer.Tile.prototype.setPreload);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getUseInterimTilesOnError',
-    ol.layer.Tile.prototype.getUseInterimTilesOnError);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setUseInterimTilesOnError',
-    ol.layer.Tile.prototype.setUseInterimTilesOnError);
-
-goog.exportSymbol(
-    'ol.layer.Vector',
-    ol.layer.Vector,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getSource',
-    ol.layer.Vector.prototype.getSource);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getStyle',
-    ol.layer.Vector.prototype.getStyle);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getStyleFunction',
-    ol.layer.Vector.prototype.getStyleFunction);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setStyle',
-    ol.layer.Vector.prototype.setStyle);
-
-goog.exportSymbol(
-    'ol.layer.VectorTile',
-    ol.layer.VectorTile,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getPreload',
-    ol.layer.VectorTile.prototype.getPreload);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getUseInterimTilesOnError',
-    ol.layer.VectorTile.prototype.getUseInterimTilesOnError);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setPreload',
-    ol.layer.VectorTile.prototype.setPreload);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setUseInterimTilesOnError',
-    ol.layer.VectorTile.prototype.setUseInterimTilesOnError);
-
-goog.exportSymbol(
-    'ol.interaction.DoubleClickZoom',
-    ol.interaction.DoubleClickZoom,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.DoubleClickZoom.handleEvent',
-    ol.interaction.DoubleClickZoom.handleEvent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.DragAndDrop',
-    ol.interaction.DragAndDrop,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.DragAndDrop.handleEvent',
-    ol.interaction.DragAndDrop.handleEvent,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.Event.prototype,
-    'features',
-    ol.interaction.DragAndDrop.Event.prototype.features);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.Event.prototype,
-    'file',
-    ol.interaction.DragAndDrop.Event.prototype.file);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.Event.prototype,
-    'projection',
-    ol.interaction.DragAndDrop.Event.prototype.projection);
-
-goog.exportSymbol(
-    'ol.interaction.DragBox',
-    ol.interaction.DragBox,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'getGeometry',
-    ol.interaction.DragBox.prototype.getGeometry);
-
-goog.exportProperty(
-    ol.interaction.DragBox.Event.prototype,
-    'coordinate',
-    ol.interaction.DragBox.Event.prototype.coordinate);
-
-goog.exportProperty(
-    ol.interaction.DragBox.Event.prototype,
-    'mapBrowserEvent',
-    ol.interaction.DragBox.Event.prototype.mapBrowserEvent);
-
-goog.exportSymbol(
-    'ol.interaction.DragPan',
-    ol.interaction.DragPan,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.DragRotate',
-    ol.interaction.DragRotate,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.DragRotateAndZoom',
-    ol.interaction.DragRotateAndZoom,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.DragZoom',
-    ol.interaction.DragZoom,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Draw',
-    ol.interaction.Draw,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Draw.handleEvent',
-    ol.interaction.Draw.handleEvent,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'removeLastPoint',
-    ol.interaction.Draw.prototype.removeLastPoint);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'finishDrawing',
-    ol.interaction.Draw.prototype.finishDrawing);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'extend',
-    ol.interaction.Draw.prototype.extend);
-
-goog.exportSymbol(
-    'ol.interaction.Draw.createRegularPolygon',
-    ol.interaction.Draw.createRegularPolygon,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Draw.createBox',
-    ol.interaction.Draw.createBox,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Draw.Event.prototype,
-    'feature',
-    ol.interaction.Draw.Event.prototype.feature);
-
-goog.exportSymbol(
-    'ol.interaction.Extent',
-    ol.interaction.Extent,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'getExtent',
-    ol.interaction.Extent.prototype.getExtent);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'setExtent',
-    ol.interaction.Extent.prototype.setExtent);
-
-goog.exportProperty(
-    ol.interaction.Extent.Event.prototype,
-    'extent_',
-    ol.interaction.Extent.Event.prototype.extent_);
-
-goog.exportSymbol(
-    'ol.interaction.defaults',
-    ol.interaction.defaults,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Interaction',
-    ol.interaction.Interaction,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'getActive',
-    ol.interaction.Interaction.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'getMap',
-    ol.interaction.Interaction.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'setActive',
-    ol.interaction.Interaction.prototype.setActive);
-
-goog.exportSymbol(
-    'ol.interaction.KeyboardPan',
-    ol.interaction.KeyboardPan,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.KeyboardPan.handleEvent',
-    ol.interaction.KeyboardPan.handleEvent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.KeyboardZoom',
-    ol.interaction.KeyboardZoom,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.KeyboardZoom.handleEvent',
-    ol.interaction.KeyboardZoom.handleEvent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Modify',
-    ol.interaction.Modify,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Modify.handleEvent',
-    ol.interaction.Modify.handleEvent,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'removePoint',
-    ol.interaction.Modify.prototype.removePoint);
-
-goog.exportProperty(
-    ol.interaction.Modify.Event.prototype,
-    'features',
-    ol.interaction.Modify.Event.prototype.features);
-
-goog.exportProperty(
-    ol.interaction.Modify.Event.prototype,
-    'mapBrowserEvent',
-    ol.interaction.Modify.Event.prototype.mapBrowserEvent);
-
-goog.exportSymbol(
-    'ol.interaction.MouseWheelZoom',
-    ol.interaction.MouseWheelZoom,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.MouseWheelZoom.handleEvent',
-    ol.interaction.MouseWheelZoom.handleEvent,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'setMouseAnchor',
-    ol.interaction.MouseWheelZoom.prototype.setMouseAnchor);
-
-goog.exportSymbol(
-    'ol.interaction.PinchRotate',
-    ol.interaction.PinchRotate,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.PinchZoom',
-    ol.interaction.PinchZoom,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Pointer',
-    ol.interaction.Pointer,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Pointer.handleEvent',
-    ol.interaction.Pointer.handleEvent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.interaction.Select',
-    ol.interaction.Select,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'getFeatures',
-    ol.interaction.Select.prototype.getFeatures);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'getLayer',
-    ol.interaction.Select.prototype.getLayer);
-
-goog.exportSymbol(
-    'ol.interaction.Select.handleEvent',
-    ol.interaction.Select.handleEvent,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'setMap',
-    ol.interaction.Select.prototype.setMap);
-
-goog.exportProperty(
-    ol.interaction.Select.Event.prototype,
-    'selected',
-    ol.interaction.Select.Event.prototype.selected);
-
-goog.exportProperty(
-    ol.interaction.Select.Event.prototype,
-    'deselected',
-    ol.interaction.Select.Event.prototype.deselected);
-
-goog.exportProperty(
-    ol.interaction.Select.Event.prototype,
-    'mapBrowserEvent',
-    ol.interaction.Select.Event.prototype.mapBrowserEvent);
-
-goog.exportSymbol(
-    'ol.interaction.Snap',
-    ol.interaction.Snap,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'addFeature',
-    ol.interaction.Snap.prototype.addFeature);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'removeFeature',
-    ol.interaction.Snap.prototype.removeFeature);
-
-goog.exportSymbol(
-    'ol.interaction.Translate',
-    ol.interaction.Translate,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.interaction.Translate.Event.prototype,
-    'features',
-    ol.interaction.Translate.Event.prototype.features);
-
-goog.exportProperty(
-    ol.interaction.Translate.Event.prototype,
-    'coordinate',
-    ol.interaction.Translate.Event.prototype.coordinate);
-
-goog.exportSymbol(
-    'ol.geom.Circle',
-    ol.geom.Circle,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'clone',
-    ol.geom.Circle.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getCenter',
-    ol.geom.Circle.prototype.getCenter);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getRadius',
-    ol.geom.Circle.prototype.getRadius);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getType',
-    ol.geom.Circle.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'intersectsExtent',
-    ol.geom.Circle.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'setCenter',
-    ol.geom.Circle.prototype.setCenter);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'setCenterAndRadius',
-    ol.geom.Circle.prototype.setCenterAndRadius);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'setRadius',
-    ol.geom.Circle.prototype.setRadius);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'transform',
-    ol.geom.Circle.prototype.transform);
-
-goog.exportSymbol(
-    'ol.geom.Geometry',
-    ol.geom.Geometry,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'getClosestPoint',
-    ol.geom.Geometry.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'intersectsCoordinate',
-    ol.geom.Geometry.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'getExtent',
-    ol.geom.Geometry.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'rotate',
-    ol.geom.Geometry.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'scale',
-    ol.geom.Geometry.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'simplify',
-    ol.geom.Geometry.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'transform',
-    ol.geom.Geometry.prototype.transform);
-
-goog.exportSymbol(
-    'ol.geom.GeometryCollection',
-    ol.geom.GeometryCollection,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'clone',
-    ol.geom.GeometryCollection.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'getGeometries',
-    ol.geom.GeometryCollection.prototype.getGeometries);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'getType',
-    ol.geom.GeometryCollection.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'intersectsExtent',
-    ol.geom.GeometryCollection.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'setGeometries',
-    ol.geom.GeometryCollection.prototype.setGeometries);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'applyTransform',
-    ol.geom.GeometryCollection.prototype.applyTransform);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'translate',
-    ol.geom.GeometryCollection.prototype.translate);
-
-goog.exportSymbol(
-    'ol.geom.LinearRing',
-    ol.geom.LinearRing,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'clone',
-    ol.geom.LinearRing.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getArea',
-    ol.geom.LinearRing.prototype.getArea);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getCoordinates',
-    ol.geom.LinearRing.prototype.getCoordinates);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getType',
-    ol.geom.LinearRing.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'setCoordinates',
-    ol.geom.LinearRing.prototype.setCoordinates);
-
-goog.exportSymbol(
-    'ol.geom.LineString',
-    ol.geom.LineString,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'appendCoordinate',
-    ol.geom.LineString.prototype.appendCoordinate);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'clone',
-    ol.geom.LineString.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'forEachSegment',
-    ol.geom.LineString.prototype.forEachSegment);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getCoordinateAtM',
-    ol.geom.LineString.prototype.getCoordinateAtM);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getCoordinates',
-    ol.geom.LineString.prototype.getCoordinates);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getCoordinateAt',
-    ol.geom.LineString.prototype.getCoordinateAt);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getLength',
-    ol.geom.LineString.prototype.getLength);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getType',
-    ol.geom.LineString.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'intersectsExtent',
-    ol.geom.LineString.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'setCoordinates',
-    ol.geom.LineString.prototype.setCoordinates);
-
-goog.exportSymbol(
-    'ol.geom.MultiLineString',
-    ol.geom.MultiLineString,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'appendLineString',
-    ol.geom.MultiLineString.prototype.appendLineString);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'clone',
-    ol.geom.MultiLineString.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getCoordinateAtM',
-    ol.geom.MultiLineString.prototype.getCoordinateAtM);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getCoordinates',
-    ol.geom.MultiLineString.prototype.getCoordinates);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getLineString',
-    ol.geom.MultiLineString.prototype.getLineString);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getLineStrings',
-    ol.geom.MultiLineString.prototype.getLineStrings);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getType',
-    ol.geom.MultiLineString.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'intersectsExtent',
-    ol.geom.MultiLineString.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'setCoordinates',
-    ol.geom.MultiLineString.prototype.setCoordinates);
-
-goog.exportSymbol(
-    'ol.geom.MultiPoint',
-    ol.geom.MultiPoint,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'appendPoint',
-    ol.geom.MultiPoint.prototype.appendPoint);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'clone',
-    ol.geom.MultiPoint.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getCoordinates',
-    ol.geom.MultiPoint.prototype.getCoordinates);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getPoint',
-    ol.geom.MultiPoint.prototype.getPoint);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getPoints',
-    ol.geom.MultiPoint.prototype.getPoints);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getType',
-    ol.geom.MultiPoint.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'intersectsExtent',
-    ol.geom.MultiPoint.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'setCoordinates',
-    ol.geom.MultiPoint.prototype.setCoordinates);
-
-goog.exportSymbol(
-    'ol.geom.MultiPolygon',
-    ol.geom.MultiPolygon,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'appendPolygon',
-    ol.geom.MultiPolygon.prototype.appendPolygon);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'clone',
-    ol.geom.MultiPolygon.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getArea',
-    ol.geom.MultiPolygon.prototype.getArea);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getCoordinates',
-    ol.geom.MultiPolygon.prototype.getCoordinates);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getInteriorPoints',
-    ol.geom.MultiPolygon.prototype.getInteriorPoints);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getPolygon',
-    ol.geom.MultiPolygon.prototype.getPolygon);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getPolygons',
-    ol.geom.MultiPolygon.prototype.getPolygons);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getType',
-    ol.geom.MultiPolygon.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'intersectsExtent',
-    ol.geom.MultiPolygon.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'setCoordinates',
-    ol.geom.MultiPolygon.prototype.setCoordinates);
-
-goog.exportSymbol(
-    'ol.geom.Point',
-    ol.geom.Point,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'clone',
-    ol.geom.Point.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getCoordinates',
-    ol.geom.Point.prototype.getCoordinates);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getType',
-    ol.geom.Point.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'intersectsExtent',
-    ol.geom.Point.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'setCoordinates',
-    ol.geom.Point.prototype.setCoordinates);
-
-goog.exportSymbol(
-    'ol.geom.Polygon',
-    ol.geom.Polygon,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'appendLinearRing',
-    ol.geom.Polygon.prototype.appendLinearRing);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'clone',
-    ol.geom.Polygon.prototype.clone);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getArea',
-    ol.geom.Polygon.prototype.getArea);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getCoordinates',
-    ol.geom.Polygon.prototype.getCoordinates);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getInteriorPoint',
-    ol.geom.Polygon.prototype.getInteriorPoint);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getLinearRingCount',
-    ol.geom.Polygon.prototype.getLinearRingCount);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getLinearRing',
-    ol.geom.Polygon.prototype.getLinearRing);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getLinearRings',
-    ol.geom.Polygon.prototype.getLinearRings);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getType',
-    ol.geom.Polygon.prototype.getType);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'intersectsExtent',
-    ol.geom.Polygon.prototype.intersectsExtent);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'setCoordinates',
-    ol.geom.Polygon.prototype.setCoordinates);
-
-goog.exportSymbol(
-    'ol.geom.Polygon.circular',
-    ol.geom.Polygon.circular,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.geom.Polygon.fromExtent',
-    ol.geom.Polygon.fromExtent,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.geom.Polygon.fromCircle',
-    ol.geom.Polygon.fromCircle,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.geom.SimpleGeometry',
-    ol.geom.SimpleGeometry,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getFirstCoordinate',
-    ol.geom.SimpleGeometry.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getLastCoordinate',
-    ol.geom.SimpleGeometry.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getLayout',
-    ol.geom.SimpleGeometry.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'applyTransform',
-    ol.geom.SimpleGeometry.prototype.applyTransform);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'translate',
-    ol.geom.SimpleGeometry.prototype.translate);
-
-goog.exportSymbol(
-    'ol.format.EsriJSON',
-    ol.format.EsriJSON,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'readFeature',
-    ol.format.EsriJSON.prototype.readFeature);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'readFeatures',
-    ol.format.EsriJSON.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'readGeometry',
-    ol.format.EsriJSON.prototype.readGeometry);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'readProjection',
-    ol.format.EsriJSON.prototype.readProjection);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'writeGeometry',
-    ol.format.EsriJSON.prototype.writeGeometry);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'writeGeometryObject',
-    ol.format.EsriJSON.prototype.writeGeometryObject);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'writeFeature',
-    ol.format.EsriJSON.prototype.writeFeature);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'writeFeatureObject',
-    ol.format.EsriJSON.prototype.writeFeatureObject);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'writeFeatures',
-    ol.format.EsriJSON.prototype.writeFeatures);
-
-goog.exportProperty(
-    ol.format.EsriJSON.prototype,
-    'writeFeaturesObject',
-    ol.format.EsriJSON.prototype.writeFeaturesObject);
-
-goog.exportSymbol(
-    'ol.format.Feature',
-    ol.format.Feature,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.GeoJSON',
-    ol.format.GeoJSON,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'readFeature',
-    ol.format.GeoJSON.prototype.readFeature);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'readFeatures',
-    ol.format.GeoJSON.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'readGeometry',
-    ol.format.GeoJSON.prototype.readGeometry);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'readProjection',
-    ol.format.GeoJSON.prototype.readProjection);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'writeFeature',
-    ol.format.GeoJSON.prototype.writeFeature);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'writeFeatureObject',
-    ol.format.GeoJSON.prototype.writeFeatureObject);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'writeFeatures',
-    ol.format.GeoJSON.prototype.writeFeatures);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'writeFeaturesObject',
-    ol.format.GeoJSON.prototype.writeFeaturesObject);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'writeGeometry',
-    ol.format.GeoJSON.prototype.writeGeometry);
-
-goog.exportProperty(
-    ol.format.GeoJSON.prototype,
-    'writeGeometryObject',
-    ol.format.GeoJSON.prototype.writeGeometryObject);
-
-goog.exportSymbol(
-    'ol.format.GML',
-    ol.format.GML,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.GML.prototype,
-    'writeFeatures',
-    ol.format.GML.prototype.writeFeatures);
-
-goog.exportProperty(
-    ol.format.GML.prototype,
-    'writeFeaturesNode',
-    ol.format.GML.prototype.writeFeaturesNode);
-
-goog.exportSymbol(
-    'ol.format.GML2',
-    ol.format.GML2,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.GML3',
-    ol.format.GML3,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.GML3.prototype,
-    'writeGeometryNode',
-    ol.format.GML3.prototype.writeGeometryNode);
-
-goog.exportProperty(
-    ol.format.GML3.prototype,
-    'writeFeatures',
-    ol.format.GML3.prototype.writeFeatures);
-
-goog.exportProperty(
-    ol.format.GML3.prototype,
-    'writeFeaturesNode',
-    ol.format.GML3.prototype.writeFeaturesNode);
-
-goog.exportProperty(
-    ol.format.GMLBase.prototype,
-    'readFeatures',
-    ol.format.GMLBase.prototype.readFeatures);
-
-goog.exportSymbol(
-    'ol.format.GPX',
-    ol.format.GPX,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.GPX.prototype,
-    'readFeature',
-    ol.format.GPX.prototype.readFeature);
-
-goog.exportProperty(
-    ol.format.GPX.prototype,
-    'readFeatures',
-    ol.format.GPX.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.GPX.prototype,
-    'readProjection',
-    ol.format.GPX.prototype.readProjection);
-
-goog.exportProperty(
-    ol.format.GPX.prototype,
-    'writeFeatures',
-    ol.format.GPX.prototype.writeFeatures);
-
-goog.exportProperty(
-    ol.format.GPX.prototype,
-    'writeFeaturesNode',
-    ol.format.GPX.prototype.writeFeaturesNode);
-
-goog.exportSymbol(
-    'ol.format.IGC',
-    ol.format.IGC,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.IGC.prototype,
-    'readFeature',
-    ol.format.IGC.prototype.readFeature);
-
-goog.exportProperty(
-    ol.format.IGC.prototype,
-    'readFeatures',
-    ol.format.IGC.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.IGC.prototype,
-    'readProjection',
-    ol.format.IGC.prototype.readProjection);
-
-goog.exportSymbol(
-    'ol.format.KML',
-    ol.format.KML,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.KML.prototype,
-    'readFeature',
-    ol.format.KML.prototype.readFeature);
-
-goog.exportProperty(
-    ol.format.KML.prototype,
-    'readFeatures',
-    ol.format.KML.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.KML.prototype,
-    'readName',
-    ol.format.KML.prototype.readName);
-
-goog.exportProperty(
-    ol.format.KML.prototype,
-    'readNetworkLinks',
-    ol.format.KML.prototype.readNetworkLinks);
-
-goog.exportProperty(
-    ol.format.KML.prototype,
-    'readProjection',
-    ol.format.KML.prototype.readProjection);
-
-goog.exportProperty(
-    ol.format.KML.prototype,
-    'writeFeatures',
-    ol.format.KML.prototype.writeFeatures);
-
-goog.exportProperty(
-    ol.format.KML.prototype,
-    'writeFeaturesNode',
-    ol.format.KML.prototype.writeFeaturesNode);
-
-goog.exportSymbol(
-    'ol.format.MVT',
-    ol.format.MVT,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.MVT.prototype,
-    'readFeatures',
-    ol.format.MVT.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.MVT.prototype,
-    'readProjection',
-    ol.format.MVT.prototype.readProjection);
-
-goog.exportProperty(
-    ol.format.MVT.prototype,
-    'setLayers',
-    ol.format.MVT.prototype.setLayers);
-
-goog.exportSymbol(
-    'ol.format.OSMXML',
-    ol.format.OSMXML,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.OSMXML.prototype,
-    'readFeatures',
-    ol.format.OSMXML.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.OSMXML.prototype,
-    'readProjection',
-    ol.format.OSMXML.prototype.readProjection);
-
-goog.exportSymbol(
-    'ol.format.Polyline',
-    ol.format.Polyline,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.Polyline.encodeDeltas',
-    ol.format.Polyline.encodeDeltas,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.Polyline.decodeDeltas',
-    ol.format.Polyline.decodeDeltas,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.Polyline.encodeFloats',
-    ol.format.Polyline.encodeFloats,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.Polyline.decodeFloats',
-    ol.format.Polyline.decodeFloats,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.Polyline.prototype,
-    'readFeature',
-    ol.format.Polyline.prototype.readFeature);
-
-goog.exportProperty(
-    ol.format.Polyline.prototype,
-    'readFeatures',
-    ol.format.Polyline.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.Polyline.prototype,
-    'readGeometry',
-    ol.format.Polyline.prototype.readGeometry);
-
-goog.exportProperty(
-    ol.format.Polyline.prototype,
-    'readProjection',
-    ol.format.Polyline.prototype.readProjection);
-
-goog.exportProperty(
-    ol.format.Polyline.prototype,
-    'writeGeometry',
-    ol.format.Polyline.prototype.writeGeometry);
-
-goog.exportSymbol(
-    'ol.format.TopoJSON',
-    ol.format.TopoJSON,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.TopoJSON.prototype,
-    'readFeatures',
-    ol.format.TopoJSON.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.TopoJSON.prototype,
-    'readProjection',
-    ol.format.TopoJSON.prototype.readProjection);
-
-goog.exportSymbol(
-    'ol.format.WFS',
-    ol.format.WFS,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.WFS.prototype,
-    'readFeatures',
-    ol.format.WFS.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.WFS.prototype,
-    'readTransactionResponse',
-    ol.format.WFS.prototype.readTransactionResponse);
-
-goog.exportProperty(
-    ol.format.WFS.prototype,
-    'readFeatureCollectionMetadata',
-    ol.format.WFS.prototype.readFeatureCollectionMetadata);
-
-goog.exportProperty(
-    ol.format.WFS.prototype,
-    'writeGetFeature',
-    ol.format.WFS.prototype.writeGetFeature);
-
-goog.exportProperty(
-    ol.format.WFS.prototype,
-    'writeTransaction',
-    ol.format.WFS.prototype.writeTransaction);
-
-goog.exportProperty(
-    ol.format.WFS.prototype,
-    'readProjection',
-    ol.format.WFS.prototype.readProjection);
-
-goog.exportSymbol(
-    'ol.format.WKT',
-    ol.format.WKT,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.WKT.prototype,
-    'readFeature',
-    ol.format.WKT.prototype.readFeature);
-
-goog.exportProperty(
-    ol.format.WKT.prototype,
-    'readFeatures',
-    ol.format.WKT.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.WKT.prototype,
-    'readGeometry',
-    ol.format.WKT.prototype.readGeometry);
-
-goog.exportProperty(
-    ol.format.WKT.prototype,
-    'writeFeature',
-    ol.format.WKT.prototype.writeFeature);
-
-goog.exportProperty(
-    ol.format.WKT.prototype,
-    'writeFeatures',
-    ol.format.WKT.prototype.writeFeatures);
-
-goog.exportProperty(
-    ol.format.WKT.prototype,
-    'writeGeometry',
-    ol.format.WKT.prototype.writeGeometry);
-
-goog.exportSymbol(
-    'ol.format.WMSCapabilities',
-    ol.format.WMSCapabilities,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.WMSCapabilities.prototype,
-    'read',
-    ol.format.WMSCapabilities.prototype.read);
-
-goog.exportSymbol(
-    'ol.format.WMSGetFeatureInfo',
-    ol.format.WMSGetFeatureInfo,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.WMSGetFeatureInfo.prototype,
-    'readFeatures',
-    ol.format.WMSGetFeatureInfo.prototype.readFeatures);
-
-goog.exportSymbol(
-    'ol.format.WMTSCapabilities',
-    ol.format.WMTSCapabilities,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.format.WMTSCapabilities.prototype,
-    'read',
-    ol.format.WMTSCapabilities.prototype.read);
-
-goog.exportSymbol(
-    'ol.format.filter.And',
-    ol.format.filter.And,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Bbox',
-    ol.format.filter.Bbox,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Comparison',
-    ol.format.filter.Comparison,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.ComparisonBinary',
-    ol.format.filter.ComparisonBinary,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.EqualTo',
-    ol.format.filter.EqualTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Filter',
-    ol.format.filter.Filter,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.GreaterThan',
-    ol.format.filter.GreaterThan,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.GreaterThanOrEqualTo',
-    ol.format.filter.GreaterThanOrEqualTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.and',
-    ol.format.filter.and,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.or',
-    ol.format.filter.or,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.not',
-    ol.format.filter.not,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.bbox',
-    ol.format.filter.bbox,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.intersects',
-    ol.format.filter.intersects,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.within',
-    ol.format.filter.within,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.equalTo',
-    ol.format.filter.equalTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.notEqualTo',
-    ol.format.filter.notEqualTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.lessThan',
-    ol.format.filter.lessThan,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.lessThanOrEqualTo',
-    ol.format.filter.lessThanOrEqualTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.greaterThan',
-    ol.format.filter.greaterThan,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.greaterThanOrEqualTo',
-    ol.format.filter.greaterThanOrEqualTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.isNull',
-    ol.format.filter.isNull,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.between',
-    ol.format.filter.between,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.like',
-    ol.format.filter.like,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Intersects',
-    ol.format.filter.Intersects,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.IsBetween',
-    ol.format.filter.IsBetween,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.IsLike',
-    ol.format.filter.IsLike,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.IsNull',
-    ol.format.filter.IsNull,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.LessThan',
-    ol.format.filter.LessThan,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.LessThanOrEqualTo',
-    ol.format.filter.LessThanOrEqualTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Not',
-    ol.format.filter.Not,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.NotEqualTo',
-    ol.format.filter.NotEqualTo,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Or',
-    ol.format.filter.Or,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Spatial',
-    ol.format.filter.Spatial,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.format.filter.Within',
-    ol.format.filter.Within,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.altKeyOnly',
-    ol.events.condition.altKeyOnly,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.altShiftKeysOnly',
-    ol.events.condition.altShiftKeysOnly,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.always',
-    ol.events.condition.always,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.click',
-    ol.events.condition.click,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.never',
-    ol.events.condition.never,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.pointerMove',
-    ol.events.condition.pointerMove,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.singleClick',
-    ol.events.condition.singleClick,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.doubleClick',
-    ol.events.condition.doubleClick,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.noModifierKeys',
-    ol.events.condition.noModifierKeys,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.platformModifierKeyOnly',
-    ol.events.condition.platformModifierKeyOnly,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.shiftKeyOnly',
-    ol.events.condition.shiftKeyOnly,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.targetNotEditable',
-    ol.events.condition.targetNotEditable,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.mouseOnly',
-    ol.events.condition.mouseOnly,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.events.condition.primaryAction',
-    ol.events.condition.primaryAction,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.events.Event.prototype,
-    'type',
-    ol.events.Event.prototype.type);
-
-goog.exportProperty(
-    ol.events.Event.prototype,
-    'target',
-    ol.events.Event.prototype.target);
-
-goog.exportProperty(
-    ol.events.Event.prototype,
-    'preventDefault',
-    ol.events.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.events.Event.prototype,
-    'stopPropagation',
-    ol.events.Event.prototype.stopPropagation);
-
-goog.exportSymbol(
-    'ol.control.Attribution',
-    ol.control.Attribution,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.Attribution.render',
-    ol.control.Attribution.render,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'getCollapsible',
-    ol.control.Attribution.prototype.getCollapsible);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'setCollapsible',
-    ol.control.Attribution.prototype.setCollapsible);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'setCollapsed',
-    ol.control.Attribution.prototype.setCollapsed);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'getCollapsed',
-    ol.control.Attribution.prototype.getCollapsed);
-
-goog.exportSymbol(
-    'ol.control.Control',
-    ol.control.Control,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'getMap',
-    ol.control.Control.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'setMap',
-    ol.control.Control.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'setTarget',
-    ol.control.Control.prototype.setTarget);
-
-goog.exportSymbol(
-    'ol.control.FullScreen',
-    ol.control.FullScreen,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.defaults',
-    ol.control.defaults,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.MousePosition',
-    ol.control.MousePosition,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.MousePosition.render',
-    ol.control.MousePosition.render,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getCoordinateFormat',
-    ol.control.MousePosition.prototype.getCoordinateFormat);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getProjection',
-    ol.control.MousePosition.prototype.getProjection);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'setCoordinateFormat',
-    ol.control.MousePosition.prototype.setCoordinateFormat);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'setProjection',
-    ol.control.MousePosition.prototype.setProjection);
-
-goog.exportSymbol(
-    'ol.control.OverviewMap',
-    ol.control.OverviewMap,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.OverviewMap.render',
-    ol.control.OverviewMap.render,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'getCollapsible',
-    ol.control.OverviewMap.prototype.getCollapsible);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'setCollapsible',
-    ol.control.OverviewMap.prototype.setCollapsible);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'setCollapsed',
-    ol.control.OverviewMap.prototype.setCollapsed);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'getCollapsed',
-    ol.control.OverviewMap.prototype.getCollapsed);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'getOverviewMap',
-    ol.control.OverviewMap.prototype.getOverviewMap);
-
-goog.exportSymbol(
-    'ol.control.Rotate',
-    ol.control.Rotate,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.Rotate.render',
-    ol.control.Rotate.render,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.ScaleLine',
-    ol.control.ScaleLine,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'getUnits',
-    ol.control.ScaleLine.prototype.getUnits);
-
-goog.exportSymbol(
-    'ol.control.ScaleLine.render',
-    ol.control.ScaleLine.render,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'setUnits',
-    ol.control.ScaleLine.prototype.setUnits);
-
-goog.exportSymbol(
-    'ol.control.Zoom',
-    ol.control.Zoom,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.ZoomSlider',
-    ol.control.ZoomSlider,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.ZoomSlider.render',
-    ol.control.ZoomSlider.render,
-    OPENLAYERS);
-
-goog.exportSymbol(
-    'ol.control.ZoomToExtent',
-    ol.control.ZoomToExtent,
-    OPENLAYERS);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'changed',
-    ol.Object.prototype.changed);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'dispatchEvent',
-    ol.Object.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'getRevision',
-    ol.Object.prototype.getRevision);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'on',
-    ol.Object.prototype.on);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'once',
-    ol.Object.prototype.once);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'un',
-    ol.Object.prototype.un);
-
-goog.exportProperty(
-    ol.Object.prototype,
-    'unByKey',
-    ol.Object.prototype.unByKey);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'get',
-    ol.Collection.prototype.get);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'getKeys',
-    ol.Collection.prototype.getKeys);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'getProperties',
-    ol.Collection.prototype.getProperties);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'set',
-    ol.Collection.prototype.set);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'setProperties',
-    ol.Collection.prototype.setProperties);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'unset',
-    ol.Collection.prototype.unset);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'changed',
-    ol.Collection.prototype.changed);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'dispatchEvent',
-    ol.Collection.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'getRevision',
-    ol.Collection.prototype.getRevision);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'on',
-    ol.Collection.prototype.on);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'once',
-    ol.Collection.prototype.once);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'un',
-    ol.Collection.prototype.un);
-
-goog.exportProperty(
-    ol.Collection.prototype,
-    'unByKey',
-    ol.Collection.prototype.unByKey);
-
-goog.exportProperty(
-    ol.Collection.Event.prototype,
-    'type',
-    ol.Collection.Event.prototype.type);
-
-goog.exportProperty(
-    ol.Collection.Event.prototype,
-    'target',
-    ol.Collection.Event.prototype.target);
-
-goog.exportProperty(
-    ol.Collection.Event.prototype,
-    'preventDefault',
-    ol.Collection.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.Collection.Event.prototype,
-    'stopPropagation',
-    ol.Collection.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'get',
-    ol.DeviceOrientation.prototype.get);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getKeys',
-    ol.DeviceOrientation.prototype.getKeys);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getProperties',
-    ol.DeviceOrientation.prototype.getProperties);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'set',
-    ol.DeviceOrientation.prototype.set);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'setProperties',
-    ol.DeviceOrientation.prototype.setProperties);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'unset',
-    ol.DeviceOrientation.prototype.unset);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'changed',
-    ol.DeviceOrientation.prototype.changed);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'dispatchEvent',
-    ol.DeviceOrientation.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'getRevision',
-    ol.DeviceOrientation.prototype.getRevision);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'on',
-    ol.DeviceOrientation.prototype.on);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'once',
-    ol.DeviceOrientation.prototype.once);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'un',
-    ol.DeviceOrientation.prototype.un);
-
-goog.exportProperty(
-    ol.DeviceOrientation.prototype,
-    'unByKey',
-    ol.DeviceOrientation.prototype.unByKey);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'get',
-    ol.Feature.prototype.get);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getKeys',
-    ol.Feature.prototype.getKeys);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getProperties',
-    ol.Feature.prototype.getProperties);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'set',
-    ol.Feature.prototype.set);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'setProperties',
-    ol.Feature.prototype.setProperties);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'unset',
-    ol.Feature.prototype.unset);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'changed',
-    ol.Feature.prototype.changed);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'dispatchEvent',
-    ol.Feature.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'getRevision',
-    ol.Feature.prototype.getRevision);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'on',
-    ol.Feature.prototype.on);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'once',
-    ol.Feature.prototype.once);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'un',
-    ol.Feature.prototype.un);
-
-goog.exportProperty(
-    ol.Feature.prototype,
-    'unByKey',
-    ol.Feature.prototype.unByKey);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'get',
-    ol.Geolocation.prototype.get);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getKeys',
-    ol.Geolocation.prototype.getKeys);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getProperties',
-    ol.Geolocation.prototype.getProperties);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'set',
-    ol.Geolocation.prototype.set);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'setProperties',
-    ol.Geolocation.prototype.setProperties);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'unset',
-    ol.Geolocation.prototype.unset);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'changed',
-    ol.Geolocation.prototype.changed);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'dispatchEvent',
-    ol.Geolocation.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'getRevision',
-    ol.Geolocation.prototype.getRevision);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'on',
-    ol.Geolocation.prototype.on);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'once',
-    ol.Geolocation.prototype.once);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'un',
-    ol.Geolocation.prototype.un);
-
-goog.exportProperty(
-    ol.Geolocation.prototype,
-    'unByKey',
-    ol.Geolocation.prototype.unByKey);
-
-goog.exportProperty(
-    ol.ImageTile.prototype,
-    'getTileCoord',
-    ol.ImageTile.prototype.getTileCoord);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'get',
-    ol.Map.prototype.get);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getKeys',
-    ol.Map.prototype.getKeys);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getProperties',
-    ol.Map.prototype.getProperties);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'set',
-    ol.Map.prototype.set);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'setProperties',
-    ol.Map.prototype.setProperties);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'unset',
-    ol.Map.prototype.unset);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'changed',
-    ol.Map.prototype.changed);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'dispatchEvent',
-    ol.Map.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'getRevision',
-    ol.Map.prototype.getRevision);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'on',
-    ol.Map.prototype.on);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'once',
-    ol.Map.prototype.once);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'un',
-    ol.Map.prototype.un);
-
-goog.exportProperty(
-    ol.Map.prototype,
-    'unByKey',
-    ol.Map.prototype.unByKey);
-
-goog.exportProperty(
-    ol.MapEvent.prototype,
-    'type',
-    ol.MapEvent.prototype.type);
-
-goog.exportProperty(
-    ol.MapEvent.prototype,
-    'target',
-    ol.MapEvent.prototype.target);
-
-goog.exportProperty(
-    ol.MapEvent.prototype,
-    'preventDefault',
-    ol.MapEvent.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.MapEvent.prototype,
-    'stopPropagation',
-    ol.MapEvent.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'map',
-    ol.MapBrowserEvent.prototype.map);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'frameState',
-    ol.MapBrowserEvent.prototype.frameState);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'type',
-    ol.MapBrowserEvent.prototype.type);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'target',
-    ol.MapBrowserEvent.prototype.target);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'preventDefault',
-    ol.MapBrowserEvent.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.MapBrowserEvent.prototype,
-    'stopPropagation',
-    ol.MapBrowserEvent.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'originalEvent',
-    ol.MapBrowserPointerEvent.prototype.originalEvent);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'pixel',
-    ol.MapBrowserPointerEvent.prototype.pixel);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'coordinate',
-    ol.MapBrowserPointerEvent.prototype.coordinate);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'dragging',
-    ol.MapBrowserPointerEvent.prototype.dragging);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'preventDefault',
-    ol.MapBrowserPointerEvent.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'stopPropagation',
-    ol.MapBrowserPointerEvent.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'map',
-    ol.MapBrowserPointerEvent.prototype.map);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'frameState',
-    ol.MapBrowserPointerEvent.prototype.frameState);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'type',
-    ol.MapBrowserPointerEvent.prototype.type);
-
-goog.exportProperty(
-    ol.MapBrowserPointerEvent.prototype,
-    'target',
-    ol.MapBrowserPointerEvent.prototype.target);
-
-goog.exportProperty(
-    ol.ObjectEvent.prototype,
-    'type',
-    ol.ObjectEvent.prototype.type);
-
-goog.exportProperty(
-    ol.ObjectEvent.prototype,
-    'target',
-    ol.ObjectEvent.prototype.target);
-
-goog.exportProperty(
-    ol.ObjectEvent.prototype,
-    'preventDefault',
-    ol.ObjectEvent.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.ObjectEvent.prototype,
-    'stopPropagation',
-    ol.ObjectEvent.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'get',
-    ol.Overlay.prototype.get);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getKeys',
-    ol.Overlay.prototype.getKeys);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getProperties',
-    ol.Overlay.prototype.getProperties);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'set',
-    ol.Overlay.prototype.set);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'setProperties',
-    ol.Overlay.prototype.setProperties);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'unset',
-    ol.Overlay.prototype.unset);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'changed',
-    ol.Overlay.prototype.changed);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'dispatchEvent',
-    ol.Overlay.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'getRevision',
-    ol.Overlay.prototype.getRevision);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'on',
-    ol.Overlay.prototype.on);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'once',
-    ol.Overlay.prototype.once);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'un',
-    ol.Overlay.prototype.un);
-
-goog.exportProperty(
-    ol.Overlay.prototype,
-    'unByKey',
-    ol.Overlay.prototype.unByKey);
-
-goog.exportProperty(
-    ol.VectorTile.prototype,
-    'getTileCoord',
-    ol.VectorTile.prototype.getTileCoord);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'get',
-    ol.View.prototype.get);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getKeys',
-    ol.View.prototype.getKeys);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getProperties',
-    ol.View.prototype.getProperties);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'set',
-    ol.View.prototype.set);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'setProperties',
-    ol.View.prototype.setProperties);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'unset',
-    ol.View.prototype.unset);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'changed',
-    ol.View.prototype.changed);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'dispatchEvent',
-    ol.View.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'getRevision',
-    ol.View.prototype.getRevision);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'on',
-    ol.View.prototype.on);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'once',
-    ol.View.prototype.once);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'un',
-    ol.View.prototype.un);
-
-goog.exportProperty(
-    ol.View.prototype,
-    'unByKey',
-    ol.View.prototype.unByKey);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'forEachTileCoord',
-    ol.tilegrid.WMTS.prototype.forEachTileCoord);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getMaxZoom',
-    ol.tilegrid.WMTS.prototype.getMaxZoom);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getMinZoom',
-    ol.tilegrid.WMTS.prototype.getMinZoom);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getOrigin',
-    ol.tilegrid.WMTS.prototype.getOrigin);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getResolution',
-    ol.tilegrid.WMTS.prototype.getResolution);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getResolutions',
-    ol.tilegrid.WMTS.prototype.getResolutions);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getTileCoordExtent',
-    ol.tilegrid.WMTS.prototype.getTileCoordExtent);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getTileCoordForCoordAndResolution',
-    ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndResolution);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getTileCoordForCoordAndZ',
-    ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndZ);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getTileSize',
-    ol.tilegrid.WMTS.prototype.getTileSize);
-
-goog.exportProperty(
-    ol.tilegrid.WMTS.prototype,
-    'getZForResolution',
-    ol.tilegrid.WMTS.prototype.getZForResolution);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getOpacity',
-    ol.style.Circle.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getRotateWithView',
-    ol.style.Circle.prototype.getRotateWithView);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getRotation',
-    ol.style.Circle.prototype.getRotation);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getScale',
-    ol.style.Circle.prototype.getScale);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'getSnapToPixel',
-    ol.style.Circle.prototype.getSnapToPixel);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'setOpacity',
-    ol.style.Circle.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'setRotation',
-    ol.style.Circle.prototype.setRotation);
-
-goog.exportProperty(
-    ol.style.Circle.prototype,
-    'setScale',
-    ol.style.Circle.prototype.setScale);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getOpacity',
-    ol.style.Icon.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getRotateWithView',
-    ol.style.Icon.prototype.getRotateWithView);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getRotation',
-    ol.style.Icon.prototype.getRotation);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getScale',
-    ol.style.Icon.prototype.getScale);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'getSnapToPixel',
-    ol.style.Icon.prototype.getSnapToPixel);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'setOpacity',
-    ol.style.Icon.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'setRotation',
-    ol.style.Icon.prototype.setRotation);
-
-goog.exportProperty(
-    ol.style.Icon.prototype,
-    'setScale',
-    ol.style.Icon.prototype.setScale);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getOpacity',
-    ol.style.RegularShape.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getRotateWithView',
-    ol.style.RegularShape.prototype.getRotateWithView);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getRotation',
-    ol.style.RegularShape.prototype.getRotation);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getScale',
-    ol.style.RegularShape.prototype.getScale);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'getSnapToPixel',
-    ol.style.RegularShape.prototype.getSnapToPixel);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'setOpacity',
-    ol.style.RegularShape.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'setRotation',
-    ol.style.RegularShape.prototype.setRotation);
-
-goog.exportProperty(
-    ol.style.RegularShape.prototype,
-    'setScale',
-    ol.style.RegularShape.prototype.setScale);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'get',
-    ol.source.Source.prototype.get);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'getKeys',
-    ol.source.Source.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'getProperties',
-    ol.source.Source.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'set',
-    ol.source.Source.prototype.set);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'setProperties',
-    ol.source.Source.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'unset',
-    ol.source.Source.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'changed',
-    ol.source.Source.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'dispatchEvent',
-    ol.source.Source.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'getRevision',
-    ol.source.Source.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'on',
-    ol.source.Source.prototype.on);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'once',
-    ol.source.Source.prototype.once);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'un',
-    ol.source.Source.prototype.un);
-
-goog.exportProperty(
-    ol.source.Source.prototype,
-    'unByKey',
-    ol.source.Source.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getAttributions',
-    ol.source.Tile.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getLogo',
-    ol.source.Tile.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getProjection',
-    ol.source.Tile.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getState',
-    ol.source.Tile.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'refresh',
-    ol.source.Tile.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'setAttributions',
-    ol.source.Tile.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'get',
-    ol.source.Tile.prototype.get);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getKeys',
-    ol.source.Tile.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getProperties',
-    ol.source.Tile.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'set',
-    ol.source.Tile.prototype.set);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'setProperties',
-    ol.source.Tile.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'unset',
-    ol.source.Tile.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'changed',
-    ol.source.Tile.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'dispatchEvent',
-    ol.source.Tile.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'getRevision',
-    ol.source.Tile.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'on',
-    ol.source.Tile.prototype.on);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'once',
-    ol.source.Tile.prototype.once);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'un',
-    ol.source.Tile.prototype.un);
-
-goog.exportProperty(
-    ol.source.Tile.prototype,
-    'unByKey',
-    ol.source.Tile.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getTileGrid',
-    ol.source.UrlTile.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'refresh',
-    ol.source.UrlTile.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getAttributions',
-    ol.source.UrlTile.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getLogo',
-    ol.source.UrlTile.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getProjection',
-    ol.source.UrlTile.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getState',
-    ol.source.UrlTile.prototype.getState);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'setAttributions',
-    ol.source.UrlTile.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'get',
-    ol.source.UrlTile.prototype.get);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getKeys',
-    ol.source.UrlTile.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getProperties',
-    ol.source.UrlTile.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'set',
-    ol.source.UrlTile.prototype.set);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'setProperties',
-    ol.source.UrlTile.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'unset',
-    ol.source.UrlTile.prototype.unset);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'changed',
-    ol.source.UrlTile.prototype.changed);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'dispatchEvent',
-    ol.source.UrlTile.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'getRevision',
-    ol.source.UrlTile.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'on',
-    ol.source.UrlTile.prototype.on);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'once',
-    ol.source.UrlTile.prototype.once);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'un',
-    ol.source.UrlTile.prototype.un);
-
-goog.exportProperty(
-    ol.source.UrlTile.prototype,
-    'unByKey',
-    ol.source.UrlTile.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getTileLoadFunction',
-    ol.source.TileImage.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getTileUrlFunction',
-    ol.source.TileImage.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getUrls',
-    ol.source.TileImage.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setTileLoadFunction',
-    ol.source.TileImage.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setTileUrlFunction',
-    ol.source.TileImage.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setUrl',
-    ol.source.TileImage.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setUrls',
-    ol.source.TileImage.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getTileGrid',
-    ol.source.TileImage.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'refresh',
-    ol.source.TileImage.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getAttributions',
-    ol.source.TileImage.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getLogo',
-    ol.source.TileImage.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getProjection',
-    ol.source.TileImage.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getState',
-    ol.source.TileImage.prototype.getState);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setAttributions',
-    ol.source.TileImage.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'get',
-    ol.source.TileImage.prototype.get);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getKeys',
-    ol.source.TileImage.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getProperties',
-    ol.source.TileImage.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'set',
-    ol.source.TileImage.prototype.set);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'setProperties',
-    ol.source.TileImage.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'unset',
-    ol.source.TileImage.prototype.unset);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'changed',
-    ol.source.TileImage.prototype.changed);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'dispatchEvent',
-    ol.source.TileImage.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'getRevision',
-    ol.source.TileImage.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'on',
-    ol.source.TileImage.prototype.on);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'once',
-    ol.source.TileImage.prototype.once);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'un',
-    ol.source.TileImage.prototype.un);
-
-goog.exportProperty(
-    ol.source.TileImage.prototype,
-    'unByKey',
-    ol.source.TileImage.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.BingMaps.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setTileGridForProjection',
-    ol.source.BingMaps.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getTileLoadFunction',
-    ol.source.BingMaps.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getTileUrlFunction',
-    ol.source.BingMaps.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getUrls',
-    ol.source.BingMaps.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setTileLoadFunction',
-    ol.source.BingMaps.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setTileUrlFunction',
-    ol.source.BingMaps.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setUrl',
-    ol.source.BingMaps.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setUrls',
-    ol.source.BingMaps.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getTileGrid',
-    ol.source.BingMaps.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'refresh',
-    ol.source.BingMaps.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getAttributions',
-    ol.source.BingMaps.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getLogo',
-    ol.source.BingMaps.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getProjection',
-    ol.source.BingMaps.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getState',
-    ol.source.BingMaps.prototype.getState);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setAttributions',
-    ol.source.BingMaps.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'get',
-    ol.source.BingMaps.prototype.get);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getKeys',
-    ol.source.BingMaps.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getProperties',
-    ol.source.BingMaps.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'set',
-    ol.source.BingMaps.prototype.set);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'setProperties',
-    ol.source.BingMaps.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'unset',
-    ol.source.BingMaps.prototype.unset);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'changed',
-    ol.source.BingMaps.prototype.changed);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'dispatchEvent',
-    ol.source.BingMaps.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'getRevision',
-    ol.source.BingMaps.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'on',
-    ol.source.BingMaps.prototype.on);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'once',
-    ol.source.BingMaps.prototype.once);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'un',
-    ol.source.BingMaps.prototype.un);
-
-goog.exportProperty(
-    ol.source.BingMaps.prototype,
-    'unByKey',
-    ol.source.BingMaps.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.XYZ.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setTileGridForProjection',
-    ol.source.XYZ.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getTileLoadFunction',
-    ol.source.XYZ.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getTileUrlFunction',
-    ol.source.XYZ.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getUrls',
-    ol.source.XYZ.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setTileLoadFunction',
-    ol.source.XYZ.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setTileUrlFunction',
-    ol.source.XYZ.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setUrl',
-    ol.source.XYZ.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setUrls',
-    ol.source.XYZ.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getTileGrid',
-    ol.source.XYZ.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'refresh',
-    ol.source.XYZ.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getAttributions',
-    ol.source.XYZ.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getLogo',
-    ol.source.XYZ.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getProjection',
-    ol.source.XYZ.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getState',
-    ol.source.XYZ.prototype.getState);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setAttributions',
-    ol.source.XYZ.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'get',
-    ol.source.XYZ.prototype.get);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getKeys',
-    ol.source.XYZ.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getProperties',
-    ol.source.XYZ.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'set',
-    ol.source.XYZ.prototype.set);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'setProperties',
-    ol.source.XYZ.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'unset',
-    ol.source.XYZ.prototype.unset);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'changed',
-    ol.source.XYZ.prototype.changed);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'dispatchEvent',
-    ol.source.XYZ.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'getRevision',
-    ol.source.XYZ.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'on',
-    ol.source.XYZ.prototype.on);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'once',
-    ol.source.XYZ.prototype.once);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'un',
-    ol.source.XYZ.prototype.un);
-
-goog.exportProperty(
-    ol.source.XYZ.prototype,
-    'unByKey',
-    ol.source.XYZ.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.CartoDB.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setTileGridForProjection',
-    ol.source.CartoDB.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getTileLoadFunction',
-    ol.source.CartoDB.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getTileUrlFunction',
-    ol.source.CartoDB.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getUrls',
-    ol.source.CartoDB.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setTileLoadFunction',
-    ol.source.CartoDB.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setTileUrlFunction',
-    ol.source.CartoDB.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setUrl',
-    ol.source.CartoDB.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setUrls',
-    ol.source.CartoDB.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getTileGrid',
-    ol.source.CartoDB.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'refresh',
-    ol.source.CartoDB.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getAttributions',
-    ol.source.CartoDB.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getLogo',
-    ol.source.CartoDB.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getProjection',
-    ol.source.CartoDB.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getState',
-    ol.source.CartoDB.prototype.getState);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setAttributions',
-    ol.source.CartoDB.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'get',
-    ol.source.CartoDB.prototype.get);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getKeys',
-    ol.source.CartoDB.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getProperties',
-    ol.source.CartoDB.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'set',
-    ol.source.CartoDB.prototype.set);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'setProperties',
-    ol.source.CartoDB.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'unset',
-    ol.source.CartoDB.prototype.unset);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'changed',
-    ol.source.CartoDB.prototype.changed);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'dispatchEvent',
-    ol.source.CartoDB.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'getRevision',
-    ol.source.CartoDB.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'on',
-    ol.source.CartoDB.prototype.on);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'once',
-    ol.source.CartoDB.prototype.once);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'un',
-    ol.source.CartoDB.prototype.un);
-
-goog.exportProperty(
-    ol.source.CartoDB.prototype,
-    'unByKey',
-    ol.source.CartoDB.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getAttributions',
-    ol.source.Vector.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getLogo',
-    ol.source.Vector.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getProjection',
-    ol.source.Vector.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getState',
-    ol.source.Vector.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'refresh',
-    ol.source.Vector.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'setAttributions',
-    ol.source.Vector.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'get',
-    ol.source.Vector.prototype.get);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getKeys',
-    ol.source.Vector.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getProperties',
-    ol.source.Vector.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'set',
-    ol.source.Vector.prototype.set);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'setProperties',
-    ol.source.Vector.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'unset',
-    ol.source.Vector.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'changed',
-    ol.source.Vector.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'dispatchEvent',
-    ol.source.Vector.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'getRevision',
-    ol.source.Vector.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'on',
-    ol.source.Vector.prototype.on);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'once',
-    ol.source.Vector.prototype.once);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'un',
-    ol.source.Vector.prototype.un);
-
-goog.exportProperty(
-    ol.source.Vector.prototype,
-    'unByKey',
-    ol.source.Vector.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'addFeature',
-    ol.source.Cluster.prototype.addFeature);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'addFeatures',
-    ol.source.Cluster.prototype.addFeatures);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'clear',
-    ol.source.Cluster.prototype.clear);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'forEachFeature',
-    ol.source.Cluster.prototype.forEachFeature);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'forEachFeatureInExtent',
-    ol.source.Cluster.prototype.forEachFeatureInExtent);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'forEachFeatureIntersectingExtent',
-    ol.source.Cluster.prototype.forEachFeatureIntersectingExtent);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getFeaturesCollection',
-    ol.source.Cluster.prototype.getFeaturesCollection);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getFeatures',
-    ol.source.Cluster.prototype.getFeatures);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getFeaturesAtCoordinate',
-    ol.source.Cluster.prototype.getFeaturesAtCoordinate);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getFeaturesInExtent',
-    ol.source.Cluster.prototype.getFeaturesInExtent);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getClosestFeatureToCoordinate',
-    ol.source.Cluster.prototype.getClosestFeatureToCoordinate);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getExtent',
-    ol.source.Cluster.prototype.getExtent);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getFeatureById',
-    ol.source.Cluster.prototype.getFeatureById);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getFormat',
-    ol.source.Cluster.prototype.getFormat);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getUrl',
-    ol.source.Cluster.prototype.getUrl);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'removeFeature',
-    ol.source.Cluster.prototype.removeFeature);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getAttributions',
-    ol.source.Cluster.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getLogo',
-    ol.source.Cluster.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getProjection',
-    ol.source.Cluster.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getState',
-    ol.source.Cluster.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'refresh',
-    ol.source.Cluster.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'setAttributions',
-    ol.source.Cluster.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'get',
-    ol.source.Cluster.prototype.get);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getKeys',
-    ol.source.Cluster.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getProperties',
-    ol.source.Cluster.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'set',
-    ol.source.Cluster.prototype.set);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'setProperties',
-    ol.source.Cluster.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'unset',
-    ol.source.Cluster.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'changed',
-    ol.source.Cluster.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'dispatchEvent',
-    ol.source.Cluster.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'getRevision',
-    ol.source.Cluster.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'on',
-    ol.source.Cluster.prototype.on);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'once',
-    ol.source.Cluster.prototype.once);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'un',
-    ol.source.Cluster.prototype.un);
-
-goog.exportProperty(
-    ol.source.Cluster.prototype,
-    'unByKey',
-    ol.source.Cluster.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'getAttributions',
-    ol.source.Image.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'getLogo',
-    ol.source.Image.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'getProjection',
-    ol.source.Image.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'getState',
-    ol.source.Image.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'refresh',
-    ol.source.Image.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'setAttributions',
-    ol.source.Image.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'get',
-    ol.source.Image.prototype.get);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'getKeys',
-    ol.source.Image.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'getProperties',
-    ol.source.Image.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'set',
-    ol.source.Image.prototype.set);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'setProperties',
-    ol.source.Image.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'unset',
-    ol.source.Image.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'changed',
-    ol.source.Image.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'dispatchEvent',
-    ol.source.Image.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'getRevision',
-    ol.source.Image.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'on',
-    ol.source.Image.prototype.on);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'once',
-    ol.source.Image.prototype.once);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'un',
-    ol.source.Image.prototype.un);
-
-goog.exportProperty(
-    ol.source.Image.prototype,
-    'unByKey',
-    ol.source.Image.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Image.Event.prototype,
-    'type',
-    ol.source.Image.Event.prototype.type);
-
-goog.exportProperty(
-    ol.source.Image.Event.prototype,
-    'target',
-    ol.source.Image.Event.prototype.target);
-
-goog.exportProperty(
-    ol.source.Image.Event.prototype,
-    'preventDefault',
-    ol.source.Image.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.source.Image.Event.prototype,
-    'stopPropagation',
-    ol.source.Image.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getAttributions',
-    ol.source.ImageArcGISRest.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getLogo',
-    ol.source.ImageArcGISRest.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getProjection',
-    ol.source.ImageArcGISRest.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getState',
-    ol.source.ImageArcGISRest.prototype.getState);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'refresh',
-    ol.source.ImageArcGISRest.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'setAttributions',
-    ol.source.ImageArcGISRest.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'get',
-    ol.source.ImageArcGISRest.prototype.get);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getKeys',
-    ol.source.ImageArcGISRest.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getProperties',
-    ol.source.ImageArcGISRest.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'set',
-    ol.source.ImageArcGISRest.prototype.set);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'setProperties',
-    ol.source.ImageArcGISRest.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'unset',
-    ol.source.ImageArcGISRest.prototype.unset);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'changed',
-    ol.source.ImageArcGISRest.prototype.changed);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'dispatchEvent',
-    ol.source.ImageArcGISRest.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'getRevision',
-    ol.source.ImageArcGISRest.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'on',
-    ol.source.ImageArcGISRest.prototype.on);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'once',
-    ol.source.ImageArcGISRest.prototype.once);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'un',
-    ol.source.ImageArcGISRest.prototype.un);
-
-goog.exportProperty(
-    ol.source.ImageArcGISRest.prototype,
-    'unByKey',
-    ol.source.ImageArcGISRest.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'getAttributions',
-    ol.source.ImageCanvas.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'getLogo',
-    ol.source.ImageCanvas.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'getProjection',
-    ol.source.ImageCanvas.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'getState',
-    ol.source.ImageCanvas.prototype.getState);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'refresh',
-    ol.source.ImageCanvas.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'setAttributions',
-    ol.source.ImageCanvas.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'get',
-    ol.source.ImageCanvas.prototype.get);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'getKeys',
-    ol.source.ImageCanvas.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'getProperties',
-    ol.source.ImageCanvas.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'set',
-    ol.source.ImageCanvas.prototype.set);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'setProperties',
-    ol.source.ImageCanvas.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'unset',
-    ol.source.ImageCanvas.prototype.unset);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'changed',
-    ol.source.ImageCanvas.prototype.changed);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'dispatchEvent',
-    ol.source.ImageCanvas.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'getRevision',
-    ol.source.ImageCanvas.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'on',
-    ol.source.ImageCanvas.prototype.on);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'once',
-    ol.source.ImageCanvas.prototype.once);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'un',
-    ol.source.ImageCanvas.prototype.un);
-
-goog.exportProperty(
-    ol.source.ImageCanvas.prototype,
-    'unByKey',
-    ol.source.ImageCanvas.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getAttributions',
-    ol.source.ImageMapGuide.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getLogo',
-    ol.source.ImageMapGuide.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getProjection',
-    ol.source.ImageMapGuide.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getState',
-    ol.source.ImageMapGuide.prototype.getState);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'refresh',
-    ol.source.ImageMapGuide.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'setAttributions',
-    ol.source.ImageMapGuide.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'get',
-    ol.source.ImageMapGuide.prototype.get);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getKeys',
-    ol.source.ImageMapGuide.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getProperties',
-    ol.source.ImageMapGuide.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'set',
-    ol.source.ImageMapGuide.prototype.set);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'setProperties',
-    ol.source.ImageMapGuide.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'unset',
-    ol.source.ImageMapGuide.prototype.unset);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'changed',
-    ol.source.ImageMapGuide.prototype.changed);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'dispatchEvent',
-    ol.source.ImageMapGuide.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'getRevision',
-    ol.source.ImageMapGuide.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'on',
-    ol.source.ImageMapGuide.prototype.on);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'once',
-    ol.source.ImageMapGuide.prototype.once);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'un',
-    ol.source.ImageMapGuide.prototype.un);
-
-goog.exportProperty(
-    ol.source.ImageMapGuide.prototype,
-    'unByKey',
-    ol.source.ImageMapGuide.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'getAttributions',
-    ol.source.ImageStatic.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'getLogo',
-    ol.source.ImageStatic.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'getProjection',
-    ol.source.ImageStatic.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'getState',
-    ol.source.ImageStatic.prototype.getState);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'refresh',
-    ol.source.ImageStatic.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'setAttributions',
-    ol.source.ImageStatic.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'get',
-    ol.source.ImageStatic.prototype.get);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'getKeys',
-    ol.source.ImageStatic.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'getProperties',
-    ol.source.ImageStatic.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'set',
-    ol.source.ImageStatic.prototype.set);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'setProperties',
-    ol.source.ImageStatic.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'unset',
-    ol.source.ImageStatic.prototype.unset);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'changed',
-    ol.source.ImageStatic.prototype.changed);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'dispatchEvent',
-    ol.source.ImageStatic.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'getRevision',
-    ol.source.ImageStatic.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'on',
-    ol.source.ImageStatic.prototype.on);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'once',
-    ol.source.ImageStatic.prototype.once);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'un',
-    ol.source.ImageStatic.prototype.un);
-
-goog.exportProperty(
-    ol.source.ImageStatic.prototype,
-    'unByKey',
-    ol.source.ImageStatic.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getAttributions',
-    ol.source.ImageVector.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getLogo',
-    ol.source.ImageVector.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getProjection',
-    ol.source.ImageVector.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getState',
-    ol.source.ImageVector.prototype.getState);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'refresh',
-    ol.source.ImageVector.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'setAttributions',
-    ol.source.ImageVector.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'get',
-    ol.source.ImageVector.prototype.get);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getKeys',
-    ol.source.ImageVector.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getProperties',
-    ol.source.ImageVector.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'set',
-    ol.source.ImageVector.prototype.set);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'setProperties',
-    ol.source.ImageVector.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'unset',
-    ol.source.ImageVector.prototype.unset);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'changed',
-    ol.source.ImageVector.prototype.changed);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'dispatchEvent',
-    ol.source.ImageVector.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'getRevision',
-    ol.source.ImageVector.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'on',
-    ol.source.ImageVector.prototype.on);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'once',
-    ol.source.ImageVector.prototype.once);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'un',
-    ol.source.ImageVector.prototype.un);
-
-goog.exportProperty(
-    ol.source.ImageVector.prototype,
-    'unByKey',
-    ol.source.ImageVector.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getAttributions',
-    ol.source.ImageWMS.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getLogo',
-    ol.source.ImageWMS.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getProjection',
-    ol.source.ImageWMS.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getState',
-    ol.source.ImageWMS.prototype.getState);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'refresh',
-    ol.source.ImageWMS.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'setAttributions',
-    ol.source.ImageWMS.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'get',
-    ol.source.ImageWMS.prototype.get);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getKeys',
-    ol.source.ImageWMS.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getProperties',
-    ol.source.ImageWMS.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'set',
-    ol.source.ImageWMS.prototype.set);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'setProperties',
-    ol.source.ImageWMS.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'unset',
-    ol.source.ImageWMS.prototype.unset);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'changed',
-    ol.source.ImageWMS.prototype.changed);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'dispatchEvent',
-    ol.source.ImageWMS.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'getRevision',
-    ol.source.ImageWMS.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'on',
-    ol.source.ImageWMS.prototype.on);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'once',
-    ol.source.ImageWMS.prototype.once);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'un',
-    ol.source.ImageWMS.prototype.un);
-
-goog.exportProperty(
-    ol.source.ImageWMS.prototype,
-    'unByKey',
-    ol.source.ImageWMS.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.OSM.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setTileGridForProjection',
-    ol.source.OSM.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getTileLoadFunction',
-    ol.source.OSM.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getTileUrlFunction',
-    ol.source.OSM.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getUrls',
-    ol.source.OSM.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setTileLoadFunction',
-    ol.source.OSM.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setTileUrlFunction',
-    ol.source.OSM.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setUrl',
-    ol.source.OSM.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setUrls',
-    ol.source.OSM.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getTileGrid',
-    ol.source.OSM.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'refresh',
-    ol.source.OSM.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getAttributions',
-    ol.source.OSM.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getLogo',
-    ol.source.OSM.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getProjection',
-    ol.source.OSM.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getState',
-    ol.source.OSM.prototype.getState);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setAttributions',
-    ol.source.OSM.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'get',
-    ol.source.OSM.prototype.get);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getKeys',
-    ol.source.OSM.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getProperties',
-    ol.source.OSM.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'set',
-    ol.source.OSM.prototype.set);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'setProperties',
-    ol.source.OSM.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'unset',
-    ol.source.OSM.prototype.unset);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'changed',
-    ol.source.OSM.prototype.changed);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'dispatchEvent',
-    ol.source.OSM.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'getRevision',
-    ol.source.OSM.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'on',
-    ol.source.OSM.prototype.on);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'once',
-    ol.source.OSM.prototype.once);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'un',
-    ol.source.OSM.prototype.un);
-
-goog.exportProperty(
-    ol.source.OSM.prototype,
-    'unByKey',
-    ol.source.OSM.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'getAttributions',
-    ol.source.Raster.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'getLogo',
-    ol.source.Raster.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'getProjection',
-    ol.source.Raster.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'getState',
-    ol.source.Raster.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'refresh',
-    ol.source.Raster.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'setAttributions',
-    ol.source.Raster.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'get',
-    ol.source.Raster.prototype.get);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'getKeys',
-    ol.source.Raster.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'getProperties',
-    ol.source.Raster.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'set',
-    ol.source.Raster.prototype.set);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'setProperties',
-    ol.source.Raster.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'unset',
-    ol.source.Raster.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'changed',
-    ol.source.Raster.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'dispatchEvent',
-    ol.source.Raster.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'getRevision',
-    ol.source.Raster.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'on',
-    ol.source.Raster.prototype.on);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'once',
-    ol.source.Raster.prototype.once);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'un',
-    ol.source.Raster.prototype.un);
-
-goog.exportProperty(
-    ol.source.Raster.prototype,
-    'unByKey',
-    ol.source.Raster.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Raster.Event.prototype,
-    'type',
-    ol.source.Raster.Event.prototype.type);
-
-goog.exportProperty(
-    ol.source.Raster.Event.prototype,
-    'target',
-    ol.source.Raster.Event.prototype.target);
-
-goog.exportProperty(
-    ol.source.Raster.Event.prototype,
-    'preventDefault',
-    ol.source.Raster.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.source.Raster.Event.prototype,
-    'stopPropagation',
-    ol.source.Raster.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.Stamen.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setTileGridForProjection',
-    ol.source.Stamen.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getTileLoadFunction',
-    ol.source.Stamen.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getTileUrlFunction',
-    ol.source.Stamen.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getUrls',
-    ol.source.Stamen.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setTileLoadFunction',
-    ol.source.Stamen.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setTileUrlFunction',
-    ol.source.Stamen.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setUrl',
-    ol.source.Stamen.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setUrls',
-    ol.source.Stamen.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getTileGrid',
-    ol.source.Stamen.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'refresh',
-    ol.source.Stamen.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getAttributions',
-    ol.source.Stamen.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getLogo',
-    ol.source.Stamen.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getProjection',
-    ol.source.Stamen.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getState',
-    ol.source.Stamen.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setAttributions',
-    ol.source.Stamen.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'get',
-    ol.source.Stamen.prototype.get);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getKeys',
-    ol.source.Stamen.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getProperties',
-    ol.source.Stamen.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'set',
-    ol.source.Stamen.prototype.set);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'setProperties',
-    ol.source.Stamen.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'unset',
-    ol.source.Stamen.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'changed',
-    ol.source.Stamen.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'dispatchEvent',
-    ol.source.Stamen.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'getRevision',
-    ol.source.Stamen.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'on',
-    ol.source.Stamen.prototype.on);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'once',
-    ol.source.Stamen.prototype.once);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'un',
-    ol.source.Stamen.prototype.un);
-
-goog.exportProperty(
-    ol.source.Stamen.prototype,
-    'unByKey',
-    ol.source.Stamen.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Tile.Event.prototype,
-    'type',
-    ol.source.Tile.Event.prototype.type);
-
-goog.exportProperty(
-    ol.source.Tile.Event.prototype,
-    'target',
-    ol.source.Tile.Event.prototype.target);
-
-goog.exportProperty(
-    ol.source.Tile.Event.prototype,
-    'preventDefault',
-    ol.source.Tile.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.source.Tile.Event.prototype,
-    'stopPropagation',
-    ol.source.Tile.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.TileArcGISRest.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setTileGridForProjection',
-    ol.source.TileArcGISRest.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getTileLoadFunction',
-    ol.source.TileArcGISRest.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getTileUrlFunction',
-    ol.source.TileArcGISRest.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getUrls',
-    ol.source.TileArcGISRest.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setTileLoadFunction',
-    ol.source.TileArcGISRest.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setTileUrlFunction',
-    ol.source.TileArcGISRest.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setUrl',
-    ol.source.TileArcGISRest.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setUrls',
-    ol.source.TileArcGISRest.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getTileGrid',
-    ol.source.TileArcGISRest.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'refresh',
-    ol.source.TileArcGISRest.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getAttributions',
-    ol.source.TileArcGISRest.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getLogo',
-    ol.source.TileArcGISRest.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getProjection',
-    ol.source.TileArcGISRest.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getState',
-    ol.source.TileArcGISRest.prototype.getState);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setAttributions',
-    ol.source.TileArcGISRest.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'get',
-    ol.source.TileArcGISRest.prototype.get);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getKeys',
-    ol.source.TileArcGISRest.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getProperties',
-    ol.source.TileArcGISRest.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'set',
-    ol.source.TileArcGISRest.prototype.set);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'setProperties',
-    ol.source.TileArcGISRest.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'unset',
-    ol.source.TileArcGISRest.prototype.unset);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'changed',
-    ol.source.TileArcGISRest.prototype.changed);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'dispatchEvent',
-    ol.source.TileArcGISRest.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'getRevision',
-    ol.source.TileArcGISRest.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'on',
-    ol.source.TileArcGISRest.prototype.on);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'once',
-    ol.source.TileArcGISRest.prototype.once);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'un',
-    ol.source.TileArcGISRest.prototype.un);
-
-goog.exportProperty(
-    ol.source.TileArcGISRest.prototype,
-    'unByKey',
-    ol.source.TileArcGISRest.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getTileGrid',
-    ol.source.TileDebug.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'refresh',
-    ol.source.TileDebug.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getAttributions',
-    ol.source.TileDebug.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getLogo',
-    ol.source.TileDebug.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getProjection',
-    ol.source.TileDebug.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getState',
-    ol.source.TileDebug.prototype.getState);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'setAttributions',
-    ol.source.TileDebug.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'get',
-    ol.source.TileDebug.prototype.get);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getKeys',
-    ol.source.TileDebug.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getProperties',
-    ol.source.TileDebug.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'set',
-    ol.source.TileDebug.prototype.set);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'setProperties',
-    ol.source.TileDebug.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'unset',
-    ol.source.TileDebug.prototype.unset);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'changed',
-    ol.source.TileDebug.prototype.changed);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'dispatchEvent',
-    ol.source.TileDebug.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'getRevision',
-    ol.source.TileDebug.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'on',
-    ol.source.TileDebug.prototype.on);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'once',
-    ol.source.TileDebug.prototype.once);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'un',
-    ol.source.TileDebug.prototype.un);
-
-goog.exportProperty(
-    ol.source.TileDebug.prototype,
-    'unByKey',
-    ol.source.TileDebug.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.TileJSON.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setTileGridForProjection',
-    ol.source.TileJSON.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getTileLoadFunction',
-    ol.source.TileJSON.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getTileUrlFunction',
-    ol.source.TileJSON.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getUrls',
-    ol.source.TileJSON.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setTileLoadFunction',
-    ol.source.TileJSON.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setTileUrlFunction',
-    ol.source.TileJSON.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setUrl',
-    ol.source.TileJSON.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setUrls',
-    ol.source.TileJSON.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getTileGrid',
-    ol.source.TileJSON.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'refresh',
-    ol.source.TileJSON.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getAttributions',
-    ol.source.TileJSON.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getLogo',
-    ol.source.TileJSON.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getProjection',
-    ol.source.TileJSON.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getState',
-    ol.source.TileJSON.prototype.getState);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setAttributions',
-    ol.source.TileJSON.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'get',
-    ol.source.TileJSON.prototype.get);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getKeys',
-    ol.source.TileJSON.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getProperties',
-    ol.source.TileJSON.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'set',
-    ol.source.TileJSON.prototype.set);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'setProperties',
-    ol.source.TileJSON.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'unset',
-    ol.source.TileJSON.prototype.unset);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'changed',
-    ol.source.TileJSON.prototype.changed);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'dispatchEvent',
-    ol.source.TileJSON.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'getRevision',
-    ol.source.TileJSON.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'on',
-    ol.source.TileJSON.prototype.on);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'once',
-    ol.source.TileJSON.prototype.once);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'un',
-    ol.source.TileJSON.prototype.un);
-
-goog.exportProperty(
-    ol.source.TileJSON.prototype,
-    'unByKey',
-    ol.source.TileJSON.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getTileGrid',
-    ol.source.TileUTFGrid.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'refresh',
-    ol.source.TileUTFGrid.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getAttributions',
-    ol.source.TileUTFGrid.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getLogo',
-    ol.source.TileUTFGrid.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getProjection',
-    ol.source.TileUTFGrid.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getState',
-    ol.source.TileUTFGrid.prototype.getState);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'setAttributions',
-    ol.source.TileUTFGrid.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'get',
-    ol.source.TileUTFGrid.prototype.get);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getKeys',
-    ol.source.TileUTFGrid.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getProperties',
-    ol.source.TileUTFGrid.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'set',
-    ol.source.TileUTFGrid.prototype.set);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'setProperties',
-    ol.source.TileUTFGrid.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'unset',
-    ol.source.TileUTFGrid.prototype.unset);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'changed',
-    ol.source.TileUTFGrid.prototype.changed);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'dispatchEvent',
-    ol.source.TileUTFGrid.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'getRevision',
-    ol.source.TileUTFGrid.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'on',
-    ol.source.TileUTFGrid.prototype.on);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'once',
-    ol.source.TileUTFGrid.prototype.once);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'un',
-    ol.source.TileUTFGrid.prototype.un);
-
-goog.exportProperty(
-    ol.source.TileUTFGrid.prototype,
-    'unByKey',
-    ol.source.TileUTFGrid.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.TileWMS.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setTileGridForProjection',
-    ol.source.TileWMS.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getTileLoadFunction',
-    ol.source.TileWMS.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getTileUrlFunction',
-    ol.source.TileWMS.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getUrls',
-    ol.source.TileWMS.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setTileLoadFunction',
-    ol.source.TileWMS.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setTileUrlFunction',
-    ol.source.TileWMS.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setUrl',
-    ol.source.TileWMS.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setUrls',
-    ol.source.TileWMS.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getTileGrid',
-    ol.source.TileWMS.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'refresh',
-    ol.source.TileWMS.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getAttributions',
-    ol.source.TileWMS.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getLogo',
-    ol.source.TileWMS.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getProjection',
-    ol.source.TileWMS.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getState',
-    ol.source.TileWMS.prototype.getState);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setAttributions',
-    ol.source.TileWMS.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'get',
-    ol.source.TileWMS.prototype.get);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getKeys',
-    ol.source.TileWMS.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getProperties',
-    ol.source.TileWMS.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'set',
-    ol.source.TileWMS.prototype.set);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'setProperties',
-    ol.source.TileWMS.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'unset',
-    ol.source.TileWMS.prototype.unset);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'changed',
-    ol.source.TileWMS.prototype.changed);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'dispatchEvent',
-    ol.source.TileWMS.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'getRevision',
-    ol.source.TileWMS.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'on',
-    ol.source.TileWMS.prototype.on);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'once',
-    ol.source.TileWMS.prototype.once);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'un',
-    ol.source.TileWMS.prototype.un);
-
-goog.exportProperty(
-    ol.source.TileWMS.prototype,
-    'unByKey',
-    ol.source.TileWMS.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Vector.Event.prototype,
-    'type',
-    ol.source.Vector.Event.prototype.type);
-
-goog.exportProperty(
-    ol.source.Vector.Event.prototype,
-    'target',
-    ol.source.Vector.Event.prototype.target);
-
-goog.exportProperty(
-    ol.source.Vector.Event.prototype,
-    'preventDefault',
-    ol.source.Vector.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.source.Vector.Event.prototype,
-    'stopPropagation',
-    ol.source.Vector.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getTileLoadFunction',
-    ol.source.VectorTile.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getTileUrlFunction',
-    ol.source.VectorTile.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getUrls',
-    ol.source.VectorTile.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'setTileLoadFunction',
-    ol.source.VectorTile.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'setTileUrlFunction',
-    ol.source.VectorTile.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'setUrl',
-    ol.source.VectorTile.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'setUrls',
-    ol.source.VectorTile.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getTileGrid',
-    ol.source.VectorTile.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'refresh',
-    ol.source.VectorTile.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getAttributions',
-    ol.source.VectorTile.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getLogo',
-    ol.source.VectorTile.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getProjection',
-    ol.source.VectorTile.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getState',
-    ol.source.VectorTile.prototype.getState);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'setAttributions',
-    ol.source.VectorTile.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'get',
-    ol.source.VectorTile.prototype.get);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getKeys',
-    ol.source.VectorTile.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getProperties',
-    ol.source.VectorTile.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'set',
-    ol.source.VectorTile.prototype.set);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'setProperties',
-    ol.source.VectorTile.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'unset',
-    ol.source.VectorTile.prototype.unset);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'changed',
-    ol.source.VectorTile.prototype.changed);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'dispatchEvent',
-    ol.source.VectorTile.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'getRevision',
-    ol.source.VectorTile.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'on',
-    ol.source.VectorTile.prototype.on);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'once',
-    ol.source.VectorTile.prototype.once);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'un',
-    ol.source.VectorTile.prototype.un);
-
-goog.exportProperty(
-    ol.source.VectorTile.prototype,
-    'unByKey',
-    ol.source.VectorTile.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.WMTS.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setTileGridForProjection',
-    ol.source.WMTS.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getTileLoadFunction',
-    ol.source.WMTS.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getTileUrlFunction',
-    ol.source.WMTS.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getUrls',
-    ol.source.WMTS.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setTileLoadFunction',
-    ol.source.WMTS.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setTileUrlFunction',
-    ol.source.WMTS.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setUrl',
-    ol.source.WMTS.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setUrls',
-    ol.source.WMTS.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getTileGrid',
-    ol.source.WMTS.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'refresh',
-    ol.source.WMTS.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getAttributions',
-    ol.source.WMTS.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getLogo',
-    ol.source.WMTS.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getProjection',
-    ol.source.WMTS.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getState',
-    ol.source.WMTS.prototype.getState);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setAttributions',
-    ol.source.WMTS.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'get',
-    ol.source.WMTS.prototype.get);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getKeys',
-    ol.source.WMTS.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getProperties',
-    ol.source.WMTS.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'set',
-    ol.source.WMTS.prototype.set);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'setProperties',
-    ol.source.WMTS.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'unset',
-    ol.source.WMTS.prototype.unset);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'changed',
-    ol.source.WMTS.prototype.changed);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'dispatchEvent',
-    ol.source.WMTS.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'getRevision',
-    ol.source.WMTS.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'on',
-    ol.source.WMTS.prototype.on);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'once',
-    ol.source.WMTS.prototype.once);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'un',
-    ol.source.WMTS.prototype.un);
-
-goog.exportProperty(
-    ol.source.WMTS.prototype,
-    'unByKey',
-    ol.source.WMTS.prototype.unByKey);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setRenderReprojectionEdges',
-    ol.source.Zoomify.prototype.setRenderReprojectionEdges);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setTileGridForProjection',
-    ol.source.Zoomify.prototype.setTileGridForProjection);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getTileLoadFunction',
-    ol.source.Zoomify.prototype.getTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getTileUrlFunction',
-    ol.source.Zoomify.prototype.getTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getUrls',
-    ol.source.Zoomify.prototype.getUrls);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setTileLoadFunction',
-    ol.source.Zoomify.prototype.setTileLoadFunction);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setTileUrlFunction',
-    ol.source.Zoomify.prototype.setTileUrlFunction);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setUrl',
-    ol.source.Zoomify.prototype.setUrl);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setUrls',
-    ol.source.Zoomify.prototype.setUrls);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getTileGrid',
-    ol.source.Zoomify.prototype.getTileGrid);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'refresh',
-    ol.source.Zoomify.prototype.refresh);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getAttributions',
-    ol.source.Zoomify.prototype.getAttributions);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getLogo',
-    ol.source.Zoomify.prototype.getLogo);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getProjection',
-    ol.source.Zoomify.prototype.getProjection);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getState',
-    ol.source.Zoomify.prototype.getState);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setAttributions',
-    ol.source.Zoomify.prototype.setAttributions);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'get',
-    ol.source.Zoomify.prototype.get);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getKeys',
-    ol.source.Zoomify.prototype.getKeys);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getProperties',
-    ol.source.Zoomify.prototype.getProperties);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'set',
-    ol.source.Zoomify.prototype.set);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'setProperties',
-    ol.source.Zoomify.prototype.setProperties);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'unset',
-    ol.source.Zoomify.prototype.unset);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'changed',
-    ol.source.Zoomify.prototype.changed);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'dispatchEvent',
-    ol.source.Zoomify.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'getRevision',
-    ol.source.Zoomify.prototype.getRevision);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'on',
-    ol.source.Zoomify.prototype.on);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'once',
-    ol.source.Zoomify.prototype.once);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'un',
-    ol.source.Zoomify.prototype.un);
-
-goog.exportProperty(
-    ol.source.Zoomify.prototype,
-    'unByKey',
-    ol.source.Zoomify.prototype.unByKey);
-
-goog.exportProperty(
-    ol.reproj.Tile.prototype,
-    'getTileCoord',
-    ol.reproj.Tile.prototype.getTileCoord);
-
-goog.exportProperty(
-    ol.reproj.Tile.prototype,
-    'load',
-    ol.reproj.Tile.prototype.load);
-
-goog.exportProperty(
-    ol.renderer.Layer.prototype,
-    'changed',
-    ol.renderer.Layer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.Layer.prototype,
-    'dispatchEvent',
-    ol.renderer.Layer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.Layer.prototype,
-    'getRevision',
-    ol.renderer.Layer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.Layer.prototype,
-    'on',
-    ol.renderer.Layer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.Layer.prototype,
-    'once',
-    ol.renderer.Layer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.Layer.prototype,
-    'un',
-    ol.renderer.Layer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.Layer.prototype,
-    'unByKey',
-    ol.renderer.Layer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.webgl.Layer.prototype,
-    'changed',
-    ol.renderer.webgl.Layer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.webgl.Layer.prototype,
-    'dispatchEvent',
-    ol.renderer.webgl.Layer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.webgl.Layer.prototype,
-    'getRevision',
-    ol.renderer.webgl.Layer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.webgl.Layer.prototype,
-    'on',
-    ol.renderer.webgl.Layer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.webgl.Layer.prototype,
-    'once',
-    ol.renderer.webgl.Layer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.webgl.Layer.prototype,
-    'un',
-    ol.renderer.webgl.Layer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.webgl.Layer.prototype,
-    'unByKey',
-    ol.renderer.webgl.Layer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.webgl.ImageLayer.prototype,
-    'changed',
-    ol.renderer.webgl.ImageLayer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.webgl.ImageLayer.prototype,
-    'dispatchEvent',
-    ol.renderer.webgl.ImageLayer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.webgl.ImageLayer.prototype,
-    'getRevision',
-    ol.renderer.webgl.ImageLayer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.webgl.ImageLayer.prototype,
-    'on',
-    ol.renderer.webgl.ImageLayer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.webgl.ImageLayer.prototype,
-    'once',
-    ol.renderer.webgl.ImageLayer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.webgl.ImageLayer.prototype,
-    'un',
-    ol.renderer.webgl.ImageLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.webgl.ImageLayer.prototype,
-    'unByKey',
-    ol.renderer.webgl.ImageLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.webgl.TileLayer.prototype,
-    'changed',
-    ol.renderer.webgl.TileLayer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.webgl.TileLayer.prototype,
-    'dispatchEvent',
-    ol.renderer.webgl.TileLayer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.webgl.TileLayer.prototype,
-    'getRevision',
-    ol.renderer.webgl.TileLayer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.webgl.TileLayer.prototype,
-    'on',
-    ol.renderer.webgl.TileLayer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.webgl.TileLayer.prototype,
-    'once',
-    ol.renderer.webgl.TileLayer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.webgl.TileLayer.prototype,
-    'un',
-    ol.renderer.webgl.TileLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.webgl.TileLayer.prototype,
-    'unByKey',
-    ol.renderer.webgl.TileLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.webgl.VectorLayer.prototype,
-    'changed',
-    ol.renderer.webgl.VectorLayer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.webgl.VectorLayer.prototype,
-    'dispatchEvent',
-    ol.renderer.webgl.VectorLayer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.webgl.VectorLayer.prototype,
-    'getRevision',
-    ol.renderer.webgl.VectorLayer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.webgl.VectorLayer.prototype,
-    'on',
-    ol.renderer.webgl.VectorLayer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.webgl.VectorLayer.prototype,
-    'once',
-    ol.renderer.webgl.VectorLayer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.webgl.VectorLayer.prototype,
-    'un',
-    ol.renderer.webgl.VectorLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.webgl.VectorLayer.prototype,
-    'unByKey',
-    ol.renderer.webgl.VectorLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'changed',
-    ol.renderer.canvas.Layer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'dispatchEvent',
-    ol.renderer.canvas.Layer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'getRevision',
-    ol.renderer.canvas.Layer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'on',
-    ol.renderer.canvas.Layer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'once',
-    ol.renderer.canvas.Layer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'un',
-    ol.renderer.canvas.Layer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.canvas.Layer.prototype,
-    'unByKey',
-    ol.renderer.canvas.Layer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.canvas.ImageLayer.prototype,
-    'changed',
-    ol.renderer.canvas.ImageLayer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.canvas.ImageLayer.prototype,
-    'dispatchEvent',
-    ol.renderer.canvas.ImageLayer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.canvas.ImageLayer.prototype,
-    'getRevision',
-    ol.renderer.canvas.ImageLayer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.canvas.ImageLayer.prototype,
-    'on',
-    ol.renderer.canvas.ImageLayer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.canvas.ImageLayer.prototype,
-    'once',
-    ol.renderer.canvas.ImageLayer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.canvas.ImageLayer.prototype,
-    'un',
-    ol.renderer.canvas.ImageLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.canvas.ImageLayer.prototype,
-    'unByKey',
-    ol.renderer.canvas.ImageLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.canvas.TileLayer.prototype,
-    'changed',
-    ol.renderer.canvas.TileLayer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.canvas.TileLayer.prototype,
-    'dispatchEvent',
-    ol.renderer.canvas.TileLayer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.canvas.TileLayer.prototype,
-    'getRevision',
-    ol.renderer.canvas.TileLayer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.canvas.TileLayer.prototype,
-    'on',
-    ol.renderer.canvas.TileLayer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.canvas.TileLayer.prototype,
-    'once',
-    ol.renderer.canvas.TileLayer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.canvas.TileLayer.prototype,
-    'un',
-    ol.renderer.canvas.TileLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.canvas.TileLayer.prototype,
-    'unByKey',
-    ol.renderer.canvas.TileLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'changed',
-    ol.renderer.canvas.VectorLayer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'dispatchEvent',
-    ol.renderer.canvas.VectorLayer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'getRevision',
-    ol.renderer.canvas.VectorLayer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'on',
-    ol.renderer.canvas.VectorLayer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'once',
-    ol.renderer.canvas.VectorLayer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'un',
-    ol.renderer.canvas.VectorLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorLayer.prototype,
-    'unByKey',
-    ol.renderer.canvas.VectorLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorTileLayer.prototype,
-    'changed',
-    ol.renderer.canvas.VectorTileLayer.prototype.changed);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorTileLayer.prototype,
-    'dispatchEvent',
-    ol.renderer.canvas.VectorTileLayer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorTileLayer.prototype,
-    'getRevision',
-    ol.renderer.canvas.VectorTileLayer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorTileLayer.prototype,
-    'on',
-    ol.renderer.canvas.VectorTileLayer.prototype.on);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorTileLayer.prototype,
-    'once',
-    ol.renderer.canvas.VectorTileLayer.prototype.once);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorTileLayer.prototype,
-    'un',
-    ol.renderer.canvas.VectorTileLayer.prototype.un);
-
-goog.exportProperty(
-    ol.renderer.canvas.VectorTileLayer.prototype,
-    'unByKey',
-    ol.renderer.canvas.VectorTileLayer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'type',
-    ol.render.Event.prototype.type);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'target',
-    ol.render.Event.prototype.target);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'preventDefault',
-    ol.render.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.render.Event.prototype,
-    'stopPropagation',
-    ol.render.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.pointer.PointerEvent.prototype,
-    'type',
-    ol.pointer.PointerEvent.prototype.type);
-
-goog.exportProperty(
-    ol.pointer.PointerEvent.prototype,
-    'target',
-    ol.pointer.PointerEvent.prototype.target);
-
-goog.exportProperty(
-    ol.pointer.PointerEvent.prototype,
-    'preventDefault',
-    ol.pointer.PointerEvent.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.pointer.PointerEvent.prototype,
-    'stopPropagation',
-    ol.pointer.PointerEvent.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'get',
-    ol.layer.Base.prototype.get);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getKeys',
-    ol.layer.Base.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getProperties',
-    ol.layer.Base.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'set',
-    ol.layer.Base.prototype.set);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'setProperties',
-    ol.layer.Base.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'unset',
-    ol.layer.Base.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'changed',
-    ol.layer.Base.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'dispatchEvent',
-    ol.layer.Base.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'getRevision',
-    ol.layer.Base.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'on',
-    ol.layer.Base.prototype.on);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'once',
-    ol.layer.Base.prototype.once);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'un',
-    ol.layer.Base.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Base.prototype,
-    'unByKey',
-    ol.layer.Base.prototype.unByKey);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getExtent',
-    ol.layer.Group.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getMaxResolution',
-    ol.layer.Group.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getMinResolution',
-    ol.layer.Group.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getOpacity',
-    ol.layer.Group.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getVisible',
-    ol.layer.Group.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getZIndex',
-    ol.layer.Group.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setExtent',
-    ol.layer.Group.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setMaxResolution',
-    ol.layer.Group.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setMinResolution',
-    ol.layer.Group.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setOpacity',
-    ol.layer.Group.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setVisible',
-    ol.layer.Group.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setZIndex',
-    ol.layer.Group.prototype.setZIndex);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'get',
-    ol.layer.Group.prototype.get);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getKeys',
-    ol.layer.Group.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getProperties',
-    ol.layer.Group.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'set',
-    ol.layer.Group.prototype.set);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'setProperties',
-    ol.layer.Group.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'unset',
-    ol.layer.Group.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'changed',
-    ol.layer.Group.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'dispatchEvent',
-    ol.layer.Group.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'getRevision',
-    ol.layer.Group.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'on',
-    ol.layer.Group.prototype.on);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'once',
-    ol.layer.Group.prototype.once);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'un',
-    ol.layer.Group.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Group.prototype,
-    'unByKey',
-    ol.layer.Group.prototype.unByKey);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getExtent',
-    ol.layer.Layer.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getMaxResolution',
-    ol.layer.Layer.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getMinResolution',
-    ol.layer.Layer.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getOpacity',
-    ol.layer.Layer.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getVisible',
-    ol.layer.Layer.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getZIndex',
-    ol.layer.Layer.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setExtent',
-    ol.layer.Layer.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setMaxResolution',
-    ol.layer.Layer.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setMinResolution',
-    ol.layer.Layer.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setOpacity',
-    ol.layer.Layer.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setVisible',
-    ol.layer.Layer.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setZIndex',
-    ol.layer.Layer.prototype.setZIndex);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'get',
-    ol.layer.Layer.prototype.get);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getKeys',
-    ol.layer.Layer.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getProperties',
-    ol.layer.Layer.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'set',
-    ol.layer.Layer.prototype.set);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'setProperties',
-    ol.layer.Layer.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'unset',
-    ol.layer.Layer.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'changed',
-    ol.layer.Layer.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'dispatchEvent',
-    ol.layer.Layer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'getRevision',
-    ol.layer.Layer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'on',
-    ol.layer.Layer.prototype.on);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'once',
-    ol.layer.Layer.prototype.once);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'un',
-    ol.layer.Layer.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Layer.prototype,
-    'unByKey',
-    ol.layer.Layer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setMap',
-    ol.layer.Vector.prototype.setMap);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setSource',
-    ol.layer.Vector.prototype.setSource);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getExtent',
-    ol.layer.Vector.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getMaxResolution',
-    ol.layer.Vector.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getMinResolution',
-    ol.layer.Vector.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getOpacity',
-    ol.layer.Vector.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getVisible',
-    ol.layer.Vector.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getZIndex',
-    ol.layer.Vector.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setExtent',
-    ol.layer.Vector.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setMaxResolution',
-    ol.layer.Vector.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setMinResolution',
-    ol.layer.Vector.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setOpacity',
-    ol.layer.Vector.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setVisible',
-    ol.layer.Vector.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setZIndex',
-    ol.layer.Vector.prototype.setZIndex);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'get',
-    ol.layer.Vector.prototype.get);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getKeys',
-    ol.layer.Vector.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getProperties',
-    ol.layer.Vector.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'set',
-    ol.layer.Vector.prototype.set);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'setProperties',
-    ol.layer.Vector.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'unset',
-    ol.layer.Vector.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'changed',
-    ol.layer.Vector.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'dispatchEvent',
-    ol.layer.Vector.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'getRevision',
-    ol.layer.Vector.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'on',
-    ol.layer.Vector.prototype.on);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'once',
-    ol.layer.Vector.prototype.once);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'un',
-    ol.layer.Vector.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Vector.prototype,
-    'unByKey',
-    ol.layer.Vector.prototype.unByKey);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getSource',
-    ol.layer.Heatmap.prototype.getSource);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getStyle',
-    ol.layer.Heatmap.prototype.getStyle);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getStyleFunction',
-    ol.layer.Heatmap.prototype.getStyleFunction);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setStyle',
-    ol.layer.Heatmap.prototype.setStyle);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setMap',
-    ol.layer.Heatmap.prototype.setMap);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setSource',
-    ol.layer.Heatmap.prototype.setSource);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getExtent',
-    ol.layer.Heatmap.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getMaxResolution',
-    ol.layer.Heatmap.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getMinResolution',
-    ol.layer.Heatmap.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getOpacity',
-    ol.layer.Heatmap.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getVisible',
-    ol.layer.Heatmap.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getZIndex',
-    ol.layer.Heatmap.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setExtent',
-    ol.layer.Heatmap.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setMaxResolution',
-    ol.layer.Heatmap.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setMinResolution',
-    ol.layer.Heatmap.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setOpacity',
-    ol.layer.Heatmap.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setVisible',
-    ol.layer.Heatmap.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setZIndex',
-    ol.layer.Heatmap.prototype.setZIndex);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'get',
-    ol.layer.Heatmap.prototype.get);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getKeys',
-    ol.layer.Heatmap.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getProperties',
-    ol.layer.Heatmap.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'set',
-    ol.layer.Heatmap.prototype.set);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'setProperties',
-    ol.layer.Heatmap.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'unset',
-    ol.layer.Heatmap.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'changed',
-    ol.layer.Heatmap.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'dispatchEvent',
-    ol.layer.Heatmap.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'getRevision',
-    ol.layer.Heatmap.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'on',
-    ol.layer.Heatmap.prototype.on);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'once',
-    ol.layer.Heatmap.prototype.once);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'un',
-    ol.layer.Heatmap.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Heatmap.prototype,
-    'unByKey',
-    ol.layer.Heatmap.prototype.unByKey);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setMap',
-    ol.layer.Image.prototype.setMap);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setSource',
-    ol.layer.Image.prototype.setSource);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getExtent',
-    ol.layer.Image.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getMaxResolution',
-    ol.layer.Image.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getMinResolution',
-    ol.layer.Image.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getOpacity',
-    ol.layer.Image.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getVisible',
-    ol.layer.Image.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getZIndex',
-    ol.layer.Image.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setExtent',
-    ol.layer.Image.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setMaxResolution',
-    ol.layer.Image.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setMinResolution',
-    ol.layer.Image.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setOpacity',
-    ol.layer.Image.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setVisible',
-    ol.layer.Image.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setZIndex',
-    ol.layer.Image.prototype.setZIndex);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'get',
-    ol.layer.Image.prototype.get);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getKeys',
-    ol.layer.Image.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getProperties',
-    ol.layer.Image.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'set',
-    ol.layer.Image.prototype.set);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'setProperties',
-    ol.layer.Image.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'unset',
-    ol.layer.Image.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'changed',
-    ol.layer.Image.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'dispatchEvent',
-    ol.layer.Image.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'getRevision',
-    ol.layer.Image.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'on',
-    ol.layer.Image.prototype.on);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'once',
-    ol.layer.Image.prototype.once);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'un',
-    ol.layer.Image.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Image.prototype,
-    'unByKey',
-    ol.layer.Image.prototype.unByKey);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setMap',
-    ol.layer.Tile.prototype.setMap);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setSource',
-    ol.layer.Tile.prototype.setSource);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getExtent',
-    ol.layer.Tile.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getMaxResolution',
-    ol.layer.Tile.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getMinResolution',
-    ol.layer.Tile.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getOpacity',
-    ol.layer.Tile.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getVisible',
-    ol.layer.Tile.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getZIndex',
-    ol.layer.Tile.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setExtent',
-    ol.layer.Tile.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setMaxResolution',
-    ol.layer.Tile.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setMinResolution',
-    ol.layer.Tile.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setOpacity',
-    ol.layer.Tile.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setVisible',
-    ol.layer.Tile.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setZIndex',
-    ol.layer.Tile.prototype.setZIndex);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'get',
-    ol.layer.Tile.prototype.get);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getKeys',
-    ol.layer.Tile.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getProperties',
-    ol.layer.Tile.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'set',
-    ol.layer.Tile.prototype.set);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'setProperties',
-    ol.layer.Tile.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'unset',
-    ol.layer.Tile.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'changed',
-    ol.layer.Tile.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'dispatchEvent',
-    ol.layer.Tile.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'getRevision',
-    ol.layer.Tile.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'on',
-    ol.layer.Tile.prototype.on);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'once',
-    ol.layer.Tile.prototype.once);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'un',
-    ol.layer.Tile.prototype.un);
-
-goog.exportProperty(
-    ol.layer.Tile.prototype,
-    'unByKey',
-    ol.layer.Tile.prototype.unByKey);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getSource',
-    ol.layer.VectorTile.prototype.getSource);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getStyle',
-    ol.layer.VectorTile.prototype.getStyle);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getStyleFunction',
-    ol.layer.VectorTile.prototype.getStyleFunction);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setStyle',
-    ol.layer.VectorTile.prototype.setStyle);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setMap',
-    ol.layer.VectorTile.prototype.setMap);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setSource',
-    ol.layer.VectorTile.prototype.setSource);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getExtent',
-    ol.layer.VectorTile.prototype.getExtent);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getMaxResolution',
-    ol.layer.VectorTile.prototype.getMaxResolution);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getMinResolution',
-    ol.layer.VectorTile.prototype.getMinResolution);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getOpacity',
-    ol.layer.VectorTile.prototype.getOpacity);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getVisible',
-    ol.layer.VectorTile.prototype.getVisible);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getZIndex',
-    ol.layer.VectorTile.prototype.getZIndex);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setExtent',
-    ol.layer.VectorTile.prototype.setExtent);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setMaxResolution',
-    ol.layer.VectorTile.prototype.setMaxResolution);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setMinResolution',
-    ol.layer.VectorTile.prototype.setMinResolution);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setOpacity',
-    ol.layer.VectorTile.prototype.setOpacity);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setVisible',
-    ol.layer.VectorTile.prototype.setVisible);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setZIndex',
-    ol.layer.VectorTile.prototype.setZIndex);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'get',
-    ol.layer.VectorTile.prototype.get);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getKeys',
-    ol.layer.VectorTile.prototype.getKeys);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getProperties',
-    ol.layer.VectorTile.prototype.getProperties);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'set',
-    ol.layer.VectorTile.prototype.set);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'setProperties',
-    ol.layer.VectorTile.prototype.setProperties);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'unset',
-    ol.layer.VectorTile.prototype.unset);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'changed',
-    ol.layer.VectorTile.prototype.changed);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'dispatchEvent',
-    ol.layer.VectorTile.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'getRevision',
-    ol.layer.VectorTile.prototype.getRevision);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'on',
-    ol.layer.VectorTile.prototype.on);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'once',
-    ol.layer.VectorTile.prototype.once);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'un',
-    ol.layer.VectorTile.prototype.un);
-
-goog.exportProperty(
-    ol.layer.VectorTile.prototype,
-    'unByKey',
-    ol.layer.VectorTile.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'get',
-    ol.interaction.Interaction.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'getKeys',
-    ol.interaction.Interaction.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'getProperties',
-    ol.interaction.Interaction.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'set',
-    ol.interaction.Interaction.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'setProperties',
-    ol.interaction.Interaction.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'unset',
-    ol.interaction.Interaction.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'changed',
-    ol.interaction.Interaction.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'dispatchEvent',
-    ol.interaction.Interaction.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'getRevision',
-    ol.interaction.Interaction.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'on',
-    ol.interaction.Interaction.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'once',
-    ol.interaction.Interaction.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'un',
-    ol.interaction.Interaction.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Interaction.prototype,
-    'unByKey',
-    ol.interaction.Interaction.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'getActive',
-    ol.interaction.DoubleClickZoom.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'getMap',
-    ol.interaction.DoubleClickZoom.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'setActive',
-    ol.interaction.DoubleClickZoom.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'get',
-    ol.interaction.DoubleClickZoom.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'getKeys',
-    ol.interaction.DoubleClickZoom.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'getProperties',
-    ol.interaction.DoubleClickZoom.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'set',
-    ol.interaction.DoubleClickZoom.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'setProperties',
-    ol.interaction.DoubleClickZoom.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'unset',
-    ol.interaction.DoubleClickZoom.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'changed',
-    ol.interaction.DoubleClickZoom.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'dispatchEvent',
-    ol.interaction.DoubleClickZoom.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'getRevision',
-    ol.interaction.DoubleClickZoom.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'on',
-    ol.interaction.DoubleClickZoom.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'once',
-    ol.interaction.DoubleClickZoom.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'un',
-    ol.interaction.DoubleClickZoom.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DoubleClickZoom.prototype,
-    'unByKey',
-    ol.interaction.DoubleClickZoom.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'getActive',
-    ol.interaction.DragAndDrop.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'getMap',
-    ol.interaction.DragAndDrop.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'setActive',
-    ol.interaction.DragAndDrop.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'get',
-    ol.interaction.DragAndDrop.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'getKeys',
-    ol.interaction.DragAndDrop.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'getProperties',
-    ol.interaction.DragAndDrop.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'set',
-    ol.interaction.DragAndDrop.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'setProperties',
-    ol.interaction.DragAndDrop.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'unset',
-    ol.interaction.DragAndDrop.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'changed',
-    ol.interaction.DragAndDrop.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'dispatchEvent',
-    ol.interaction.DragAndDrop.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'getRevision',
-    ol.interaction.DragAndDrop.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'on',
-    ol.interaction.DragAndDrop.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'once',
-    ol.interaction.DragAndDrop.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'un',
-    ol.interaction.DragAndDrop.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.prototype,
-    'unByKey',
-    ol.interaction.DragAndDrop.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.Event.prototype,
-    'type',
-    ol.interaction.DragAndDrop.Event.prototype.type);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.Event.prototype,
-    'target',
-    ol.interaction.DragAndDrop.Event.prototype.target);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.Event.prototype,
-    'preventDefault',
-    ol.interaction.DragAndDrop.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.interaction.DragAndDrop.Event.prototype,
-    'stopPropagation',
-    ol.interaction.DragAndDrop.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'getActive',
-    ol.interaction.Pointer.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'getMap',
-    ol.interaction.Pointer.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'setActive',
-    ol.interaction.Pointer.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'get',
-    ol.interaction.Pointer.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'getKeys',
-    ol.interaction.Pointer.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'getProperties',
-    ol.interaction.Pointer.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'set',
-    ol.interaction.Pointer.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'setProperties',
-    ol.interaction.Pointer.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'unset',
-    ol.interaction.Pointer.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'changed',
-    ol.interaction.Pointer.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'dispatchEvent',
-    ol.interaction.Pointer.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'getRevision',
-    ol.interaction.Pointer.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'on',
-    ol.interaction.Pointer.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'once',
-    ol.interaction.Pointer.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'un',
-    ol.interaction.Pointer.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Pointer.prototype,
-    'unByKey',
-    ol.interaction.Pointer.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'getActive',
-    ol.interaction.DragBox.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'getMap',
-    ol.interaction.DragBox.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'setActive',
-    ol.interaction.DragBox.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'get',
-    ol.interaction.DragBox.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'getKeys',
-    ol.interaction.DragBox.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'getProperties',
-    ol.interaction.DragBox.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'set',
-    ol.interaction.DragBox.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'setProperties',
-    ol.interaction.DragBox.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'unset',
-    ol.interaction.DragBox.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'changed',
-    ol.interaction.DragBox.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'dispatchEvent',
-    ol.interaction.DragBox.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'getRevision',
-    ol.interaction.DragBox.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'on',
-    ol.interaction.DragBox.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'once',
-    ol.interaction.DragBox.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'un',
-    ol.interaction.DragBox.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DragBox.prototype,
-    'unByKey',
-    ol.interaction.DragBox.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DragBox.Event.prototype,
-    'type',
-    ol.interaction.DragBox.Event.prototype.type);
-
-goog.exportProperty(
-    ol.interaction.DragBox.Event.prototype,
-    'target',
-    ol.interaction.DragBox.Event.prototype.target);
-
-goog.exportProperty(
-    ol.interaction.DragBox.Event.prototype,
-    'preventDefault',
-    ol.interaction.DragBox.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.interaction.DragBox.Event.prototype,
-    'stopPropagation',
-    ol.interaction.DragBox.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'getActive',
-    ol.interaction.DragPan.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'getMap',
-    ol.interaction.DragPan.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'setActive',
-    ol.interaction.DragPan.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'get',
-    ol.interaction.DragPan.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'getKeys',
-    ol.interaction.DragPan.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'getProperties',
-    ol.interaction.DragPan.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'set',
-    ol.interaction.DragPan.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'setProperties',
-    ol.interaction.DragPan.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'unset',
-    ol.interaction.DragPan.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'changed',
-    ol.interaction.DragPan.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'dispatchEvent',
-    ol.interaction.DragPan.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'getRevision',
-    ol.interaction.DragPan.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'on',
-    ol.interaction.DragPan.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'once',
-    ol.interaction.DragPan.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'un',
-    ol.interaction.DragPan.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DragPan.prototype,
-    'unByKey',
-    ol.interaction.DragPan.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'getActive',
-    ol.interaction.DragRotate.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'getMap',
-    ol.interaction.DragRotate.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'setActive',
-    ol.interaction.DragRotate.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'get',
-    ol.interaction.DragRotate.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'getKeys',
-    ol.interaction.DragRotate.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'getProperties',
-    ol.interaction.DragRotate.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'set',
-    ol.interaction.DragRotate.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'setProperties',
-    ol.interaction.DragRotate.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'unset',
-    ol.interaction.DragRotate.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'changed',
-    ol.interaction.DragRotate.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'dispatchEvent',
-    ol.interaction.DragRotate.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'getRevision',
-    ol.interaction.DragRotate.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'on',
-    ol.interaction.DragRotate.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'once',
-    ol.interaction.DragRotate.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'un',
-    ol.interaction.DragRotate.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DragRotate.prototype,
-    'unByKey',
-    ol.interaction.DragRotate.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'getActive',
-    ol.interaction.DragRotateAndZoom.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'getMap',
-    ol.interaction.DragRotateAndZoom.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'setActive',
-    ol.interaction.DragRotateAndZoom.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'get',
-    ol.interaction.DragRotateAndZoom.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'getKeys',
-    ol.interaction.DragRotateAndZoom.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'getProperties',
-    ol.interaction.DragRotateAndZoom.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'set',
-    ol.interaction.DragRotateAndZoom.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'setProperties',
-    ol.interaction.DragRotateAndZoom.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'unset',
-    ol.interaction.DragRotateAndZoom.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'changed',
-    ol.interaction.DragRotateAndZoom.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'dispatchEvent',
-    ol.interaction.DragRotateAndZoom.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'getRevision',
-    ol.interaction.DragRotateAndZoom.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'on',
-    ol.interaction.DragRotateAndZoom.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'once',
-    ol.interaction.DragRotateAndZoom.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'un',
-    ol.interaction.DragRotateAndZoom.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DragRotateAndZoom.prototype,
-    'unByKey',
-    ol.interaction.DragRotateAndZoom.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'getGeometry',
-    ol.interaction.DragZoom.prototype.getGeometry);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'getActive',
-    ol.interaction.DragZoom.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'getMap',
-    ol.interaction.DragZoom.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'setActive',
-    ol.interaction.DragZoom.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'get',
-    ol.interaction.DragZoom.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'getKeys',
-    ol.interaction.DragZoom.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'getProperties',
-    ol.interaction.DragZoom.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'set',
-    ol.interaction.DragZoom.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'setProperties',
-    ol.interaction.DragZoom.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'unset',
-    ol.interaction.DragZoom.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'changed',
-    ol.interaction.DragZoom.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'dispatchEvent',
-    ol.interaction.DragZoom.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'getRevision',
-    ol.interaction.DragZoom.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'on',
-    ol.interaction.DragZoom.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'once',
-    ol.interaction.DragZoom.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'un',
-    ol.interaction.DragZoom.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.DragZoom.prototype,
-    'unByKey',
-    ol.interaction.DragZoom.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'getActive',
-    ol.interaction.Draw.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'getMap',
-    ol.interaction.Draw.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'setActive',
-    ol.interaction.Draw.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'get',
-    ol.interaction.Draw.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'getKeys',
-    ol.interaction.Draw.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'getProperties',
-    ol.interaction.Draw.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'set',
-    ol.interaction.Draw.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'setProperties',
-    ol.interaction.Draw.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'unset',
-    ol.interaction.Draw.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'changed',
-    ol.interaction.Draw.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'dispatchEvent',
-    ol.interaction.Draw.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'getRevision',
-    ol.interaction.Draw.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'on',
-    ol.interaction.Draw.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'once',
-    ol.interaction.Draw.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'un',
-    ol.interaction.Draw.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Draw.prototype,
-    'unByKey',
-    ol.interaction.Draw.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Draw.Event.prototype,
-    'type',
-    ol.interaction.Draw.Event.prototype.type);
-
-goog.exportProperty(
-    ol.interaction.Draw.Event.prototype,
-    'target',
-    ol.interaction.Draw.Event.prototype.target);
-
-goog.exportProperty(
-    ol.interaction.Draw.Event.prototype,
-    'preventDefault',
-    ol.interaction.Draw.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.interaction.Draw.Event.prototype,
-    'stopPropagation',
-    ol.interaction.Draw.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'getActive',
-    ol.interaction.Extent.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'getMap',
-    ol.interaction.Extent.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'setActive',
-    ol.interaction.Extent.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'get',
-    ol.interaction.Extent.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'getKeys',
-    ol.interaction.Extent.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'getProperties',
-    ol.interaction.Extent.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'set',
-    ol.interaction.Extent.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'setProperties',
-    ol.interaction.Extent.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'unset',
-    ol.interaction.Extent.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'changed',
-    ol.interaction.Extent.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'dispatchEvent',
-    ol.interaction.Extent.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'getRevision',
-    ol.interaction.Extent.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'on',
-    ol.interaction.Extent.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'once',
-    ol.interaction.Extent.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'un',
-    ol.interaction.Extent.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Extent.prototype,
-    'unByKey',
-    ol.interaction.Extent.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Extent.Event.prototype,
-    'type',
-    ol.interaction.Extent.Event.prototype.type);
-
-goog.exportProperty(
-    ol.interaction.Extent.Event.prototype,
-    'target',
-    ol.interaction.Extent.Event.prototype.target);
-
-goog.exportProperty(
-    ol.interaction.Extent.Event.prototype,
-    'preventDefault',
-    ol.interaction.Extent.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.interaction.Extent.Event.prototype,
-    'stopPropagation',
-    ol.interaction.Extent.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'getActive',
-    ol.interaction.KeyboardPan.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'getMap',
-    ol.interaction.KeyboardPan.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'setActive',
-    ol.interaction.KeyboardPan.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'get',
-    ol.interaction.KeyboardPan.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'getKeys',
-    ol.interaction.KeyboardPan.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'getProperties',
-    ol.interaction.KeyboardPan.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'set',
-    ol.interaction.KeyboardPan.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'setProperties',
-    ol.interaction.KeyboardPan.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'unset',
-    ol.interaction.KeyboardPan.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'changed',
-    ol.interaction.KeyboardPan.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'dispatchEvent',
-    ol.interaction.KeyboardPan.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'getRevision',
-    ol.interaction.KeyboardPan.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'on',
-    ol.interaction.KeyboardPan.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'once',
-    ol.interaction.KeyboardPan.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'un',
-    ol.interaction.KeyboardPan.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.KeyboardPan.prototype,
-    'unByKey',
-    ol.interaction.KeyboardPan.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'getActive',
-    ol.interaction.KeyboardZoom.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'getMap',
-    ol.interaction.KeyboardZoom.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'setActive',
-    ol.interaction.KeyboardZoom.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'get',
-    ol.interaction.KeyboardZoom.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'getKeys',
-    ol.interaction.KeyboardZoom.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'getProperties',
-    ol.interaction.KeyboardZoom.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'set',
-    ol.interaction.KeyboardZoom.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'setProperties',
-    ol.interaction.KeyboardZoom.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'unset',
-    ol.interaction.KeyboardZoom.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'changed',
-    ol.interaction.KeyboardZoom.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'dispatchEvent',
-    ol.interaction.KeyboardZoom.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'getRevision',
-    ol.interaction.KeyboardZoom.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'on',
-    ol.interaction.KeyboardZoom.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'once',
-    ol.interaction.KeyboardZoom.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'un',
-    ol.interaction.KeyboardZoom.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.KeyboardZoom.prototype,
-    'unByKey',
-    ol.interaction.KeyboardZoom.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'getActive',
-    ol.interaction.Modify.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'getMap',
-    ol.interaction.Modify.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'setActive',
-    ol.interaction.Modify.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'get',
-    ol.interaction.Modify.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'getKeys',
-    ol.interaction.Modify.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'getProperties',
-    ol.interaction.Modify.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'set',
-    ol.interaction.Modify.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'setProperties',
-    ol.interaction.Modify.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'unset',
-    ol.interaction.Modify.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'changed',
-    ol.interaction.Modify.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'dispatchEvent',
-    ol.interaction.Modify.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'getRevision',
-    ol.interaction.Modify.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'on',
-    ol.interaction.Modify.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'once',
-    ol.interaction.Modify.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'un',
-    ol.interaction.Modify.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Modify.prototype,
-    'unByKey',
-    ol.interaction.Modify.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Modify.Event.prototype,
-    'type',
-    ol.interaction.Modify.Event.prototype.type);
-
-goog.exportProperty(
-    ol.interaction.Modify.Event.prototype,
-    'target',
-    ol.interaction.Modify.Event.prototype.target);
-
-goog.exportProperty(
-    ol.interaction.Modify.Event.prototype,
-    'preventDefault',
-    ol.interaction.Modify.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.interaction.Modify.Event.prototype,
-    'stopPropagation',
-    ol.interaction.Modify.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'getActive',
-    ol.interaction.MouseWheelZoom.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'getMap',
-    ol.interaction.MouseWheelZoom.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'setActive',
-    ol.interaction.MouseWheelZoom.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'get',
-    ol.interaction.MouseWheelZoom.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'getKeys',
-    ol.interaction.MouseWheelZoom.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'getProperties',
-    ol.interaction.MouseWheelZoom.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'set',
-    ol.interaction.MouseWheelZoom.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'setProperties',
-    ol.interaction.MouseWheelZoom.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'unset',
-    ol.interaction.MouseWheelZoom.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'changed',
-    ol.interaction.MouseWheelZoom.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'dispatchEvent',
-    ol.interaction.MouseWheelZoom.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'getRevision',
-    ol.interaction.MouseWheelZoom.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'on',
-    ol.interaction.MouseWheelZoom.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'once',
-    ol.interaction.MouseWheelZoom.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'un',
-    ol.interaction.MouseWheelZoom.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.MouseWheelZoom.prototype,
-    'unByKey',
-    ol.interaction.MouseWheelZoom.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'getActive',
-    ol.interaction.PinchRotate.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'getMap',
-    ol.interaction.PinchRotate.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'setActive',
-    ol.interaction.PinchRotate.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'get',
-    ol.interaction.PinchRotate.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'getKeys',
-    ol.interaction.PinchRotate.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'getProperties',
-    ol.interaction.PinchRotate.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'set',
-    ol.interaction.PinchRotate.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'setProperties',
-    ol.interaction.PinchRotate.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'unset',
-    ol.interaction.PinchRotate.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'changed',
-    ol.interaction.PinchRotate.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'dispatchEvent',
-    ol.interaction.PinchRotate.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'getRevision',
-    ol.interaction.PinchRotate.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'on',
-    ol.interaction.PinchRotate.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'once',
-    ol.interaction.PinchRotate.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'un',
-    ol.interaction.PinchRotate.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.PinchRotate.prototype,
-    'unByKey',
-    ol.interaction.PinchRotate.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'getActive',
-    ol.interaction.PinchZoom.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'getMap',
-    ol.interaction.PinchZoom.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'setActive',
-    ol.interaction.PinchZoom.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'get',
-    ol.interaction.PinchZoom.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'getKeys',
-    ol.interaction.PinchZoom.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'getProperties',
-    ol.interaction.PinchZoom.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'set',
-    ol.interaction.PinchZoom.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'setProperties',
-    ol.interaction.PinchZoom.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'unset',
-    ol.interaction.PinchZoom.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'changed',
-    ol.interaction.PinchZoom.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'dispatchEvent',
-    ol.interaction.PinchZoom.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'getRevision',
-    ol.interaction.PinchZoom.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'on',
-    ol.interaction.PinchZoom.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'once',
-    ol.interaction.PinchZoom.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'un',
-    ol.interaction.PinchZoom.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.PinchZoom.prototype,
-    'unByKey',
-    ol.interaction.PinchZoom.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'getActive',
-    ol.interaction.Select.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'getMap',
-    ol.interaction.Select.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'setActive',
-    ol.interaction.Select.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'get',
-    ol.interaction.Select.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'getKeys',
-    ol.interaction.Select.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'getProperties',
-    ol.interaction.Select.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'set',
-    ol.interaction.Select.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'setProperties',
-    ol.interaction.Select.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'unset',
-    ol.interaction.Select.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'changed',
-    ol.interaction.Select.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'dispatchEvent',
-    ol.interaction.Select.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'getRevision',
-    ol.interaction.Select.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'on',
-    ol.interaction.Select.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'once',
-    ol.interaction.Select.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'un',
-    ol.interaction.Select.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Select.prototype,
-    'unByKey',
-    ol.interaction.Select.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Select.Event.prototype,
-    'type',
-    ol.interaction.Select.Event.prototype.type);
-
-goog.exportProperty(
-    ol.interaction.Select.Event.prototype,
-    'target',
-    ol.interaction.Select.Event.prototype.target);
-
-goog.exportProperty(
-    ol.interaction.Select.Event.prototype,
-    'preventDefault',
-    ol.interaction.Select.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.interaction.Select.Event.prototype,
-    'stopPropagation',
-    ol.interaction.Select.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'getActive',
-    ol.interaction.Snap.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'getMap',
-    ol.interaction.Snap.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'setActive',
-    ol.interaction.Snap.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'get',
-    ol.interaction.Snap.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'getKeys',
-    ol.interaction.Snap.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'getProperties',
-    ol.interaction.Snap.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'set',
-    ol.interaction.Snap.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'setProperties',
-    ol.interaction.Snap.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'unset',
-    ol.interaction.Snap.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'changed',
-    ol.interaction.Snap.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'dispatchEvent',
-    ol.interaction.Snap.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'getRevision',
-    ol.interaction.Snap.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'on',
-    ol.interaction.Snap.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'once',
-    ol.interaction.Snap.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'un',
-    ol.interaction.Snap.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Snap.prototype,
-    'unByKey',
-    ol.interaction.Snap.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'getActive',
-    ol.interaction.Translate.prototype.getActive);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'getMap',
-    ol.interaction.Translate.prototype.getMap);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'setActive',
-    ol.interaction.Translate.prototype.setActive);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'get',
-    ol.interaction.Translate.prototype.get);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'getKeys',
-    ol.interaction.Translate.prototype.getKeys);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'getProperties',
-    ol.interaction.Translate.prototype.getProperties);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'set',
-    ol.interaction.Translate.prototype.set);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'setProperties',
-    ol.interaction.Translate.prototype.setProperties);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'unset',
-    ol.interaction.Translate.prototype.unset);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'changed',
-    ol.interaction.Translate.prototype.changed);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'dispatchEvent',
-    ol.interaction.Translate.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'getRevision',
-    ol.interaction.Translate.prototype.getRevision);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'on',
-    ol.interaction.Translate.prototype.on);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'once',
-    ol.interaction.Translate.prototype.once);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'un',
-    ol.interaction.Translate.prototype.un);
-
-goog.exportProperty(
-    ol.interaction.Translate.prototype,
-    'unByKey',
-    ol.interaction.Translate.prototype.unByKey);
-
-goog.exportProperty(
-    ol.interaction.Translate.Event.prototype,
-    'type',
-    ol.interaction.Translate.Event.prototype.type);
-
-goog.exportProperty(
-    ol.interaction.Translate.Event.prototype,
-    'target',
-    ol.interaction.Translate.Event.prototype.target);
-
-goog.exportProperty(
-    ol.interaction.Translate.Event.prototype,
-    'preventDefault',
-    ol.interaction.Translate.Event.prototype.preventDefault);
-
-goog.exportProperty(
-    ol.interaction.Translate.Event.prototype,
-    'stopPropagation',
-    ol.interaction.Translate.Event.prototype.stopPropagation);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'get',
-    ol.geom.Geometry.prototype.get);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'getKeys',
-    ol.geom.Geometry.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'getProperties',
-    ol.geom.Geometry.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'set',
-    ol.geom.Geometry.prototype.set);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'setProperties',
-    ol.geom.Geometry.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'unset',
-    ol.geom.Geometry.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'changed',
-    ol.geom.Geometry.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'dispatchEvent',
-    ol.geom.Geometry.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'getRevision',
-    ol.geom.Geometry.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'on',
-    ol.geom.Geometry.prototype.on);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'once',
-    ol.geom.Geometry.prototype.once);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'un',
-    ol.geom.Geometry.prototype.un);
-
-goog.exportProperty(
-    ol.geom.Geometry.prototype,
-    'unByKey',
-    ol.geom.Geometry.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getClosestPoint',
-    ol.geom.SimpleGeometry.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'intersectsCoordinate',
-    ol.geom.SimpleGeometry.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getExtent',
-    ol.geom.SimpleGeometry.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'rotate',
-    ol.geom.SimpleGeometry.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'scale',
-    ol.geom.SimpleGeometry.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'simplify',
-    ol.geom.SimpleGeometry.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'transform',
-    ol.geom.SimpleGeometry.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'get',
-    ol.geom.SimpleGeometry.prototype.get);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getKeys',
-    ol.geom.SimpleGeometry.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getProperties',
-    ol.geom.SimpleGeometry.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'set',
-    ol.geom.SimpleGeometry.prototype.set);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'setProperties',
-    ol.geom.SimpleGeometry.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'unset',
-    ol.geom.SimpleGeometry.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'changed',
-    ol.geom.SimpleGeometry.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'dispatchEvent',
-    ol.geom.SimpleGeometry.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'getRevision',
-    ol.geom.SimpleGeometry.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'on',
-    ol.geom.SimpleGeometry.prototype.on);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'once',
-    ol.geom.SimpleGeometry.prototype.once);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'un',
-    ol.geom.SimpleGeometry.prototype.un);
-
-goog.exportProperty(
-    ol.geom.SimpleGeometry.prototype,
-    'unByKey',
-    ol.geom.SimpleGeometry.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getFirstCoordinate',
-    ol.geom.Circle.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getLastCoordinate',
-    ol.geom.Circle.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getLayout',
-    ol.geom.Circle.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'rotate',
-    ol.geom.Circle.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'scale',
-    ol.geom.Circle.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getClosestPoint',
-    ol.geom.Circle.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'intersectsCoordinate',
-    ol.geom.Circle.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getExtent',
-    ol.geom.Circle.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'simplify',
-    ol.geom.Circle.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'get',
-    ol.geom.Circle.prototype.get);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getKeys',
-    ol.geom.Circle.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getProperties',
-    ol.geom.Circle.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'set',
-    ol.geom.Circle.prototype.set);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'setProperties',
-    ol.geom.Circle.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'unset',
-    ol.geom.Circle.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'changed',
-    ol.geom.Circle.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'dispatchEvent',
-    ol.geom.Circle.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'getRevision',
-    ol.geom.Circle.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'on',
-    ol.geom.Circle.prototype.on);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'once',
-    ol.geom.Circle.prototype.once);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'un',
-    ol.geom.Circle.prototype.un);
-
-goog.exportProperty(
-    ol.geom.Circle.prototype,
-    'unByKey',
-    ol.geom.Circle.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'getClosestPoint',
-    ol.geom.GeometryCollection.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'intersectsCoordinate',
-    ol.geom.GeometryCollection.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'getExtent',
-    ol.geom.GeometryCollection.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'rotate',
-    ol.geom.GeometryCollection.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'scale',
-    ol.geom.GeometryCollection.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'simplify',
-    ol.geom.GeometryCollection.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'transform',
-    ol.geom.GeometryCollection.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'get',
-    ol.geom.GeometryCollection.prototype.get);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'getKeys',
-    ol.geom.GeometryCollection.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'getProperties',
-    ol.geom.GeometryCollection.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'set',
-    ol.geom.GeometryCollection.prototype.set);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'setProperties',
-    ol.geom.GeometryCollection.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'unset',
-    ol.geom.GeometryCollection.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'changed',
-    ol.geom.GeometryCollection.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'dispatchEvent',
-    ol.geom.GeometryCollection.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'getRevision',
-    ol.geom.GeometryCollection.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'on',
-    ol.geom.GeometryCollection.prototype.on);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'once',
-    ol.geom.GeometryCollection.prototype.once);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'un',
-    ol.geom.GeometryCollection.prototype.un);
-
-goog.exportProperty(
-    ol.geom.GeometryCollection.prototype,
-    'unByKey',
-    ol.geom.GeometryCollection.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getFirstCoordinate',
-    ol.geom.LinearRing.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getLastCoordinate',
-    ol.geom.LinearRing.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getLayout',
-    ol.geom.LinearRing.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'rotate',
-    ol.geom.LinearRing.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'scale',
-    ol.geom.LinearRing.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getClosestPoint',
-    ol.geom.LinearRing.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'intersectsCoordinate',
-    ol.geom.LinearRing.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getExtent',
-    ol.geom.LinearRing.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'simplify',
-    ol.geom.LinearRing.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'transform',
-    ol.geom.LinearRing.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'get',
-    ol.geom.LinearRing.prototype.get);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getKeys',
-    ol.geom.LinearRing.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getProperties',
-    ol.geom.LinearRing.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'set',
-    ol.geom.LinearRing.prototype.set);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'setProperties',
-    ol.geom.LinearRing.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'unset',
-    ol.geom.LinearRing.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'changed',
-    ol.geom.LinearRing.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'dispatchEvent',
-    ol.geom.LinearRing.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'getRevision',
-    ol.geom.LinearRing.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'on',
-    ol.geom.LinearRing.prototype.on);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'once',
-    ol.geom.LinearRing.prototype.once);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'un',
-    ol.geom.LinearRing.prototype.un);
-
-goog.exportProperty(
-    ol.geom.LinearRing.prototype,
-    'unByKey',
-    ol.geom.LinearRing.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getFirstCoordinate',
-    ol.geom.LineString.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getLastCoordinate',
-    ol.geom.LineString.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getLayout',
-    ol.geom.LineString.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'rotate',
-    ol.geom.LineString.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'scale',
-    ol.geom.LineString.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getClosestPoint',
-    ol.geom.LineString.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'intersectsCoordinate',
-    ol.geom.LineString.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getExtent',
-    ol.geom.LineString.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'simplify',
-    ol.geom.LineString.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'transform',
-    ol.geom.LineString.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'get',
-    ol.geom.LineString.prototype.get);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getKeys',
-    ol.geom.LineString.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getProperties',
-    ol.geom.LineString.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'set',
-    ol.geom.LineString.prototype.set);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'setProperties',
-    ol.geom.LineString.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'unset',
-    ol.geom.LineString.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'changed',
-    ol.geom.LineString.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'dispatchEvent',
-    ol.geom.LineString.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'getRevision',
-    ol.geom.LineString.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'on',
-    ol.geom.LineString.prototype.on);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'once',
-    ol.geom.LineString.prototype.once);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'un',
-    ol.geom.LineString.prototype.un);
-
-goog.exportProperty(
-    ol.geom.LineString.prototype,
-    'unByKey',
-    ol.geom.LineString.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getFirstCoordinate',
-    ol.geom.MultiLineString.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getLastCoordinate',
-    ol.geom.MultiLineString.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getLayout',
-    ol.geom.MultiLineString.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'rotate',
-    ol.geom.MultiLineString.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'scale',
-    ol.geom.MultiLineString.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getClosestPoint',
-    ol.geom.MultiLineString.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'intersectsCoordinate',
-    ol.geom.MultiLineString.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getExtent',
-    ol.geom.MultiLineString.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'simplify',
-    ol.geom.MultiLineString.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'transform',
-    ol.geom.MultiLineString.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'get',
-    ol.geom.MultiLineString.prototype.get);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getKeys',
-    ol.geom.MultiLineString.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getProperties',
-    ol.geom.MultiLineString.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'set',
-    ol.geom.MultiLineString.prototype.set);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'setProperties',
-    ol.geom.MultiLineString.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'unset',
-    ol.geom.MultiLineString.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'changed',
-    ol.geom.MultiLineString.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'dispatchEvent',
-    ol.geom.MultiLineString.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'getRevision',
-    ol.geom.MultiLineString.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'on',
-    ol.geom.MultiLineString.prototype.on);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'once',
-    ol.geom.MultiLineString.prototype.once);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'un',
-    ol.geom.MultiLineString.prototype.un);
-
-goog.exportProperty(
-    ol.geom.MultiLineString.prototype,
-    'unByKey',
-    ol.geom.MultiLineString.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getFirstCoordinate',
-    ol.geom.MultiPoint.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getLastCoordinate',
-    ol.geom.MultiPoint.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getLayout',
-    ol.geom.MultiPoint.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'rotate',
-    ol.geom.MultiPoint.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'scale',
-    ol.geom.MultiPoint.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getClosestPoint',
-    ol.geom.MultiPoint.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'intersectsCoordinate',
-    ol.geom.MultiPoint.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getExtent',
-    ol.geom.MultiPoint.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'simplify',
-    ol.geom.MultiPoint.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'transform',
-    ol.geom.MultiPoint.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'get',
-    ol.geom.MultiPoint.prototype.get);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getKeys',
-    ol.geom.MultiPoint.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getProperties',
-    ol.geom.MultiPoint.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'set',
-    ol.geom.MultiPoint.prototype.set);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'setProperties',
-    ol.geom.MultiPoint.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'unset',
-    ol.geom.MultiPoint.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'changed',
-    ol.geom.MultiPoint.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'dispatchEvent',
-    ol.geom.MultiPoint.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'getRevision',
-    ol.geom.MultiPoint.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'on',
-    ol.geom.MultiPoint.prototype.on);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'once',
-    ol.geom.MultiPoint.prototype.once);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'un',
-    ol.geom.MultiPoint.prototype.un);
-
-goog.exportProperty(
-    ol.geom.MultiPoint.prototype,
-    'unByKey',
-    ol.geom.MultiPoint.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getFirstCoordinate',
-    ol.geom.MultiPolygon.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getLastCoordinate',
-    ol.geom.MultiPolygon.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getLayout',
-    ol.geom.MultiPolygon.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'rotate',
-    ol.geom.MultiPolygon.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'scale',
-    ol.geom.MultiPolygon.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getClosestPoint',
-    ol.geom.MultiPolygon.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'intersectsCoordinate',
-    ol.geom.MultiPolygon.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getExtent',
-    ol.geom.MultiPolygon.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'simplify',
-    ol.geom.MultiPolygon.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'transform',
-    ol.geom.MultiPolygon.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'get',
-    ol.geom.MultiPolygon.prototype.get);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getKeys',
-    ol.geom.MultiPolygon.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getProperties',
-    ol.geom.MultiPolygon.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'set',
-    ol.geom.MultiPolygon.prototype.set);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'setProperties',
-    ol.geom.MultiPolygon.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'unset',
-    ol.geom.MultiPolygon.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'changed',
-    ol.geom.MultiPolygon.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'dispatchEvent',
-    ol.geom.MultiPolygon.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'getRevision',
-    ol.geom.MultiPolygon.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'on',
-    ol.geom.MultiPolygon.prototype.on);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'once',
-    ol.geom.MultiPolygon.prototype.once);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'un',
-    ol.geom.MultiPolygon.prototype.un);
-
-goog.exportProperty(
-    ol.geom.MultiPolygon.prototype,
-    'unByKey',
-    ol.geom.MultiPolygon.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getFirstCoordinate',
-    ol.geom.Point.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getLastCoordinate',
-    ol.geom.Point.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getLayout',
-    ol.geom.Point.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'rotate',
-    ol.geom.Point.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'scale',
-    ol.geom.Point.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getClosestPoint',
-    ol.geom.Point.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'intersectsCoordinate',
-    ol.geom.Point.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getExtent',
-    ol.geom.Point.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'simplify',
-    ol.geom.Point.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'transform',
-    ol.geom.Point.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'get',
-    ol.geom.Point.prototype.get);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getKeys',
-    ol.geom.Point.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getProperties',
-    ol.geom.Point.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'set',
-    ol.geom.Point.prototype.set);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'setProperties',
-    ol.geom.Point.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'unset',
-    ol.geom.Point.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'changed',
-    ol.geom.Point.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'dispatchEvent',
-    ol.geom.Point.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'getRevision',
-    ol.geom.Point.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'on',
-    ol.geom.Point.prototype.on);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'once',
-    ol.geom.Point.prototype.once);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'un',
-    ol.geom.Point.prototype.un);
-
-goog.exportProperty(
-    ol.geom.Point.prototype,
-    'unByKey',
-    ol.geom.Point.prototype.unByKey);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getFirstCoordinate',
-    ol.geom.Polygon.prototype.getFirstCoordinate);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getLastCoordinate',
-    ol.geom.Polygon.prototype.getLastCoordinate);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getLayout',
-    ol.geom.Polygon.prototype.getLayout);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'rotate',
-    ol.geom.Polygon.prototype.rotate);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'scale',
-    ol.geom.Polygon.prototype.scale);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getClosestPoint',
-    ol.geom.Polygon.prototype.getClosestPoint);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'intersectsCoordinate',
-    ol.geom.Polygon.prototype.intersectsCoordinate);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getExtent',
-    ol.geom.Polygon.prototype.getExtent);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'simplify',
-    ol.geom.Polygon.prototype.simplify);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'transform',
-    ol.geom.Polygon.prototype.transform);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'get',
-    ol.geom.Polygon.prototype.get);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getKeys',
-    ol.geom.Polygon.prototype.getKeys);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getProperties',
-    ol.geom.Polygon.prototype.getProperties);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'set',
-    ol.geom.Polygon.prototype.set);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'setProperties',
-    ol.geom.Polygon.prototype.setProperties);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'unset',
-    ol.geom.Polygon.prototype.unset);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'changed',
-    ol.geom.Polygon.prototype.changed);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'dispatchEvent',
-    ol.geom.Polygon.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'getRevision',
-    ol.geom.Polygon.prototype.getRevision);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'on',
-    ol.geom.Polygon.prototype.on);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'once',
-    ol.geom.Polygon.prototype.once);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'un',
-    ol.geom.Polygon.prototype.un);
-
-goog.exportProperty(
-    ol.geom.Polygon.prototype,
-    'unByKey',
-    ol.geom.Polygon.prototype.unByKey);
-
-goog.exportProperty(
-    ol.format.GML.prototype,
-    'readFeatures',
-    ol.format.GML.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.GML2.prototype,
-    'readFeatures',
-    ol.format.GML2.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.format.GML3.prototype,
-    'readFeatures',
-    ol.format.GML3.prototype.readFeatures);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'get',
-    ol.control.Control.prototype.get);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'getKeys',
-    ol.control.Control.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'getProperties',
-    ol.control.Control.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'set',
-    ol.control.Control.prototype.set);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'setProperties',
-    ol.control.Control.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'unset',
-    ol.control.Control.prototype.unset);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'changed',
-    ol.control.Control.prototype.changed);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'dispatchEvent',
-    ol.control.Control.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'getRevision',
-    ol.control.Control.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'on',
-    ol.control.Control.prototype.on);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'once',
-    ol.control.Control.prototype.once);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'un',
-    ol.control.Control.prototype.un);
-
-goog.exportProperty(
-    ol.control.Control.prototype,
-    'unByKey',
-    ol.control.Control.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'getMap',
-    ol.control.Attribution.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'setMap',
-    ol.control.Attribution.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'setTarget',
-    ol.control.Attribution.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'get',
-    ol.control.Attribution.prototype.get);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'getKeys',
-    ol.control.Attribution.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'getProperties',
-    ol.control.Attribution.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'set',
-    ol.control.Attribution.prototype.set);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'setProperties',
-    ol.control.Attribution.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'unset',
-    ol.control.Attribution.prototype.unset);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'changed',
-    ol.control.Attribution.prototype.changed);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'dispatchEvent',
-    ol.control.Attribution.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'getRevision',
-    ol.control.Attribution.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'on',
-    ol.control.Attribution.prototype.on);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'once',
-    ol.control.Attribution.prototype.once);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'un',
-    ol.control.Attribution.prototype.un);
-
-goog.exportProperty(
-    ol.control.Attribution.prototype,
-    'unByKey',
-    ol.control.Attribution.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'getMap',
-    ol.control.FullScreen.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'setMap',
-    ol.control.FullScreen.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'setTarget',
-    ol.control.FullScreen.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'get',
-    ol.control.FullScreen.prototype.get);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'getKeys',
-    ol.control.FullScreen.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'getProperties',
-    ol.control.FullScreen.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'set',
-    ol.control.FullScreen.prototype.set);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'setProperties',
-    ol.control.FullScreen.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'unset',
-    ol.control.FullScreen.prototype.unset);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'changed',
-    ol.control.FullScreen.prototype.changed);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'dispatchEvent',
-    ol.control.FullScreen.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'getRevision',
-    ol.control.FullScreen.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'on',
-    ol.control.FullScreen.prototype.on);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'once',
-    ol.control.FullScreen.prototype.once);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'un',
-    ol.control.FullScreen.prototype.un);
-
-goog.exportProperty(
-    ol.control.FullScreen.prototype,
-    'unByKey',
-    ol.control.FullScreen.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getMap',
-    ol.control.MousePosition.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'setMap',
-    ol.control.MousePosition.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'setTarget',
-    ol.control.MousePosition.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'get',
-    ol.control.MousePosition.prototype.get);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getKeys',
-    ol.control.MousePosition.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getProperties',
-    ol.control.MousePosition.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'set',
-    ol.control.MousePosition.prototype.set);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'setProperties',
-    ol.control.MousePosition.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'unset',
-    ol.control.MousePosition.prototype.unset);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'changed',
-    ol.control.MousePosition.prototype.changed);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'dispatchEvent',
-    ol.control.MousePosition.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'getRevision',
-    ol.control.MousePosition.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'on',
-    ol.control.MousePosition.prototype.on);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'once',
-    ol.control.MousePosition.prototype.once);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'un',
-    ol.control.MousePosition.prototype.un);
-
-goog.exportProperty(
-    ol.control.MousePosition.prototype,
-    'unByKey',
-    ol.control.MousePosition.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'getMap',
-    ol.control.OverviewMap.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'setMap',
-    ol.control.OverviewMap.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'setTarget',
-    ol.control.OverviewMap.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'get',
-    ol.control.OverviewMap.prototype.get);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'getKeys',
-    ol.control.OverviewMap.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'getProperties',
-    ol.control.OverviewMap.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'set',
-    ol.control.OverviewMap.prototype.set);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'setProperties',
-    ol.control.OverviewMap.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'unset',
-    ol.control.OverviewMap.prototype.unset);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'changed',
-    ol.control.OverviewMap.prototype.changed);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'dispatchEvent',
-    ol.control.OverviewMap.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'getRevision',
-    ol.control.OverviewMap.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'on',
-    ol.control.OverviewMap.prototype.on);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'once',
-    ol.control.OverviewMap.prototype.once);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'un',
-    ol.control.OverviewMap.prototype.un);
-
-goog.exportProperty(
-    ol.control.OverviewMap.prototype,
-    'unByKey',
-    ol.control.OverviewMap.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'getMap',
-    ol.control.Rotate.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'setMap',
-    ol.control.Rotate.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'setTarget',
-    ol.control.Rotate.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'get',
-    ol.control.Rotate.prototype.get);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'getKeys',
-    ol.control.Rotate.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'getProperties',
-    ol.control.Rotate.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'set',
-    ol.control.Rotate.prototype.set);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'setProperties',
-    ol.control.Rotate.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'unset',
-    ol.control.Rotate.prototype.unset);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'changed',
-    ol.control.Rotate.prototype.changed);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'dispatchEvent',
-    ol.control.Rotate.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'getRevision',
-    ol.control.Rotate.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'on',
-    ol.control.Rotate.prototype.on);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'once',
-    ol.control.Rotate.prototype.once);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'un',
-    ol.control.Rotate.prototype.un);
-
-goog.exportProperty(
-    ol.control.Rotate.prototype,
-    'unByKey',
-    ol.control.Rotate.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'getMap',
-    ol.control.ScaleLine.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'setMap',
-    ol.control.ScaleLine.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'setTarget',
-    ol.control.ScaleLine.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'get',
-    ol.control.ScaleLine.prototype.get);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'getKeys',
-    ol.control.ScaleLine.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'getProperties',
-    ol.control.ScaleLine.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'set',
-    ol.control.ScaleLine.prototype.set);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'setProperties',
-    ol.control.ScaleLine.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'unset',
-    ol.control.ScaleLine.prototype.unset);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'changed',
-    ol.control.ScaleLine.prototype.changed);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'dispatchEvent',
-    ol.control.ScaleLine.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'getRevision',
-    ol.control.ScaleLine.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'on',
-    ol.control.ScaleLine.prototype.on);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'once',
-    ol.control.ScaleLine.prototype.once);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'un',
-    ol.control.ScaleLine.prototype.un);
-
-goog.exportProperty(
-    ol.control.ScaleLine.prototype,
-    'unByKey',
-    ol.control.ScaleLine.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'getMap',
-    ol.control.Zoom.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'setMap',
-    ol.control.Zoom.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'setTarget',
-    ol.control.Zoom.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'get',
-    ol.control.Zoom.prototype.get);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'getKeys',
-    ol.control.Zoom.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'getProperties',
-    ol.control.Zoom.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'set',
-    ol.control.Zoom.prototype.set);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'setProperties',
-    ol.control.Zoom.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'unset',
-    ol.control.Zoom.prototype.unset);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'changed',
-    ol.control.Zoom.prototype.changed);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'dispatchEvent',
-    ol.control.Zoom.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'getRevision',
-    ol.control.Zoom.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'on',
-    ol.control.Zoom.prototype.on);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'once',
-    ol.control.Zoom.prototype.once);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'un',
-    ol.control.Zoom.prototype.un);
-
-goog.exportProperty(
-    ol.control.Zoom.prototype,
-    'unByKey',
-    ol.control.Zoom.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'getMap',
-    ol.control.ZoomSlider.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'setMap',
-    ol.control.ZoomSlider.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'setTarget',
-    ol.control.ZoomSlider.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'get',
-    ol.control.ZoomSlider.prototype.get);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'getKeys',
-    ol.control.ZoomSlider.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'getProperties',
-    ol.control.ZoomSlider.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'set',
-    ol.control.ZoomSlider.prototype.set);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'setProperties',
-    ol.control.ZoomSlider.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'unset',
-    ol.control.ZoomSlider.prototype.unset);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'changed',
-    ol.control.ZoomSlider.prototype.changed);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'dispatchEvent',
-    ol.control.ZoomSlider.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'getRevision',
-    ol.control.ZoomSlider.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'on',
-    ol.control.ZoomSlider.prototype.on);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'once',
-    ol.control.ZoomSlider.prototype.once);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'un',
-    ol.control.ZoomSlider.prototype.un);
-
-goog.exportProperty(
-    ol.control.ZoomSlider.prototype,
-    'unByKey',
-    ol.control.ZoomSlider.prototype.unByKey);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'getMap',
-    ol.control.ZoomToExtent.prototype.getMap);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'setMap',
-    ol.control.ZoomToExtent.prototype.setMap);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'setTarget',
-    ol.control.ZoomToExtent.prototype.setTarget);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'get',
-    ol.control.ZoomToExtent.prototype.get);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'getKeys',
-    ol.control.ZoomToExtent.prototype.getKeys);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'getProperties',
-    ol.control.ZoomToExtent.prototype.getProperties);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'set',
-    ol.control.ZoomToExtent.prototype.set);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'setProperties',
-    ol.control.ZoomToExtent.prototype.setProperties);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'unset',
-    ol.control.ZoomToExtent.prototype.unset);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'changed',
-    ol.control.ZoomToExtent.prototype.changed);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'dispatchEvent',
-    ol.control.ZoomToExtent.prototype.dispatchEvent);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'getRevision',
-    ol.control.ZoomToExtent.prototype.getRevision);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'on',
-    ol.control.ZoomToExtent.prototype.on);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'once',
-    ol.control.ZoomToExtent.prototype.once);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'un',
-    ol.control.ZoomToExtent.prototype.un);
-
-goog.exportProperty(
-    ol.control.ZoomToExtent.prototype,
-    'unByKey',
-    ol.control.ZoomToExtent.prototype.unByKey);
-ol.VERSION = 'v3.19.0';
-OPENLAYERS.ol = ol;
-
-  return OPENLAYERS.ol;
-}));
diff --git a/debian/JS/openlayers-3.19.0/ol.css b/debian/JS/openlayers-3.19.0/ol.css
deleted file mode 100644
index 538a602..0000000
--- a/debian/JS/openlayers-3.19.0/ol.css
+++ /dev/null
@@ -1,241 +0,0 @@
-.ol-box {
-  box-sizing: border-box;
-  border-radius: 2px;
-  border: 2px solid blue;
-}
-
-.ol-mouse-position {
-  top: 8px;
-  right: 8px;
-  position: absolute;
-}
-
-.ol-scale-line {
-  background: rgba(0,60,136,0.3);
-  border-radius: 4px;
-  bottom: 8px;
-  left: 8px;
-  padding: 2px;
-  position: absolute;
-}
-.ol-scale-line-inner {
-  border: 1px solid #eee;
-  border-top: none;
-  color: #eee;
-  font-size: 10px;
-  text-align: center;
-  margin: 1px;
-  will-change: contents, width;
-}
-.ol-overlay-container {
-  will-change: left,right,top,bottom;
-}
-
-.ol-unsupported {
-  display: none;
-}
-.ol-viewport .ol-unselectable {
-  -webkit-touch-callout: none;
-  -webkit-user-select: none;
-  -khtml-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
-  user-select: none;
-  -webkit-tap-highlight-color: rgba(0,0,0,0);
-}
-
-.ol-control {
-  position: absolute;
-  background-color: rgba(255,255,255,0.4);
-  border-radius: 4px;
-  padding: 2px;
-}
-.ol-control:hover {
-  background-color: rgba(255,255,255,0.6);
-}
-.ol-zoom {
-  top: .5em;
-  left: .5em;
-}
-.ol-rotate {
-  top: .5em;
-  right: .5em;
-  transition: opacity .25s linear, visibility 0s linear;
-}
-.ol-rotate.ol-hidden {
-  opacity: 0;
-  visibility: hidden;
-  transition: opacity .25s linear, visibility 0s linear .25s;
-}
-.ol-zoom-extent {
-  top: 4.643em;
-  left: .5em;
-}
-.ol-full-screen {
-  right: .5em;
-  top: .5em;
-}
- at media print {
-  .ol-control {
-    display: none;
-  }
-}
-
-.ol-control button {
-  display: block;
-  margin: 1px;
-  padding: 0;
-  color: white;
-  font-size: 1.14em;
-  font-weight: bold;
-  text-decoration: none;
-  text-align: center;
-  height: 1.375em;
-  width: 1.375em;
-  line-height: .4em;
-  background-color: rgba(0,60,136,0.5);
-  border: none;
-  border-radius: 2px;
-}
-.ol-control button::-moz-focus-inner {
-  border: none;
-  padding: 0;
-}
-.ol-zoom-extent button {
-  line-height: 1.4em;
-}
-.ol-compass {
-  display: block;
-  font-weight: normal;
-  font-size: 1.2em;
-  will-change: transform;
-}
-.ol-touch .ol-control button {
-  font-size: 1.5em;
-}
-.ol-touch .ol-zoom-extent {
-  top: 5.5em;
-}
-.ol-control button:hover,
-.ol-control button:focus {
-  text-decoration: none;
-  background-color: rgba(0,60,136,0.7);
-}
-.ol-zoom .ol-zoom-in {
-  border-radius: 2px 2px 0 0;
-}
-.ol-zoom .ol-zoom-out {
-  border-radius: 0 0 2px 2px;
-}
-
-
-.ol-attribution {
-  text-align: right;
-  bottom: .5em;
-  right: .5em;
-  max-width: calc(100% - 1.3em);
-}
-
-.ol-attribution ul {
-  margin: 0;
-  padding: 0 .5em;
-  font-size: .7rem;
-  line-height: 1.375em;
-  color: #000;
-  text-shadow: 0 0 2px #fff;
-}
-.ol-attribution li {
-  display: inline;
-  list-style: none;
-  line-height: inherit;
-}
-.ol-attribution li:not(:last-child):after {
-  content: " ";
-}
-.ol-attribution img {
-  max-height: 2em;
-  max-width: inherit;
-  vertical-align: middle;
-}
-.ol-attribution ul, .ol-attribution button {
-  display: inline-block;
-}
-.ol-attribution.ol-collapsed ul {
-  display: none;
-}
-.ol-attribution.ol-logo-only ul {
-  display: block;
-}
-.ol-attribution:not(.ol-collapsed) {
-  background: rgba(255,255,255,0.8);
-}
-.ol-attribution.ol-uncollapsible {
-  bottom: 0;
-  right: 0;
-  border-radius: 4px 0 0;
-  height: 1.1em;
-  line-height: 1em;
-}
-.ol-attribution.ol-logo-only {
-  background: transparent;
-  bottom: .4em;
-  height: 1.1em;
-  line-height: 1em;
-}
-.ol-attribution.ol-uncollapsible img {
-  margin-top: -.2em;
-  max-height: 1.6em;
-}
-.ol-attribution.ol-logo-only button,
-.ol-attribution.ol-uncollapsible button {
-  display: none;
-}
-
-.ol-zoomslider {
-  top: 4.5em;
-  left: .5em;
-  height: 200px;
-}
-.ol-zoomslider button {
-  position: relative;
-  height: 10px;
-}
-
-.ol-touch .ol-zoomslider {
-  top: 5.5em;
-}
-
-.ol-overviewmap {
-  left: 0.5em;
-  bottom: 0.5em;
-}
-.ol-overviewmap.ol-uncollapsible {
-  bottom: 0;
-  left: 0;
-  border-radius: 0 4px 0 0;
-}
-.ol-overviewmap .ol-overviewmap-map,
-.ol-overviewmap button {
-  display: inline-block;
-}
-.ol-overviewmap .ol-overviewmap-map {
-  border: 1px solid #7b98bc;
-  height: 150px;
-  margin: 2px;
-  width: 150px;
-}
-.ol-overviewmap:not(.ol-collapsed) button{
-  bottom: 1px;
-  left: 2px;
-  position: absolute;
-}
-.ol-overviewmap.ol-collapsed .ol-overviewmap-map,
-.ol-overviewmap.ol-uncollapsible button {
-  display: none;
-}
-.ol-overviewmap:not(.ol-collapsed) {
-  background: rgba(255,255,255,0.8);
-}
-.ol-overviewmap-box {
-  border: 2px dotted rgba(0,60,136,0.7);
-}
diff --git a/debian/README.Debian b/debian/README.Debian
deleted file mode 100644
index d9910fa..0000000
--- a/debian/README.Debian
+++ /dev/null
@@ -1,18 +0,0 @@
-Orthanc-WSI brings support of whole-slide imaging for digital
-pathology into Orthanc, the lightweight, RESTful Vendor Neutral
-Archive for medical imaging.
-
-It is made of 3 components:
-
-(a) A command-line tool called "OrthancWSIDicomizer" that converts
-    whole-slide images to DICOM. OpenSlide can be used to decode
-    proprietary file formats.
-
-(b) An Orthanc plugin to display such DICOM images in a standard Web
-    browser thanks to OpenLayers.
-
-(c) A command-line tool called "OrthancWSIDicomToTiff" that allows one
-    to create a standard hierarchical TIFF file from some DICOM
-    whole-slide image stored in Orthanc.
-
-Homepage: http://www.orthanc-server.com/static.php?page=wsi
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 9546612..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-orthanc-wsi (0.1+dfsg-1) UNRELEASED; urgency=medium
-
-  * Initial release. (Closes: #842168)
-
- -- Sebastien Jodogne <s.jodogne at gmail.com>  Thu, 27 Oct 2016 14:39:21 +0200
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec63514..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
deleted file mode 100644
index f8aff0e..0000000
--- a/debian/control
+++ /dev/null
@@ -1,41 +0,0 @@
-Source: orthanc-wsi
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Sebastien Jodogne <s.jodogne at gmail.com>,
-           Andreas Tille <tille at debian.org>
-Section: science
-Priority: optional
-Build-Depends: cmake,
-               debhelper (>= 9),
-               libboost-all-dev,
-               libcurl4-openssl-dev | libcurl4-dev,
-               libdcmtk-dev,
-               libjpeg-dev,
-               libjsoncpp-dev,
-               libopenjp2-7-dev,
-               libpng-dev,
-               libtiff5-dev,
-               orthanc-dev (>= 1.1.0),
-               uuid-dev,
-               yui-compressor,
-               zlib1g-dev
-Standards-Version: 3.9.8
-Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/orthanc-wsi/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/orthanc-wsi/trunk/
-Homepage: http://www.orthanc-server.com/static.php?page=wsi
-
-Package: orthanc-wsi
-Architecture: any
-Depends: ${misc:Depends},
-         ${shlibs:Depends}
-Recommends: orthanc (>= 1.1.0),
-            libopenslide0
-Description: Whole-slide imaging support for Orthanc (digital pathology)
- Orthanc-WSI brings support of whole-slide imaging for digital
- pathology into Orthanc, the lightweight, RESTful Vendor Neutral
- Archive for medical imaging.
- .
- This package contains two command-line tools to convert whole-slide
- images to and from DICOM. Support for proprietary file formats is
- available through OpenSlide. The package also contains an Orthanc
- plugin to display such DICOM images by any standard Web browser. The
- implementation follows DICOM Supplement 145.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 17f5e1b..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,149 +0,0 @@
-Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: OrthancWSI
-Upstream-Contact: Sebastien Jodogne <s.jodogne at gmail.com>
-Source: https://bitbucket.org/sjodogne/orthanc-wsi/
-Files-Excluded:
-	Framework/Orthanc/Resources/ThirdParty/patch/*
-
-Files: *
-Copyright: 2012-2016 Sebastien Jodogne <s.jodogne at gmail.com>, University Hospital of Liege, Belgium
-License: AGPL-3+
-
-Files: Framework/Orthanc/*
-Copyright: 2012-2016 Sebastien Jodogne <s.jodogne at gmail.com>, University Hospital of Liege, Belgium
-License: GPL-3+ with OpenSSL exception
-
-Files: Framework/Orthanc/Resources/ThirdParty/base64/*
-Copyright: 2004-2008 Rene Nyffenegger
-License: zlib
-
-Files: Framework/Orthanc/Resources/ThirdParty/VisualStudio/*
-Copyright: 2006-2013 Alexander Chemeris
-License: BSD-3-clause
-
-Files: debian/JS/openlayers-3.19.0/*
-Copyright: 2005-2016 OpenLayers Contributors. All rights reserved.
-License: BSD-2-clause
-
-
-License: AGPL-3+
- This program is free software: you can redistribute it and/or
- modify it under the terms of the GNU Affero General Public License
- as published by the Free Software Foundation, either version 3 of
- the License, or (at your option) any later version.
- .
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- Affero General Public License for more details.
- . 
- You should have received a copy of the GNU Affero General Public License
- along with this package; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-
-License: GPL-3+ with OpenSSL exception
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or (at
- your option) any later version.
- .
- In addition, as a special exception, the copyright holders of this
- program give permission to link the code of its release with the
- OpenSSL project's "OpenSSL" library (or with modified versions of it
- that use the same license as the "OpenSSL" library), and distribute
- the linked executables. You must obey the GNU General Public License
- in all respects for all of the code used other than "OpenSSL". If you
- modify file(s) with this exception, you may extend this exception to
- your version of the file(s), but you are not obligated to do so. If
- you do not wish to do so, delete this exception statement from your
- version. If you delete this exception statement from all source files
- in the program, then also delete it here.
- .
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- .
- You should have received a copy of the GNU General Public License along
- with this package; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- .
- On Debian systems, the full text of the GNU General Public License
- version 3 can be found in the file
- `/usr/share/common-licenses/GPL-3'.
-
-
-License: zlib
- This software is provided 'as-is', without any express or implied
- warranty.  In no event will the authors be held liable for any
- damages arising from the use of this software.
- .
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- .
- 1. The origin of this software must not be misrepresented; you must
- not claim that you wrote the original software. If you use this
- software in a product, an acknowledgment in the product documentation
- would be appreciated but is not required.
- .
- 2. Altered source versions must be plainly marked as such, and must
- not be misrepresented as being the original software.
- .
- 3. This notice may not be removed or altered from any source
- distribution.
-
-
-License: BSD-3-clause
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
- .
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- .
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- .
- * The name of the autors may be used to endorse or promote
- products derived from this software without specific prior written
- permission.
- .
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-License: BSD-2-clause
- Copyright 2005-2016 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:
- .
- 1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
- .
- 2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation and/or
- other materials provided with the distribution.
- .
- THIS SOFTWARE IS PROVIDED BY OPENLAYERS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
- OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
- SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/docs/OrthancWSIDicomToTiff.1 b/debian/docs/OrthancWSIDicomToTiff.1
deleted file mode 100644
index 8497cc7..0000000
--- a/debian/docs/OrthancWSIDicomToTiff.1
+++ /dev/null
@@ -1,52 +0,0 @@
-.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.4.
-.TH ORTHANCWSIDICOMTOTIFF "1" "October 2016" "OrthancWSIDicomToTiff 0.1" "User Commands"
-.SH NAME
-OrthancWSIDicomToTiff \- Whole-slide imaging support for Orthanc
-.SH SYNOPSIS
-.B OrthancWSIDicomToTiff
-[\fI\,OPTION\/\fR]... [\fI\,INPUT\/\fR] [\fI\,OUTPUT\/\fR]
-.SH DESCRIPTION
-Orthanc, lightweight, RESTful DICOM server for healthcare and medical research.
-.PP
-Convert a DICOM for digital pathology stored in some Orthanc server as a standard hierarchical TIFF.
-.SS "Generic options:"
-.TP
-\fB\-\-help\fR
-Display this help and exit
-.TP
-\fB\-\-version\fR
-Output version information and exit
-.TP
-\fB\-\-verbose\fR
-Be verbose in logs
-.SS "Options for the source DICOM image:"
-.TP
-\fB\-\-orthanc\fR arg (=http://localhost:8042/)
-URL to the REST API of the target
-Orthanc server
-.TP
-\fB\-\-username\fR arg
-Username for the target Orthanc server
-.TP
-\fB\-\-password\fR arg
-Password for the target Orthanc server
-.SS "Options for the target TIFF image:"
-.TP
-\fB\-\-color\fR arg
-Color of the background for missing
-tiles (e.g. "255,0,0")
-.TP
-\fB\-\-reencode\fR arg
-Whether to re\-encode each tile in JPEG
-(no transcoding, much slower) (Boolean)
-.TP
-\fB\-\-jpeg\-quality\fR arg
-Set quality level for JPEG (0..100)
-.SH AUTHOR
-Written by Sebastien Jodogne <s.jodogne at gmail.com>
-.SH COPYRIGHT
-Copyright \(co 2012\-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)
-Licensing AGPL: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl.html>.
-.br
-This is free software: you are free to change and redistribute it.
-There is NO WARRANTY, to the extent permitted by law.
diff --git a/debian/docs/OrthancWSIDicomizer.1 b/debian/docs/OrthancWSIDicomizer.1
deleted file mode 100644
index 00927e3..0000000
--- a/debian/docs/OrthancWSIDicomizer.1
+++ /dev/null
@@ -1,145 +0,0 @@
-.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.4.
-.TH ORTHANCWSIDICOMIZER "1" "October 2016" "OrthancWSIDicomizer 0.1" "User Commands"
-.SH NAME
-OrthancWSIDicomizer \- Whole-slide imaging support for Orthanc
-.SH SYNOPSIS
-.B OrthancWSIDicomizer
-[\fI\,OPTION\/\fR]... [\fI\,INPUT\/\fR]
-.SH DESCRIPTION
-Orthanc, lightweight, RESTful DICOM server for healthcare and medical research.
-.PP
-Create a DICOM file from a digital pathology image.
-.SS "Generic options:"
-.TP
-\fB\-\-help\fR
-Display this help and exit
-.TP
-\fB\-\-version\fR
-Output version information and exit
-.TP
-\fB\-\-verbose\fR
-Be verbose in logs
-.TP
-\fB\-\-threads\fR arg (=2)
-Number of processing threads to be used
-.TP
-\fB\-\-openslide\fR arg
-Path to the shared library of OpenSlide
-(not necessary if converting from
-standard hierarchical TIFF)
-.SS "Options for the source image:"
-.TP
-\fB\-\-dataset\fR arg
-Path to a JSON file containing the
-DICOM dataset
-.TP
-\fB\-\-sample\-dataset\fR
-Display a minimalistic sample DICOM
-dataset in JSON format, then exit
-.TP
-\fB\-\-reencode\fR arg
-Whether to re\-encode each tile (no
-transcoding, much slower) (Boolean)
-.TP
-\fB\-\-repaint\fR arg
-Whether to repaint the background of
-the image (Boolean)
-.TP
-\fB\-\-color\fR arg
-Color of the background (e.g.
-"255,0,0")
-.SS "Options to construct the pyramid:"
-.TP
-\fB\-\-pyramid\fR arg (=0)
-Reconstruct the full pyramid (slow)
-(Boolean)
-.TP
-\fB\-\-smooth\fR arg (=0)
-Apply smoothing when reconstructing the
-pyramid (slower, but higher quality)
-(Boolean)
-.TP
-\fB\-\-levels\fR arg
-Number of levels in the target pyramid
-.SS "Options for the target image:"
-.TP
-\fB\-\-tile\-width\fR arg
-Width of the tiles in the target image
-.TP
-\fB\-\-tile\-height\fR arg
-Height of the tiles in the target image
-.TP
-\fB\-\-compression\fR arg
-Compression of the target image
-("none", "jpeg" or "jpeg2000")
-.TP
-\fB\-\-jpeg\-quality\fR arg
-Set quality level for JPEG (0..100)
-.TP
-\fB\-\-max\-size\fR arg (=10)
-Maximum size per DICOM instance (in MB)
-.TP
-\fB\-\-folder\fR arg
-Folder where to store the output DICOM
-instances
-.TP
-\fB\-\-folder\-pattern\fR arg (=wsi\-%06d.dcm)
-Pattern for the files in the output
-folder
-.TP
-\fB\-\-orthanc\fR arg (=http://localhost:8042/)
-URL to the REST API of the target
-Orthanc server
-.TP
-\fB\-\-username\fR arg
-Username for the target Orthanc server
-.TP
-\fB\-\-password\fR arg
-Password for the target Orthanc server
-.SS "Description of the imaged volume:"
-.TP
-\fB\-\-imaged\-width\fR arg (=15)
-With of the specimen (in mm)
-.TP
-\fB\-\-imaged\-height\fR arg (=15)
-Height of the specimen (in mm)
-.TP
-\fB\-\-imaged\-depth\fR arg (=1)
-Depth of the specimen (in mm)
-.TP
-\fB\-\-offset\-x\fR arg (=20)
-X offset the specimen, wrt. slide
-coordinates origin (in mm)
-.TP
-\fB\-\-offset\-y\fR arg (=40)
-Y offset the specimen, wrt. slide
-coordinates origin (in mm)
-.SS "Advanced options:"
-.TP
-\fB\-\-optical\-path\fR arg (=brightfield)
-Optical path to be automatically added
-to the DICOM dataset ("none" or
-"brightfield")
-.TP
-\fB\-\-icc\-profile\fR arg
-Path to the ICC profile to be included.
-If empty, a default sRGB profile will
-be added.
-.TP
-\fB\-\-safety\fR arg (=1)
-Whether to do additional checks to
-verify the source image is supported
-(might slow down) (Boolean)
-.TP
-\fB\-\-lower\-levels\fR arg
-Number of pyramid levels up to which
-multithreading should be applied (only
-for performance/memory tuning)
-.SH AUTHOR
-Written by Sebastien Jodogne <s.jodogne at gmail.com>
-.SH COPYRIGHT
-Copyright \(co 2012\-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)
-Licensing AGPL: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl.html>.
-.br
-This is free software: you are free to change and redistribute it.
-There is NO WARRANTY, to the extent permitted by law.
diff --git a/debian/manpages b/debian/manpages
deleted file mode 100644
index 2a5ebc5..0000000
--- a/debian/manpages
+++ /dev/null
@@ -1 +0,0 @@
-debian/docs/*.1
diff --git a/debian/patches/cmake b/debian/patches/cmake
deleted file mode 100644
index b4855bc..0000000
--- a/debian/patches/cmake
+++ /dev/null
@@ -1,22 +0,0 @@
-Description: Fix the version of the shared library
-Author: Sebastien Jodogne <s.jodogne at gmail.com>
----
-This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
-Index: OrthancWSI-1.0/ViewerPlugin/CMakeLists.txt
-===================================================================
---- OrthancWSI-1.0.orig/ViewerPlugin/CMakeLists.txt
-+++ OrthancWSI-1.0/ViewerPlugin/CMakeLists.txt
-@@ -227,9 +227,10 @@ add_library(OrthancWSI SHARED
-   )
- 
- message("Setting the version of the library to ${ORTHANC_WSI_VERSION}")
--set_target_properties(OrthancWSI PROPERTIES 
--  VERSION ${ORTHANC_WSI_VERSION} 
--  SOVERSION ${ORTHANC_WSI_VERSION})
-+set_target_properties(OrthancWSI PROPERTIES
-+  NO_SONAME ON
-+  LINK_FLAGS "-Wl,-soname,libOrthancWSI.so.${ORTHANC_WSI_VERSION}"
-+  )
- 
- install(
-   TARGETS OrthancWSI
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index a3ea3e4..0000000
--- a/debian/patches/series
+++ /dev/null
@@ -1 +0,0 @@
-cmake
diff --git a/debian/postinst b/debian/postinst
deleted file mode 100755
index b1e0ded..0000000
--- a/debian/postinst
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-
-set -e
-
-case $1 in
-    configure)
-        # Restart the Orthanc service
-        # https://www.debian.org/doc/debian-policy/ch-opersys.html#s9.3.3.2
-        if [ -x /etc/init.d/orthanc ]; then
-            if which invoke-rc.d >/dev/null 2>&1; then
-                invoke-rc.d orthanc restart
-            else
-                /etc/init.d/orthanc restart
-            fi
-        fi
-        ;;
-
-    abort-upgrade|abort-remove|abort-deconfigure)
-        ;;
-
-    *)
-        echo "prerm called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-#DEBHELPER#
diff --git a/debian/postrm b/debian/postrm
deleted file mode 100755
index 1e5be69..0000000
--- a/debian/postrm
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-
-set -e
-
-case $1 in
-    purge|remove|upgrade)
-        # Restart the Orthanc service
-        # https://www.debian.org/doc/debian-policy/ch-opersys.html#s9.3.3.2
-        if [ -x /etc/init.d/orthanc ]; then
-            if which invoke-rc.d >/dev/null 2>&1; then
-                invoke-rc.d orthanc restart
-            else
-                /etc/init.d/orthanc restart
-            fi
-        fi
-        ;;
-
-    failed-upgrade|abort-install|abort-upgrade|disappear)
-        ;;
-
-    *)
-        echo "prerm called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-#DEBHELPER#
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 6230409..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/make -f
-
-export DEBIAN_VERSION := $(shell dpkg-parsechangelog | sed -n -e 's/^Version: //p')
-export UPSTREAM_VERSION := $(shell echo "$(DEBIAN_VERSION)" | cut -d '+' -f 1)
-export VIEWER_TARGET := libOrthancWSI.so
-
-export DEB_BUILD_MAINT_OPTIONS := hardening=+all
-
-%:
-	dh $@ --parallel
-
-CMAKE_APPLICATIONS_EXTRA_FLAGS += \
-	-DCMAKE_SKIP_RPATH:BOOL=ON \
-	-DCMAKE_BUILD_TYPE=""   # The build type must be left empty, see #711515
-
-CMAKE_VIEWER_EXTRA_FLAGS += \
-	-DCMAKE_SKIP_RPATH:BOOL=ON \
-	-DUSE_SYSTEM_OPENLAYERS=ON \
-	-DOPENLAYERS_CSS=$(CURDIR)/BuildViewer/ol.min.css \
-	-DOPENLAYERS_JS=$(CURDIR)/BuildViewer/ol.min.js \
-	-DCMAKE_BUILD_TYPE=""   # The build type must be left empty, see #711515
-
-override_dh_auto_configure:
-	# Configure the command-line tools
-	mkdir BuildApplications
-	dh_auto_configure --builddirectory=BuildApplications \
-			  --sourcedirectory=Applications \
-			  -- $(CMAKE_APPLICATIONS_EXTRA_FLAGS)
-
-	# Minify the official OpenLayers 3.19.0 distribution (Debian
-	# packages only contains OpenLayers 2 for the time being,
-	# which is incompatible with OpenLayers 3)
-	mkdir -p BuildViewer
-	yui-compressor debian/JS/openlayers-3.19.0/ol-debug.js > BuildViewer/ol.min.js
-	yui-compressor debian/JS/openlayers-3.19.0/ol.css > BuildViewer/ol.min.css
-
-	# Configure the Web viewer plugin
-	dh_auto_configure --builddirectory=BuildViewer \
-			  --sourcedirectory=ViewerPlugin \
-			  -- $(CMAKE_VIEWER_EXTRA_FLAGS)
-
-override_dh_auto_build:
-	# Build the command-line tools
-	dh_auto_build --builddirectory=BuildApplications \
-		      --sourcedirectory=Applications
-
-	# Build the Web viewer plugin
-	dh_auto_build --builddirectory=BuildViewer \
-		      --sourcedirectory=ViewerPlugin
-
-override_dh_auto_install:
-	# Install the command-line tools
-	dh_auto_install --builddirectory=BuildApplications
-
-	# Install the plugin
-	cp BuildViewer/${VIEWER_TARGET} BuildViewer/${VIEWER_TARGET}.${UPSTREAM_VERSION}
-	dh_install BuildViewer/${VIEWER_TARGET}.${UPSTREAM_VERSION} usr/lib
-
-override_dh_link:
-	dh_link usr/lib/${VIEWER_TARGET}.${UPSTREAM_VERSION} \
-		usr/share/orthanc/plugins/${VIEWER_TARGET}
-
-override_dh_installchangelogs:
-	dh_installchangelogs -k NEWS
-
-get-orig-source:
-	uscan --verbose --force-download --repack --compression xz
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/source/include-binaries b/debian/source/include-binaries
deleted file mode 100644
index 80b6624..0000000
--- a/debian/source/include-binaries
+++ /dev/null
@@ -1 +0,0 @@
-debian/ThirdPartyDownloads/openlayers-3.19.0-dist.zip
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
deleted file mode 100644
index 7cf8c15..0000000
--- a/debian/upstream/metadata
+++ /dev/null
@@ -1,6 +0,0 @@
-Reference:
-  Author: S. Jodogne and C. Bernard and M. Devillers and E. Lenaerts and P. Coucke
-  Title: "Orthanc -- A Lightweight, RESTful DICOM Server for Healthcare and Medical Research"
-  Type: book
-  Booktitle: Proc. of the International Symposium on Biomedical Imaging
-  Year: 2013
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index e3894c2..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,3 +0,0 @@
-version=3
-opts="repacksuffix=+dfsg,dversionmangle=s/\+dfsg//g,downloadurlmangle=s/\/browse\.php\?path=//g,repack,compression=xz" \
-http://www.orthanc-server.com/browse.php?path=/whole-slide-imaging downloads/get\.php\?path=/whole-slide-imaging/OrthancWSI-(\d\S*)\.tar\.gz

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/orthanc-wsi.git



More information about the debian-med-commit mailing list